aboutsummaryrefslogtreecommitdiff
path: root/modules/json
diff options
context:
space:
mode:
Diffstat (limited to 'modules/json')
-rw-r--r--modules/json/pom.xml26
-rw-r--r--modules/json/src/main/clojure/clojure/contrib/json.clj341
2 files changed, 367 insertions, 0 deletions
diff --git a/modules/json/pom.xml b/modules/json/pom.xml
new file mode 100644
index 00000000..95f611ca
--- /dev/null
+++ b/modules/json/pom.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http//www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
+ http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.clojure.contrib</groupId>
+ <artifactId>parent</artifactId>
+ <version>1.3.0-SNAPSHOT</version>
+ <relativePath>../parent</relativePath>
+ </parent>
+ <artifactId>json</artifactId>
+ <dependencies>
+ <dependency>
+ <groupId>org.clojure.contrib</groupId>
+ <artifactId>pprint</artifactId>
+ <version>1.3.0-SNAPSHOT</version>
+ </dependency>
+ <dependency>
+ <groupId>org.clojure.contrib</groupId>
+ <artifactId>string</artifactId>
+ <version>1.3.0-SNAPSHOT</version>
+ </dependency>
+ </dependencies>
+</project> \ No newline at end of file
diff --git a/modules/json/src/main/clojure/clojure/contrib/json.clj b/modules/json/src/main/clojure/clojure/contrib/json.clj
new file mode 100644
index 00000000..69f6cc9d
--- /dev/null
+++ b/modules/json/src/main/clojure/clojure/contrib/json.clj
@@ -0,0 +1,341 @@
+;;; json.clj: JavaScript Object Notation (JSON) parser/writer
+
+;; by Stuart Sierra, http://stuartsierra.com/
+;; January 30, 2010
+
+;; Copyright (c) Stuart Sierra, 2010. All rights reserved. The use
+;; and distribution terms for this software are covered by the Eclipse
+;; Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
+;; which can be found in the file epl-v10.html at the root of this
+;; distribution. By using this software in any fashion, you are
+;; agreeing to be bound by the terms of this license. You must not
+;; remove this notice, or any other, from this software.
+
+(ns ^{:author "Stuart Sierra"
+ :doc "JavaScript Object Notation (JSON) parser/writer.
+ See http://www.json.org/
+ To write JSON, use json-str, write-json, or write-json.
+ To read JSON, use read-json."}
+ clojure.contrib.json
+ (:use [clojure.contrib.pprint :only (write formatter-out)]
+ [clojure.contrib.string :only (as-str)])
+ (:import (java.io PrintWriter PushbackReader StringWriter
+ StringReader Reader EOFException)))
+
+;;; JSON READER
+
+(declare read-json-reader)
+
+(defn- read-json-array [^PushbackReader stream keywordize?]
+ ;; Expects to be called with the head of the stream AFTER the
+ ;; opening bracket.
+ (loop [i (.read stream), result (transient [])]
+ (let [c (char i)]
+ (cond
+ (= i -1) (throw (EOFException. "JSON error (end-of-file inside array)"))
+ (Character/isWhitespace c) (recur (.read stream) result)
+ (= c \,) (recur (.read stream) result)
+ (= c \]) (persistent! result)
+ :else (do (.unread stream (int c))
+ (let [element (read-json-reader stream keywordize? true nil)]
+ (recur (.read stream) (conj! result element))))))))
+
+(defn- read-json-object [^PushbackReader stream keywordize?]
+ ;; Expects to be called with the head of the stream AFTER the
+ ;; opening bracket.
+ (loop [i (.read stream), key nil, result (transient {})]
+ (let [c (char i)]
+ (cond
+ (= i -1) (throw (EOFException. "JSON error (end-of-file inside object)"))
+
+ (Character/isWhitespace c) (recur (.read stream) key result)
+
+ (= c \,) (recur (.read stream) nil result)
+
+ (= c \:) (recur (.read stream) key result)
+
+ (= c \}) (if (nil? key)
+ (persistent! result)
+ (throw (Exception. "JSON error (key missing value in object)")))
+
+ :else (do (.unread stream i)
+ (let [element (read-json-reader stream keywordize? true nil)]
+ (if (nil? key)
+ (if (string? element)
+ (recur (.read stream) element result)
+ (throw (Exception. "JSON error (non-string key in object)")))
+ (recur (.read stream) nil
+ (assoc! result (if keywordize? (keyword key) key)
+ element)))))))))
+
+(defn- read-json-hex-character [^PushbackReader stream]
+ ;; Expects to be called with the head of the stream AFTER the
+ ;; initial "\u". Reads the next four characters from the stream.
+ (let [digits [(.read stream)
+ (.read stream)
+ (.read stream)
+ (.read stream)]]
+ (when (some neg? digits)
+ (throw (EOFException. "JSON error (end-of-file inside Unicode character escape)")))
+ (let [chars (map char digits)]
+ (when-not (every? #{\0 \1 \2 \3 \4 \5 \6 \7 \8 \9 \a \b \c \d \e \f \A \B \C \D \E \F}
+ chars)
+ (throw (Exception. "JSON error (invalid hex character in Unicode character escape)")))
+ (char (Integer/parseInt (apply str chars) 16)))))
+
+(defn- read-json-escaped-character [^PushbackReader stream]
+ ;; Expects to be called with the head of the stream AFTER the
+ ;; initial backslash.
+ (let [c (char (.read stream))]
+ (cond
+ (#{\" \\ \/} c) c
+ (= c \b) \backspace
+ (= c \f) \formfeed
+ (= c \n) \newline
+ (= c \r) \return
+ (= c \t) \tab
+ (= c \u) (read-json-hex-character stream))))
+
+(defn- read-json-quoted-string [^PushbackReader stream]
+ ;; Expects to be called with the head of the stream AFTER the
+ ;; opening quotation mark.
+ (let [buffer (StringBuilder.)]
+ (loop [i (.read stream)]
+ (let [c (char i)]
+ (cond
+ (= i -1) (throw (EOFException. "JSON error (end-of-file inside string)"))
+ (= c \") (str buffer)
+ (= c \\) (do (.append buffer (read-json-escaped-character stream))
+ (recur (.read stream)))
+ :else (do (.append buffer c)
+ (recur (.read stream))))))))
+
+(defn- read-json-reader
+ ([^PushbackReader stream keywordize? eof-error? eof-value]
+ (loop [i (.read stream)]
+ (let [c (char i)]
+ (cond
+ ;; Handle end-of-stream
+ (= i -1) (if eof-error?
+ (throw (EOFException. "JSON error (end-of-file)"))
+ eof-value)
+
+ ;; Ignore whitespace
+ (Character/isWhitespace c) (recur (.read stream))
+
+ ;; Read numbers, true, and false with Clojure reader
+ (#{\- \0 \1 \2 \3 \4 \5 \6 \7 \8 \9} c)
+ (do (.unread stream i)
+ (read stream true nil))
+
+ ;; Read strings
+ (= c \") (read-json-quoted-string stream)
+
+ ;; Read null as nil
+ (= c \n) (let [ull [(char (.read stream))
+ (char (.read stream))
+ (char (.read stream))]]
+ (if (= ull [\u \l \l])
+ nil
+ (throw (Exception. (str "JSON error (expected null): " c ull)))))
+
+ ;; Read true
+ (= c \t) (let [rue [(char (.read stream))
+ (char (.read stream))
+ (char (.read stream))]]
+ (if (= rue [\r \u \e])
+ true
+ (throw (Exception. (str "JSON error (expected true): " c rue)))))
+
+ ;; Read false
+ (= c \f) (let [alse [(char (.read stream))
+ (char (.read stream))
+ (char (.read stream))
+ (char (.read stream))]]
+ (if (= alse [\a \l \s \e])
+ false
+ (throw (Exception. (str "JSON error (expected false): " c alse)))))
+
+ ;; Read JSON objects
+ (= c \{) (read-json-object stream keywordize?)
+
+ ;; Read JSON arrays
+ (= c \[) (read-json-array stream keywordize?)
+
+ :else (throw (Exception. (str "JSON error (unexpected character): " c))))))))
+
+(defprotocol Read-JSON-From
+ (read-json-from [input keywordize? eof-error? eof-value]
+ "Reads one JSON value from input String or Reader.
+ If keywordize? is true, object keys will be converted to keywords.
+ If eof-error? is true, empty input will throw an EOFException; if
+ false EOF will return eof-value. "))
+
+(extend-protocol
+ Read-JSON-From
+ String
+ (read-json-from [input keywordize? eof-error? eof-value]
+ (read-json-reader (PushbackReader. (StringReader. input))
+ keywordize? eof-error? eof-value))
+ PushbackReader
+ (read-json-from [input keywordize? eof-error? eof-value]
+ (read-json-reader input
+ keywordize? eof-error? eof-value))
+ Reader
+ (read-json-from [input keywordize? eof-error? eof-value]
+ (read-json-reader (PushbackReader. input)
+ keywordize? eof-error? eof-value)))
+
+(defn read-json
+ "Reads one JSON value from input String or Reader.
+ If keywordize? is true (default), object keys will be converted to
+ keywords. If eof-error? is true (default), empty input will throw
+ an EOFException; if false EOF will return eof-value. "
+ ([input]
+ (read-json-from input true true nil))
+ ([input keywordize?]
+ (read-json-from input keywordize? true nil))
+ ([input keywordize? eof-error? eof-value]
+ (read-json-from input keywordize? eof-error? eof-value)))
+
+
+;;; JSON PRINTER
+
+(defprotocol Write-JSON
+ (write-json [object out]
+ "Print object to PrintWriter out as JSON"))
+
+(defn- write-json-string [^CharSequence s ^PrintWriter out]
+ (let [sb (StringBuilder. ^Integer (count s))]
+ (.append sb \")
+ (dotimes [i (count s)]
+ (let [cp (Character/codePointAt s i)]
+ (cond
+ ;; Handle printable JSON escapes before ASCII
+ (= cp 34) (.append sb "\\\"")
+ (= cp 92) (.append sb "\\\\")
+ (= cp 47) (.append sb "\\/")
+ ;; Print simple ASCII characters
+ (< 31 cp 127) (.append sb (.charAt s i))
+ ;; Handle non-printable JSON escapes
+ (= cp 8) (.append sb "\\b")
+ (= cp 12) (.append sb "\\f")
+ (= cp 10) (.append sb "\\n")
+ (= cp 13) (.append sb "\\r")
+ (= cp 9) (.append sb "\\t")
+ ;; Any other character is Hexadecimal-escaped
+ :else (.append sb (format "\\u%04x" cp)))))
+ (.append sb \")
+ (.print out (str sb))))
+
+(defn- write-json-object [m ^PrintWriter out]
+ (.print out \{)
+ (loop [x m]
+ (when (seq m)
+ (let [[k v] (first x)]
+ (when (nil? k)
+ (throw (Exception. "JSON object keys cannot be nil/null")))
+ (.print out \")
+ (.print out (as-str k))
+ (.print out \")
+ (.print out \:)
+ (write-json v out))
+ (let [nxt (next x)]
+ (when (seq nxt)
+ (.print out \,)
+ (recur nxt)))))
+ (.print out \}))
+
+(defn- write-json-array [s ^PrintWriter out]
+ (.print out \[)
+ (loop [x s]
+ (when (seq x)
+ (let [fst (first x)
+ nxt (next x)]
+ (write-json fst out)
+ (when (seq nxt)
+ (.print out \,)
+ (recur nxt)))))
+ (.print out \]))
+
+(defn- write-json-bignum [x ^PrintWriter out]
+ (.print out (str x)))
+
+(defn- write-json-plain [x ^PrintWriter out]
+ (.print out x))
+
+(defn- write-json-null [x ^PrintWriter out]
+ (.print out "null"))
+
+(defn- write-json-named [x ^PrintWriter out]
+ (write-json-string (name x) out))
+
+(defn- write-json-generic [x out]
+ (if (.isArray (class x))
+ (write-json (seq x) out)
+ (throw (Exception. (str "Don't know how to write JSON of " (class x))))))
+
+(extend nil Write-JSON
+ {:write-json write-json-null})
+(extend clojure.lang.Named Write-JSON
+ {:write-json write-json-named})
+(extend java.lang.Boolean Write-JSON
+ {:write-json write-json-plain})
+(extend java.lang.Number Write-JSON
+ {:write-json write-json-plain})
+(extend java.math.BigInteger Write-JSON
+ {:write-json write-json-bignum})
+(extend java.math.BigDecimal Write-JSON
+ {:write-json write-json-bignum})
+(extend java.lang.CharSequence Write-JSON
+ {:write-json write-json-string})
+(extend java.util.Map Write-JSON
+ {:write-json write-json-object})
+(extend java.util.Collection Write-JSON
+ {:write-json write-json-array})
+(extend clojure.lang.ISeq Write-JSON
+ {:write-json write-json-array})
+(extend java.lang.Object Write-JSON
+ {:write-json write-json-generic})
+
+(defn json-str
+ "Converts x to a JSON-formatted string."
+ [x]
+ (let [sw (StringWriter.)
+ out (PrintWriter. sw)]
+ (write-json x out)
+ (.toString sw)))
+
+(defn print-json
+ "Write JSON-formatted output to *out*"
+ [x]
+ (write-json x *out*))
+
+
+;;; JSON PRETTY-PRINTER
+
+;; Based on code by Tom Faulhaber
+
+(defn- pprint-json-array [s]
+ ((formatter-out "~<[~;~@{~w~^, ~:_~}~;]~:>") s))
+
+(defn- pprint-json-object [m]
+ ((formatter-out "~<{~;~@{~<~w:~_~w~:>~^, ~_~}~;}~:>")
+ (for [[k v] m] [(as-str k) v])))
+
+(defn- pprint-json-generic [x]
+ (if (.isArray (class x))
+ (pprint-json-array (seq x))
+ (print (json-str x))))
+
+(defn- pprint-json-dispatch [x]
+ (cond (nil? x) (print "null")
+ (instance? java.util.Map x) (pprint-json-object x)
+ (instance? java.util.Collection x) (pprint-json-array x)
+ (instance? clojure.lang.ISeq x) (pprint-json-array x)
+ :else (pprint-json-generic x)))
+
+(defn pprint-json
+ "Pretty-prints JSON representation of x to *out*"
+ [x]
+ (write x :dispatch pprint-json-dispatch))