rlm@10
|
1 ;;; json.clj: A pretty printing version of the JavaScript Object Notation (JSON) generator
|
rlm@10
|
2
|
rlm@10
|
3 ;; by Tom Faulhaber, based on the version by Stuart Sierra (clojure.contrib.json.write)
|
rlm@10
|
4 ;; May 9, 2009
|
rlm@10
|
5
|
rlm@10
|
6 ;; Copyright (c) Tom Faulhaber/Stuart Sierra, 2009. 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
|
rlm@10
|
15 (ns
|
rlm@10
|
16 #^{:author "Tom Faulhaber (based on the version by Stuart Sierra)",
|
rlm@10
|
17 :doc "Pretty printing JavaScript Object Notation (JSON) generator.
|
rlm@10
|
18
|
rlm@10
|
19 This is an example of using a pretty printer dispatch function to generate JSON output",
|
rlm@10
|
20 :see-also [["http://json.org/", "JSON Home Page"]]}
|
rlm@10
|
21 clojure.contrib.pprint.examples.json
|
rlm@10
|
22 (:use [clojure.test :only (deftest- is)]
|
rlm@10
|
23 [clojure.contrib.string :only (as-str)]
|
rlm@10
|
24 [clojure.contrib.pprint :only (write formatter-out)]))
|
rlm@10
|
25
|
rlm@10
|
26
|
rlm@10
|
27
|
rlm@10
|
28 (defmulti dispatch-json
|
rlm@10
|
29 "The dispatch function for printing objects as JSON"
|
rlm@10
|
30 {:arglists '[[x]]}
|
rlm@10
|
31 (fn [x] (cond
|
rlm@10
|
32 (nil? x) nil ;; prevent NullPointerException on next line
|
rlm@10
|
33 (.isArray (class x)) ::array
|
rlm@10
|
34 :else (type x))))
|
rlm@10
|
35
|
rlm@10
|
36 ;; Primitive types can be printed with Clojure's pr function.
|
rlm@10
|
37 (derive java.lang.Boolean ::pr)
|
rlm@10
|
38 (derive java.lang.Byte ::pr)
|
rlm@10
|
39 (derive java.lang.Short ::pr)
|
rlm@10
|
40 (derive java.lang.Integer ::pr)
|
rlm@10
|
41 (derive java.lang.Long ::pr)
|
rlm@10
|
42 (derive java.lang.Float ::pr)
|
rlm@10
|
43 (derive java.lang.Double ::pr)
|
rlm@10
|
44
|
rlm@10
|
45 ;; Collection types can be printed as JSON objects or arrays.
|
rlm@10
|
46 (derive java.util.Map ::object)
|
rlm@10
|
47 (derive java.util.Collection ::array)
|
rlm@10
|
48
|
rlm@10
|
49 ;; Symbols and keywords are converted to strings.
|
rlm@10
|
50 (derive clojure.lang.Symbol ::symbol)
|
rlm@10
|
51 (derive clojure.lang.Keyword ::symbol)
|
rlm@10
|
52
|
rlm@10
|
53
|
rlm@10
|
54 (defmethod dispatch-json ::pr [x] (pr x))
|
rlm@10
|
55
|
rlm@10
|
56 (defmethod dispatch-json nil [x] (print "null"))
|
rlm@10
|
57
|
rlm@10
|
58 (defmethod dispatch-json ::symbol [x] (pr (name x)))
|
rlm@10
|
59
|
rlm@10
|
60 (defmethod dispatch-json ::array [s]
|
rlm@10
|
61 ((formatter-out "~<[~;~@{~w~^, ~:_~}~;]~:>") s))
|
rlm@10
|
62
|
rlm@10
|
63 (defmethod dispatch-json ::object [m]
|
rlm@10
|
64 ((formatter-out "~<{~;~@{~<~w:~_~w~:>~^, ~_~}~;}~:>")
|
rlm@10
|
65 (for [[k v] m] [(as-str k) v])))
|
rlm@10
|
66
|
rlm@10
|
67 (defmethod dispatch-json java.lang.CharSequence [s]
|
rlm@10
|
68 (print \")
|
rlm@10
|
69 (dotimes [i (count s)]
|
rlm@10
|
70 (let [cp (Character/codePointAt s i)]
|
rlm@10
|
71 (cond
|
rlm@10
|
72 ;; Handle printable JSON escapes before ASCII
|
rlm@10
|
73 (= cp 34) (print "\\\"")
|
rlm@10
|
74 (= cp 92) (print "\\\\")
|
rlm@10
|
75 ;; Print simple ASCII characters
|
rlm@10
|
76 (< 31 cp 127) (print (.charAt s i))
|
rlm@10
|
77 ;; Handle non-printable JSON escapes
|
rlm@10
|
78 (= cp 8) (print "\\b")
|
rlm@10
|
79 (= cp 12) (print "\\f")
|
rlm@10
|
80 (= cp 10) (print "\\n")
|
rlm@10
|
81 (= cp 13) (print "\\r")
|
rlm@10
|
82 (= cp 9) (print "\\t")
|
rlm@10
|
83 ;; Any other character is printed as Hexadecimal escape
|
rlm@10
|
84 :else (printf "\\u%04x" cp))))
|
rlm@10
|
85 (print \"))
|
rlm@10
|
86
|
rlm@10
|
87 (defn print-json
|
rlm@10
|
88 "Prints x as JSON. Nil becomes JSON null. Keywords become
|
rlm@10
|
89 strings, without the leading colon. Maps become JSON objects, all
|
rlm@10
|
90 other collection types become JSON arrays. Java arrays become JSON
|
rlm@10
|
91 arrays. Unicode characters in strings are escaped as \\uXXXX.
|
rlm@10
|
92 Numbers print as with pr."
|
rlm@10
|
93 [x]
|
rlm@10
|
94 (write x :dispatch dispatch-json))
|
rlm@10
|
95
|
rlm@10
|
96 (defn json-str
|
rlm@10
|
97 "Converts x to a JSON-formatted string."
|
rlm@10
|
98 [x]
|
rlm@10
|
99 (with-out-str (print-json x)))
|
rlm@10
|
100
|
rlm@10
|
101
|
rlm@10
|
102
|
rlm@10
|
103 ;;; TESTS
|
rlm@10
|
104
|
rlm@10
|
105 ;; Run these tests with
|
rlm@10
|
106 ;; (clojure.test/run-tests 'clojure.contrib.print-json)
|
rlm@10
|
107
|
rlm@10
|
108 ;; Bind clojure.test/*load-tests* to false to omit these
|
rlm@10
|
109 ;; tests from production code.
|
rlm@10
|
110
|
rlm@10
|
111 (deftest- can-print-json-strings
|
rlm@10
|
112 (is (= "\"Hello, World!\"" (json-str "Hello, World!")))
|
rlm@10
|
113 (is (= "\"\\\"Embedded\\\" Quotes\"" (json-str "\"Embedded\" Quotes"))))
|
rlm@10
|
114
|
rlm@10
|
115 (deftest- can-print-unicode
|
rlm@10
|
116 (is (= "\"\\u1234\\u4567\"" (json-str "\u1234\u4567"))))
|
rlm@10
|
117
|
rlm@10
|
118 (deftest- can-print-json-null
|
rlm@10
|
119 (is (= "null" (json-str nil))))
|
rlm@10
|
120
|
rlm@10
|
121 (deftest- can-print-json-arrays
|
rlm@10
|
122 (is (= "[1, 2, 3]" (json-str [1 2 3])))
|
rlm@10
|
123 (is (= "[1, 2, 3]" (json-str (list 1 2 3))))
|
rlm@10
|
124 (is (= "[1, 2, 3]" (json-str (sorted-set 1 2 3))))
|
rlm@10
|
125 (is (= "[1, 2, 3]" (json-str (seq [1 2 3])))))
|
rlm@10
|
126
|
rlm@10
|
127 (deftest- can-print-java-arrays
|
rlm@10
|
128 (is (= "[1, 2, 3]" (json-str (into-array [1 2 3])))))
|
rlm@10
|
129
|
rlm@10
|
130 (deftest- can-print-empty-arrays
|
rlm@10
|
131 (is (= "[]" (json-str [])))
|
rlm@10
|
132 (is (= "[]" (json-str (list))))
|
rlm@10
|
133 (is (= "[]" (json-str #{}))))
|
rlm@10
|
134
|
rlm@10
|
135 (deftest- can-print-json-objects
|
rlm@10
|
136 (is (= "{\"a\":1, \"b\":2}" (json-str (sorted-map :a 1 :b 2)))))
|
rlm@10
|
137
|
rlm@10
|
138 (deftest- object-keys-must-be-strings
|
rlm@10
|
139 (is (= "{\"1\":1, \"2\":2}" (json-str (sorted-map 1 1 2 2)))))
|
rlm@10
|
140
|
rlm@10
|
141 (deftest- can-print-empty-objects
|
rlm@10
|
142 (is (= "{}" (json-str {}))))
|