annotate src/clojure/contrib/json.clj @ 10:ef7dbbd6452c

added clojure source goodness
author Robert McIntyre <rlm@mit.edu>
date Sat, 21 Aug 2010 06:25:44 -0400
parents
children
rev   line source
rlm@10 1 ;;; json.clj: JavaScript Object Notation (JSON) parser/writer
rlm@10 2
rlm@10 3 ;; by Stuart Sierra, http://stuartsierra.com/
rlm@10 4 ;; January 30, 2010
rlm@10 5
rlm@10 6 ;; Copyright (c) Stuart Sierra, 2010. All rights reserved. The use
rlm@10 7 ;; and distribution terms for this software are covered by the Eclipse
rlm@10 8 ;; Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
rlm@10 9 ;; which can be found in the file epl-v10.html at the root of this
rlm@10 10 ;; distribution. By using this software in any fashion, you are
rlm@10 11 ;; agreeing to be bound by the terms of this license. You must not
rlm@10 12 ;; remove this notice, or any other, from this software.
rlm@10 13
rlm@10 14 (ns ^{:author "Stuart Sierra"
rlm@10 15 :doc "JavaScript Object Notation (JSON) parser/writer.
rlm@10 16 See http://www.json.org/
rlm@10 17 To write JSON, use json-str, write-json, or write-json.
rlm@10 18 To read JSON, use read-json."}
rlm@10 19 clojure.contrib.json
rlm@10 20 (:use [clojure.contrib.pprint :only (write formatter-out)]
rlm@10 21 [clojure.contrib.string :only (as-str)])
rlm@10 22 (:import (java.io PrintWriter PushbackReader StringWriter
rlm@10 23 StringReader Reader EOFException)))
rlm@10 24
rlm@10 25 ;;; JSON READER
rlm@10 26
rlm@10 27 (declare read-json-reader)
rlm@10 28
rlm@10 29 (defn- read-json-array [^PushbackReader stream keywordize?]
rlm@10 30 ;; Expects to be called with the head of the stream AFTER the
rlm@10 31 ;; opening bracket.
rlm@10 32 (loop [i (.read stream), result (transient [])]
rlm@10 33 (let [c (char i)]
rlm@10 34 (cond
rlm@10 35 (= i -1) (throw (EOFException. "JSON error (end-of-file inside array)"))
rlm@10 36 (Character/isWhitespace c) (recur (.read stream) result)
rlm@10 37 (= c \,) (recur (.read stream) result)
rlm@10 38 (= c \]) (persistent! result)
rlm@10 39 :else (do (.unread stream (int c))
rlm@10 40 (let [element (read-json-reader stream keywordize? true nil)]
rlm@10 41 (recur (.read stream) (conj! result element))))))))
rlm@10 42
rlm@10 43 (defn- read-json-object [^PushbackReader stream keywordize?]
rlm@10 44 ;; Expects to be called with the head of the stream AFTER the
rlm@10 45 ;; opening bracket.
rlm@10 46 (loop [i (.read stream), key nil, result (transient {})]
rlm@10 47 (let [c (char i)]
rlm@10 48 (cond
rlm@10 49 (= i -1) (throw (EOFException. "JSON error (end-of-file inside object)"))
rlm@10 50
rlm@10 51 (Character/isWhitespace c) (recur (.read stream) key result)
rlm@10 52
rlm@10 53 (= c \,) (recur (.read stream) nil result)
rlm@10 54
rlm@10 55 (= c \:) (recur (.read stream) key result)
rlm@10 56
rlm@10 57 (= c \}) (if (nil? key)
rlm@10 58 (persistent! result)
rlm@10 59 (throw (Exception. "JSON error (key missing value in object)")))
rlm@10 60
rlm@10 61 :else (do (.unread stream i)
rlm@10 62 (let [element (read-json-reader stream keywordize? true nil)]
rlm@10 63 (if (nil? key)
rlm@10 64 (if (string? element)
rlm@10 65 (recur (.read stream) element result)
rlm@10 66 (throw (Exception. "JSON error (non-string key in object)")))
rlm@10 67 (recur (.read stream) nil
rlm@10 68 (assoc! result (if keywordize? (keyword key) key)
rlm@10 69 element)))))))))
rlm@10 70
rlm@10 71 (defn- read-json-hex-character [^PushbackReader stream]
rlm@10 72 ;; Expects to be called with the head of the stream AFTER the
rlm@10 73 ;; initial "\u". Reads the next four characters from the stream.
rlm@10 74 (let [digits [(.read stream)
rlm@10 75 (.read stream)
rlm@10 76 (.read stream)
rlm@10 77 (.read stream)]]
rlm@10 78 (when (some neg? digits)
rlm@10 79 (throw (EOFException. "JSON error (end-of-file inside Unicode character escape)")))
rlm@10 80 (let [chars (map char digits)]
rlm@10 81 (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}
rlm@10 82 chars)
rlm@10 83 (throw (Exception. "JSON error (invalid hex character in Unicode character escape)")))
rlm@10 84 (char (Integer/parseInt (apply str chars) 16)))))
rlm@10 85
rlm@10 86 (defn- read-json-escaped-character [^PushbackReader stream]
rlm@10 87 ;; Expects to be called with the head of the stream AFTER the
rlm@10 88 ;; initial backslash.
rlm@10 89 (let [c (char (.read stream))]
rlm@10 90 (cond
rlm@10 91 (#{\" \\ \/} c) c
rlm@10 92 (= c \b) \backspace
rlm@10 93 (= c \f) \formfeed
rlm@10 94 (= c \n) \newline
rlm@10 95 (= c \r) \return
rlm@10 96 (= c \t) \tab
rlm@10 97 (= c \u) (read-json-hex-character stream))))
rlm@10 98
rlm@10 99 (defn- read-json-quoted-string [^PushbackReader stream]
rlm@10 100 ;; Expects to be called with the head of the stream AFTER the
rlm@10 101 ;; opening quotation mark.
rlm@10 102 (let [buffer (StringBuilder.)]
rlm@10 103 (loop [i (.read stream)]
rlm@10 104 (let [c (char i)]
rlm@10 105 (cond
rlm@10 106 (= i -1) (throw (EOFException. "JSON error (end-of-file inside string)"))
rlm@10 107 (= c \") (str buffer)
rlm@10 108 (= c \\) (do (.append buffer (read-json-escaped-character stream))
rlm@10 109 (recur (.read stream)))
rlm@10 110 :else (do (.append buffer c)
rlm@10 111 (recur (.read stream))))))))
rlm@10 112
rlm@10 113 (defn- read-json-reader
rlm@10 114 ([^PushbackReader stream keywordize? eof-error? eof-value]
rlm@10 115 (loop [i (.read stream)]
rlm@10 116 (let [c (char i)]
rlm@10 117 (cond
rlm@10 118 ;; Handle end-of-stream
rlm@10 119 (= i -1) (if eof-error?
rlm@10 120 (throw (EOFException. "JSON error (end-of-file)"))
rlm@10 121 eof-value)
rlm@10 122
rlm@10 123 ;; Ignore whitespace
rlm@10 124 (Character/isWhitespace c) (recur (.read stream))
rlm@10 125
rlm@10 126 ;; Read numbers, true, and false with Clojure reader
rlm@10 127 (#{\- \0 \1 \2 \3 \4 \5 \6 \7 \8 \9} c)
rlm@10 128 (do (.unread stream i)
rlm@10 129 (read stream true nil))
rlm@10 130
rlm@10 131 ;; Read strings
rlm@10 132 (= c \") (read-json-quoted-string stream)
rlm@10 133
rlm@10 134 ;; Read null as nil
rlm@10 135 (= c \n) (let [ull [(char (.read stream))
rlm@10 136 (char (.read stream))
rlm@10 137 (char (.read stream))]]
rlm@10 138 (if (= ull [\u \l \l])
rlm@10 139 nil
rlm@10 140 (throw (Exception. (str "JSON error (expected null): " c ull)))))
rlm@10 141
rlm@10 142 ;; Read true
rlm@10 143 (= c \t) (let [rue [(char (.read stream))
rlm@10 144 (char (.read stream))
rlm@10 145 (char (.read stream))]]
rlm@10 146 (if (= rue [\r \u \e])
rlm@10 147 true
rlm@10 148 (throw (Exception. (str "JSON error (expected true): " c rue)))))
rlm@10 149
rlm@10 150 ;; Read false
rlm@10 151 (= c \f) (let [alse [(char (.read stream))
rlm@10 152 (char (.read stream))
rlm@10 153 (char (.read stream))
rlm@10 154 (char (.read stream))]]
rlm@10 155 (if (= alse [\a \l \s \e])
rlm@10 156 false
rlm@10 157 (throw (Exception. (str "JSON error (expected false): " c alse)))))
rlm@10 158
rlm@10 159 ;; Read JSON objects
rlm@10 160 (= c \{) (read-json-object stream keywordize?)
rlm@10 161
rlm@10 162 ;; Read JSON arrays
rlm@10 163 (= c \[) (read-json-array stream keywordize?)
rlm@10 164
rlm@10 165 :else (throw (Exception. (str "JSON error (unexpected character): " c))))))))
rlm@10 166
rlm@10 167 (defprotocol Read-JSON-From
rlm@10 168 (read-json-from [input keywordize? eof-error? eof-value]
rlm@10 169 "Reads one JSON value from input String or Reader.
rlm@10 170 If keywordize? is true, object keys will be converted to keywords.
rlm@10 171 If eof-error? is true, empty input will throw an EOFException; if
rlm@10 172 false EOF will return eof-value. "))
rlm@10 173
rlm@10 174 (extend-protocol
rlm@10 175 Read-JSON-From
rlm@10 176 String
rlm@10 177 (read-json-from [input keywordize? eof-error? eof-value]
rlm@10 178 (read-json-reader (PushbackReader. (StringReader. input))
rlm@10 179 keywordize? eof-error? eof-value))
rlm@10 180 PushbackReader
rlm@10 181 (read-json-from [input keywordize? eof-error? eof-value]
rlm@10 182 (read-json-reader input
rlm@10 183 keywordize? eof-error? eof-value))
rlm@10 184 Reader
rlm@10 185 (read-json-from [input keywordize? eof-error? eof-value]
rlm@10 186 (read-json-reader (PushbackReader. input)
rlm@10 187 keywordize? eof-error? eof-value)))
rlm@10 188
rlm@10 189 (defn read-json
rlm@10 190 "Reads one JSON value from input String or Reader.
rlm@10 191 If keywordize? is true (default), object keys will be converted to
rlm@10 192 keywords. If eof-error? is true (default), empty input will throw
rlm@10 193 an EOFException; if false EOF will return eof-value. "
rlm@10 194 ([input]
rlm@10 195 (read-json-from input true true nil))
rlm@10 196 ([input keywordize?]
rlm@10 197 (read-json-from input keywordize? true nil))
rlm@10 198 ([input keywordize? eof-error? eof-value]
rlm@10 199 (read-json-from input keywordize? eof-error? eof-value)))
rlm@10 200
rlm@10 201
rlm@10 202 ;;; JSON PRINTER
rlm@10 203
rlm@10 204 (defprotocol Write-JSON
rlm@10 205 (write-json [object out]
rlm@10 206 "Print object to PrintWriter out as JSON"))
rlm@10 207
rlm@10 208 (defn- write-json-string [^CharSequence s ^PrintWriter out]
rlm@10 209 (let [sb (StringBuilder. ^Integer (count s))]
rlm@10 210 (.append sb \")
rlm@10 211 (dotimes [i (count s)]
rlm@10 212 (let [cp (Character/codePointAt s i)]
rlm@10 213 (cond
rlm@10 214 ;; Handle printable JSON escapes before ASCII
rlm@10 215 (= cp 34) (.append sb "\\\"")
rlm@10 216 (= cp 92) (.append sb "\\\\")
rlm@10 217 (= cp 47) (.append sb "\\/")
rlm@10 218 ;; Print simple ASCII characters
rlm@10 219 (< 31 cp 127) (.append sb (.charAt s i))
rlm@10 220 ;; Handle non-printable JSON escapes
rlm@10 221 (= cp 8) (.append sb "\\b")
rlm@10 222 (= cp 12) (.append sb "\\f")
rlm@10 223 (= cp 10) (.append sb "\\n")
rlm@10 224 (= cp 13) (.append sb "\\r")
rlm@10 225 (= cp 9) (.append sb "\\t")
rlm@10 226 ;; Any other character is Hexadecimal-escaped
rlm@10 227 :else (.append sb (format "\\u%04x" cp)))))
rlm@10 228 (.append sb \")
rlm@10 229 (.print out (str sb))))
rlm@10 230
rlm@10 231 (defn- write-json-object [m ^PrintWriter out]
rlm@10 232 (.print out \{)
rlm@10 233 (loop [x m]
rlm@10 234 (when (seq m)
rlm@10 235 (let [[k v] (first x)]
rlm@10 236 (when (nil? k)
rlm@10 237 (throw (Exception. "JSON object keys cannot be nil/null")))
rlm@10 238 (.print out \")
rlm@10 239 (.print out (as-str k))
rlm@10 240 (.print out \")
rlm@10 241 (.print out \:)
rlm@10 242 (write-json v out))
rlm@10 243 (let [nxt (next x)]
rlm@10 244 (when (seq nxt)
rlm@10 245 (.print out \,)
rlm@10 246 (recur nxt)))))
rlm@10 247 (.print out \}))
rlm@10 248
rlm@10 249 (defn- write-json-array [s ^PrintWriter out]
rlm@10 250 (.print out \[)
rlm@10 251 (loop [x s]
rlm@10 252 (when (seq x)
rlm@10 253 (let [fst (first x)
rlm@10 254 nxt (next x)]
rlm@10 255 (write-json fst out)
rlm@10 256 (when (seq nxt)
rlm@10 257 (.print out \,)
rlm@10 258 (recur nxt)))))
rlm@10 259 (.print out \]))
rlm@10 260
rlm@10 261 (defn- write-json-bignum [x ^PrintWriter out]
rlm@10 262 (.print out (str x)))
rlm@10 263
rlm@10 264 (defn- write-json-plain [x ^PrintWriter out]
rlm@10 265 (.print out x))
rlm@10 266
rlm@10 267 (defn- write-json-null [x ^PrintWriter out]
rlm@10 268 (.print out "null"))
rlm@10 269
rlm@10 270 (defn- write-json-named [x ^PrintWriter out]
rlm@10 271 (write-json-string (name x) out))
rlm@10 272
rlm@10 273 (defn- write-json-generic [x out]
rlm@10 274 (if (.isArray (class x))
rlm@10 275 (write-json (seq x) out)
rlm@10 276 (throw (Exception. (str "Don't know how to write JSON of " (class x))))))
rlm@10 277
rlm@10 278 (extend nil Write-JSON
rlm@10 279 {:write-json write-json-null})
rlm@10 280 (extend clojure.lang.Named Write-JSON
rlm@10 281 {:write-json write-json-named})
rlm@10 282 (extend java.lang.Boolean Write-JSON
rlm@10 283 {:write-json write-json-plain})
rlm@10 284 (extend java.lang.Number Write-JSON
rlm@10 285 {:write-json write-json-plain})
rlm@10 286 (extend java.math.BigInteger Write-JSON
rlm@10 287 {:write-json write-json-bignum})
rlm@10 288 (extend java.math.BigDecimal Write-JSON
rlm@10 289 {:write-json write-json-bignum})
rlm@10 290 (extend java.lang.CharSequence Write-JSON
rlm@10 291 {:write-json write-json-string})
rlm@10 292 (extend java.util.Map Write-JSON
rlm@10 293 {:write-json write-json-object})
rlm@10 294 (extend java.util.Collection Write-JSON
rlm@10 295 {:write-json write-json-array})
rlm@10 296 (extend clojure.lang.ISeq Write-JSON
rlm@10 297 {:write-json write-json-array})
rlm@10 298 (extend java.lang.Object Write-JSON
rlm@10 299 {:write-json write-json-generic})
rlm@10 300
rlm@10 301 (defn json-str
rlm@10 302 "Converts x to a JSON-formatted string."
rlm@10 303 [x]
rlm@10 304 (let [sw (StringWriter.)
rlm@10 305 out (PrintWriter. sw)]
rlm@10 306 (write-json x out)
rlm@10 307 (.toString sw)))
rlm@10 308
rlm@10 309 (defn print-json
rlm@10 310 "Write JSON-formatted output to *out*"
rlm@10 311 [x]
rlm@10 312 (write-json x *out*))
rlm@10 313
rlm@10 314
rlm@10 315 ;;; JSON PRETTY-PRINTER
rlm@10 316
rlm@10 317 ;; Based on code by Tom Faulhaber
rlm@10 318
rlm@10 319 (defn- pprint-json-array [s]
rlm@10 320 ((formatter-out "~<[~;~@{~w~^, ~:_~}~;]~:>") s))
rlm@10 321
rlm@10 322 (defn- pprint-json-object [m]
rlm@10 323 ((formatter-out "~<{~;~@{~<~w:~_~w~:>~^, ~_~}~;}~:>")
rlm@10 324 (for [[k v] m] [(as-str k) v])))
rlm@10 325
rlm@10 326 (defn- pprint-json-generic [x]
rlm@10 327 (if (.isArray (class x))
rlm@10 328 (pprint-json-array (seq x))
rlm@10 329 (print (json-str x))))
rlm@10 330
rlm@10 331 (defn- pprint-json-dispatch [x]
rlm@10 332 (cond (nil? x) (print "null")
rlm@10 333 (instance? java.util.Map x) (pprint-json-object x)
rlm@10 334 (instance? java.util.Collection x) (pprint-json-array x)
rlm@10 335 (instance? clojure.lang.ISeq x) (pprint-json-array x)
rlm@10 336 :else (pprint-json-generic x)))
rlm@10 337
rlm@10 338 (defn pprint-json
rlm@10 339 "Pretty-prints JSON representation of x to *out*"
rlm@10 340 [x]
rlm@10 341 (write x :dispatch pprint-json-dispatch))