diff options
author | Stuart Sierra <mail@stuartsierra.com> | 2009-05-09 22:05:47 +0000 |
---|---|---|
committer | Stuart Sierra <mail@stuartsierra.com> | 2009-05-09 22:05:47 +0000 |
commit | 02831af62ead8f8a1ae5163956f64af8bac41488 (patch) | |
tree | a6c55c324c1b3ef5ff566a24af922f688f8f7154 | |
parent | 1aca2922b1b3404b4d2aa5b32ca95ff9e184fe60 (diff) |
json/write.clj: Handle more collection types and unicode in strings.
Escapes all non-ASCII characters as \uXXXX in Strings.
Handles more collection types, including all java.util.Map,
java.util.Collection, and Java arrays.
-rw-r--r-- | src/clojure/contrib/json/write.clj | 103 |
1 files changed, 78 insertions, 25 deletions
diff --git a/src/clojure/contrib/json/write.clj b/src/clojure/contrib/json/write.clj index ee6d59a0..c633c826 100644 --- a/src/clojure/contrib/json/write.clj +++ b/src/clojure/contrib/json/write.clj @@ -12,21 +12,31 @@ ;; remove this notice, or any other, from this software. +(ns + #^{:author "Stuart Sierra", + :doc "JavaScript Object Notation (JSON) generator. -;; For more information on JSON, see http://www.json.org/ +This is a low-level implementation of JSON. It only supports basic +types, arrays, Collections, and Maps. -;; This is a very simple implementation of JSON. It does NOT -;; guarantee round-trip equality, i.e. that -;; (= x (read-json-string (json-str x)) +You can extend the library to handle new types by adding methods to +the print-json multimethod. -;; Map keys will be converted to strings. All keywords will become -;; strings. Most other types are printed as with "pr". +This library does NOT attempt to preserve round-trip equality between +JSON and Clojure data types. That is, if you write a JSON string with +this library, then read it back with clojure.contrib.json.read, you +won't necessarily get the exact same data structure. +If you want round-trip equality and/or indented output, try Dan +Larkin's clojure-json library at +http://github.com/danlarkin/clojure-json -(ns - #^{:author "Stuart Sierra", - :doc "JavaScript Object Notation (JSON) generator", - :see-also [["http://www.json.org", "JSON Home Page"]]} +This implementation attempts to follow the description of JSON at +<http://json.org/>. Maps become JSON objects, all other collections +become JSON arrays. JSON object keys are always converted to strings. +Within strings, all non-ASCII characters are hexadecimal escaped. +", + :see-also [["http://json.org/", "JSON Home Page"]]} clojure.contrib.json.write (:require [clojure.contrib.java-utils :as j]) (:use [clojure.contrib.test-is :only (deftest- is)])) @@ -37,31 +47,47 @@ JSON objects, all other collection types become JSON arrays. Strings and numbers print as with pr." :arglists '([x])} - print-json (fn [x] - (cond (nil? x) nil - (map? x) :object - (coll? x) :array - (keyword? x) :symbol - (symbol? x) :symbol - :else :default))) + print-json (fn [x] (cond + (nil? x) nil ;; prevent NullPointerException on next line + (.isArray (class x)) ::array + :else (type x)))) + + +;; Primitive types can be printed with Clojure's pr function. +(derive java.lang.Boolean ::pr) +(derive java.lang.Byte ::pr) +(derive java.lang.Short ::pr) +(derive java.lang.Integer ::pr) +(derive java.lang.Long ::pr) +(derive java.lang.Float ::pr) +(derive java.lang.Double ::pr) + +;; Collection types can be printed as JSON objects or arrays. +(derive java.util.Map ::object) +(derive java.util.Collection ::array) + +;; Symbols and keywords are converted to strings. +(derive clojure.lang.Symbol ::symbol) +(derive clojure.lang.Keyword ::symbol) -(defmethod print-json :default [x] (pr x)) + +(defmethod print-json ::pr [x] (pr x)) (defmethod print-json nil [x] (print "null")) -(defmethod print-json :symbol [x] (pr (name x))) +(defmethod print-json ::symbol [x] (pr (name x))) -(defmethod print-json :array [s] +(defmethod print-json ::array [s] (print "[") (loop [x s] - (when (first x) - (print-json (first x)) - (when (next x) + (when-let [fst (first x)] + (print-json fst) + (when-let [nxt (next x)] (print ",") - (recur (next x))))) + (recur nxt)))) (print "]")) -(defmethod print-json :object [m] +(defmethod print-json ::object [m] (print "{") (loop [x m] (when (first x) @@ -74,6 +100,27 @@ (recur (next x))))) (print "}")) +(defmethod print-json java.lang.CharSequence [s] + (print \") + (dotimes [i (count s)] + (let [cp (Character/codePointAt s i)] + (cond + ;; Handle printable JSON escapes before ASCII + (= cp 34) (print "\\\"") + (= cp 92) (print "\\\\") + (= cp 47) (print "\\/") + ;; Print simple ASCII characters + (< 31 cp 127) (print (.charAt s i)) + ;; Handle non-printable JSON escapes + (= cp 8) (print "\\b") + (= cp 12) (print "\\f") + (= cp 10) (print "\\n") + (= cp 13) (print "\\r") + (= cp 9) (print "\\t") + ;; Any other character is printed as Hexadecimal escape + :else (printf "\\u%04x" cp)))) + (print \")) + (defn json-str "Converts Clojure data structures to a JSON-formatted string." [x] @@ -93,6 +140,9 @@ (is (= "\"Hello, World!\"" (json-str "Hello, World!"))) (is (= "\"\\\"Embedded\\\" Quotes\"" (json-str "\"Embedded\" Quotes")))) +(deftest- can-print-unicode + (is (= "\"\\u1234\\u4567\"" (json-str "\u1234\u4567")))) + (deftest- can-print-json-null (is (= "null" (json-str nil)))) @@ -102,6 +152,9 @@ (is (= "[1,2,3]" (json-str (sorted-set 1 2 3)))) (is (= "[1,2,3]" (json-str (seq [1 2 3]))))) +(deftest- can-print-java-arrays + (is (= "[1,2,3]" (json-str (into-array [1 2 3]))))) + (deftest- can-print-empty-arrays (is (= "[]" (json-str []))) (is (= "[]" (json-str (list)))) |