├── test ├── multi.json ├── all_month.geojson.gz ├── pass1.json └── cheshire │ └── test │ ├── generative.clj │ ├── custom.clj │ └── core.clj ├── .gitignore ├── .travis.yml ├── LICENSE ├── src └── cheshire │ ├── experimental.clj │ ├── parse.clj │ ├── factory.clj │ ├── generate_seq.clj │ ├── generate.clj │ ├── custom.clj │ └── core.clj ├── ChangeLog.md ├── project.clj ├── benchmarks └── cheshire │ └── test │ └── benchmark.clj └── README.md /test/multi.json: -------------------------------------------------------------------------------- 1 | {"one":1,"foo":"bar"} 2 | {"two":2,"foo":"bar"} 3 | -------------------------------------------------------------------------------- /test/all_month.geojson.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Leonidas-from-XIV/cheshire/master/test/all_month.geojson.gz -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | lib/* 2 | multi-lib/* 3 | coref 4 | classes/* 5 | .lein-failures 6 | .lein-deps-sum 7 | target/* 8 | pom.xml* 9 | doc* 10 | .lein* 11 | .nrepl* 12 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: clojure 2 | lein: lein2 3 | script: lein2 all test :all 4 | branches: 5 | only: 6 | - master 7 | jdk: 8 | - oraclejdk8 9 | - openjdk7 10 | - oraclejdk7 11 | - openjdk6 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011 Matthew Lee Hinman 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 17 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 18 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 19 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 20 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /src/cheshire/experimental.clj: -------------------------------------------------------------------------------- 1 | (ns cheshire.experimental 2 | "Experimental JSON encoding/decoding tools." 3 | (:require [cheshire.core :refer :all] 4 | [clojure.java.io :refer :all] 5 | [tigris.core :refer [str-escaping-input-stream]]) 6 | (:import (java.io ByteArrayInputStream SequenceInputStream))) 7 | 8 | (defn encode-large-field-in-map 9 | "EXPERIMENTAL - SUBJECT TO CHANGE. 10 | 11 | Given a clojure object, a field name and a stream for a the string value of 12 | the field, return a stream that, when read, JSON encodes in a streamy way. 13 | 14 | An optional opt-map can be specified to pass enocding options to the map 15 | encoding, supports the same options as generate-string. 16 | 17 | Note that the input stream is not closed. You are responsible for closing it 18 | by calling .close() on the stream object returned from this method." 19 | ([obj field stream] 20 | (encode-large-field-in-map obj field stream nil)) 21 | ([obj field stream & [opt-map]] 22 | (let [otherstr (encode (dissoc obj field) opt-map) 23 | truncstr (str (subs otherstr 0 (dec (count otherstr)))) 24 | stream (str-escaping-input-stream stream) 25 | pre-stream (ByteArrayInputStream. 26 | (.getBytes (str truncstr ",\"" (name field) "\":\""))) 27 | post-stream (ByteArrayInputStream. (.getBytes "\"}"))] 28 | (reduce #(SequenceInputStream. %1 %2) [pre-stream stream post-stream])))) 29 | -------------------------------------------------------------------------------- /test/pass1.json: -------------------------------------------------------------------------------- 1 | [ 2 | "JSON Test Pattern pass1", 3 | {"object with 1 member":["array with 1 element"]}, 4 | {}, 5 | [], 6 | -42, 7 | true, 8 | false, 9 | null, 10 | { 11 | "integer": 1234567890, 12 | "real": -9876.543210, 13 | "e": 0.123456789e-12, 14 | "E": 1.234567890E+34, 15 | "": 23456789012E66, 16 | "zero": 0, 17 | "one": 1, 18 | "space": " ", 19 | "quote": "\"", 20 | "backslash": "\\", 21 | "controls": "\b\f\n\r\t", 22 | "slash": "/ & \/", 23 | "alpha": "abcdefghijklmnopqrstuvwyz", 24 | "ALPHA": "ABCDEFGHIJKLMNOPQRSTUVWYZ", 25 | "digit": "0123456789", 26 | "0123456789": "digit", 27 | "special": "`1~!@#$%^&*()_+-={':[,]}|;.?", 28 | "hex": "\u0123\u4567\u89AB\uCDEF\uabcd\uef4A", 29 | "true": true, 30 | "false": false, 31 | "null": null, 32 | "array":[ ], 33 | "object":{ }, 34 | "address": "50 St. James Street", 35 | "url": "http://www.JSON.org/", 36 | "comment": "// /* */": " ", 38 | " s p a c e d " :[1,2 , 3 39 | 40 | , 41 | 42 | 4 , 5 , 6 ,7 ],"compact":[1,2,3,4,5,6,7], 43 | "jsontext": "{\"object with 1 member\":[\"array with 1 element\"]}", 44 | "quotes": "" \u0022 %22 0x22 034 "", 45 | "\/\\\"\uCAFE\uBABE\uAB98\uFCDE\ubcda\uef4A\b\f\n\r\t`1~!@#$%^&*()_+-=[]{}|;:',./<>?" 46 | : "A key can be any string" 47 | }, 48 | 0.5 ,98.6 49 | , 50 | 99.44 51 | , 52 | 53 | 1066, 54 | 1e1, 55 | 0.1e1, 56 | 1e-1, 57 | 1e00,2e+00,2e-00 58 | ,"rosebud"] -------------------------------------------------------------------------------- /test/cheshire/test/generative.clj: -------------------------------------------------------------------------------- 1 | (ns cheshire.test.generative 2 | (:use [cheshire.core] 3 | [clojure.test.generative] 4 | [clojure.test :only [deftest is]])) 5 | 6 | ;; determines whether generative stuff is printed to stdout 7 | (def verbose true) 8 | 9 | (defn encode-equality [x] 10 | [x (decode (encode x))]) 11 | 12 | (defn encode-equality-keys [x] 13 | [x (decode (encode x) true)]) 14 | 15 | (defspec number-json-encoding 16 | (fn [a b c] [[a b c] (decode (encode [a b c]))]) 17 | [^int a ^long b ^double c] 18 | (is (= (first %) (last %)))) 19 | 20 | (defspec bool-json-encoding 21 | encode-equality 22 | [^boolean a] 23 | (is (= (first %) (last %)))) 24 | 25 | (defspec symbol-json-encoding 26 | encode-equality 27 | [^symbol a] 28 | (is (= (str (first %)) (last %)))) 29 | 30 | (defspec keyword-json-encoding 31 | encode-equality 32 | [^keyword a] 33 | (is (= (name (first %)) (last %)))) 34 | 35 | (defspec map-json-encoding 36 | encode-equality 37 | [^{:tag (hash-map string (hash-map string (vec string 10) 10) 10)} a] 38 | (is (= (first %) (last %)))) 39 | 40 | (defspec map-keyword-json-encoding 41 | encode-equality-keys 42 | [^{:tag (hash-map keyword (hash-map keyword (list int 10) 10) 10)} a] 43 | (is (= (first %) (last %)))) 44 | 45 | (deftest ^{:generative true} t-generative 46 | ;; I want the seeds to change every time, set the number higher if 47 | ;; you have more than 16 CPU cores 48 | (let [seeds (take 16 (repeatedly #(rand-int 1024)))] 49 | (when-not verbose 50 | (reset! report-fn identity)) 51 | (println "Seeds:" seeds) 52 | (binding [*msec* 25000 53 | *seeds* seeds 54 | *verbose* false] 55 | (doall (map deref (test-namespaces 'cheshire.test.generative)))))) 56 | -------------------------------------------------------------------------------- /ChangeLog.md: -------------------------------------------------------------------------------- 1 | ## Changes between Cheshire 5.3.2 and 5.3.1 2 | 3 | * Add default encoder for java.lang.Character 4 | * Add sequention write support 5 | 6 | ## Changes between Cheshire 5.3.1 and 5.3.0 7 | 8 | * Fix string parsing for 1 and 2 arity methods 9 | * Bump Jackson to 2.3.1 10 | 11 | ## Changes between Cheshire 5.3.0 and 5.2.0 12 | 13 | * Dependencies have been bumped 14 | * Parse streams strictly by default to avoid scoping issues 15 | 16 | ## Changes between Cheshire 5.2.0 and 5.1.2 17 | 18 | * Bump tigris to 0.1.1 to use PushbackReader 19 | * Lazily decode top-level arrays (thanks ztellman) 20 | 21 | ## Changes between Cheshire 5.1.2 and 5.1.1 22 | 23 | * Add experimental namespace 24 | * Bump Jackson deps to 2.2.1 25 | 26 | ## Changes between Cheshire 5.1.1 and 5.1.0 27 | 28 | * Remove all reflection (thanks amalloy) 29 | * Fixed custom encoder helpers (thanks lynaghk) 30 | 31 | ## Changes between Cheshire 5.1.0 and 5.0.2 32 | 33 | * Allow custom keyword function for encoding (thanks goodwink) 34 | 35 | ## Changes between Cheshire 5.0.2 and 5.0.1 36 | 37 | * Bump Jackson dependency from 2.1.1 to 2.1.3 38 | 39 | * Add more type hinting (thanks to ztellman) 40 | 41 | ## Changes between Cheshire 5.0.0 and 5.0.1 42 | 43 | * Protocol custom encoders now take precedence over regular map 44 | encoders. 45 | 46 | * Benchmarking is now a separate lein command, not a test selector. 47 | 48 | ## Changes between Cheshire 5.0.0 and 4.0.x 49 | 50 | ### Custom Encoders Changes 51 | 52 | Custom encoder functions were moved to the `cheshire.generate` namespace: 53 | 54 | * `cheshire.custom/add-encoder` is now `cheshire.generate/add-encoder` 55 | * `cheshire.custom/remove-encoder` is now `cheshire.generate/remove-encoder` 56 | 57 | In addition, `cheshire.custom/encode` and `cheshire.custom/decode` are no longer 58 | necessary. Use `cheshire.core/encode` and `cheshire.core/decode` instead and 59 | those functions will pick up custom encoders while still preserving the same 60 | level of efficiency. 61 | 62 | GH issue: [#32](https://github.com/dakrone/cheshire/issues/32). 63 | -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject cheshire "5.5.1-SNAPSHOT" 2 | :description "JSON and JSON SMILE encoding, fast." 3 | :url "https://github.com/dakrone/cheshire" 4 | :license {:name "The MIT License" 5 | :url "http://opensource.org/licenses/MIT" 6 | :distribution :repo} 7 | :global-vars {*warn-on-reflection* false} 8 | :dependencies [[com.fasterxml.jackson.core/jackson-core "2.5.3"] 9 | [com.fasterxml.jackson.dataformat/jackson-dataformat-smile "2.5.3"] 10 | [com.fasterxml.jackson.dataformat/jackson-dataformat-cbor "2.5.3"] 11 | [tigris "0.1.1"]] 12 | :profiles {:dev {:dependencies [[org.clojure/clojure "1.6.0"] 13 | [org.clojure/test.generative "0.1.4"]]} 14 | :1.2 {:dependencies [[org.clojure/clojure "1.2.1"]]} 15 | :1.3 {:dependencies [[org.clojure/clojure "1.3.0"]]} 16 | :1.4 {:dependencies [[org.clojure/clojure "1.4.0"]]} 17 | :1.5 {:dependencies [[org.clojure/clojure "1.5.1"]]} 18 | :1.7 {:dependencies [[org.clojure/clojure "1.7.0-RC1"]]} 19 | :benchmark {:test-paths ["benchmarks"] 20 | :jvm-opts ^:replace ["-Xms1g" "-Xmx1g" "-server"] 21 | :dependencies [[criterium "0.4.3"] 22 | [org.clojure/data.json "0.2.6"] 23 | [clj-json "0.5.3"]]}} 24 | :aliases {"all" ["with-profile" "dev,1.3:dev,1.4:dev,1.5:dev,1.7:dev"] 25 | "benchmark" ["with-profile" "dev,benchmark" "test"] 26 | "core-bench" ["with-profile" "dev,benchmark" "test" ":only" 27 | "cheshire.test.benchmark/t-bench-core"]} 28 | :test-selectors {:default #(and (not (:benchmark %)) 29 | (not (:generative %))) 30 | :generative :generative 31 | :all (constantly true)} 32 | :plugins [[codox "0.6.3"]] 33 | :jvm-opts ["-Xmx512M" 34 | ;; "-XX:+PrintCompilation" 35 | ;; "-XX:+UnlockDiagnosticVMOptions" 36 | ;; "-XX:+PrintInlining" 37 | ]) 38 | -------------------------------------------------------------------------------- /src/cheshire/parse.clj: -------------------------------------------------------------------------------- 1 | (ns cheshire.parse 2 | (:import (com.fasterxml.jackson.core JsonParser JsonToken))) 3 | 4 | (declare parse*) 5 | 6 | (def ^{:doc "Flag to determine whether float values should be returned as 7 | BigDecimals to retain precision. Defaults to false." 8 | :dynamic true} 9 | *use-bigdecimals?* false) 10 | 11 | (defmacro ^:private tag 12 | ([obj] 13 | `(vary-meta ~obj assoc :tag `JsonParser))) 14 | 15 | (definline parse-object [^JsonParser jp key-fn bd? array-coerce-fn] 16 | (let [jp (tag jp)] 17 | `(do 18 | (.nextToken ~jp) 19 | (loop [mmap# (transient {})] 20 | (if-not (identical? (.getCurrentToken ~jp) 21 | JsonToken/END_OBJECT) 22 | (let [key-str# (.getText ~jp) 23 | _# (.nextToken ~jp) 24 | key# (~key-fn key-str#) 25 | mmap# (assoc! mmap# key# 26 | (parse* ~jp ~key-fn ~bd? ~array-coerce-fn))] 27 | (.nextToken ~jp) 28 | (recur mmap#)) 29 | (persistent! mmap#)))))) 30 | 31 | (definline parse-array [^JsonParser jp key-fn bd? array-coerce-fn] 32 | (let [jp (tag jp)] 33 | `(let [array-field-name# (.getCurrentName ~jp)] 34 | (.nextToken ~jp) 35 | (loop [coll# (transient (if ~array-coerce-fn 36 | (~array-coerce-fn array-field-name#) 37 | []))] 38 | (if-not (identical? (.getCurrentToken ~jp) 39 | JsonToken/END_ARRAY) 40 | (let [coll# (conj! coll# 41 | (parse* ~jp ~key-fn ~bd? ~array-coerce-fn))] 42 | (.nextToken ~jp) 43 | (recur coll#)) 44 | (persistent! coll#)))))) 45 | 46 | (defn lazily-parse-array [^JsonParser jp key-fn bd? array-coerce-fn] 47 | (lazy-seq 48 | (loop [chunk-idx 0, buf (chunk-buffer 32)] 49 | (if (identical? (.getCurrentToken jp) JsonToken/END_ARRAY) 50 | (chunk-cons (chunk buf) nil) 51 | (do 52 | (chunk-append buf (parse* jp key-fn bd? array-coerce-fn)) 53 | (.nextToken jp) 54 | (let [chunk-idx* (unchecked-inc chunk-idx)] 55 | (if (< chunk-idx* 32) 56 | (recur chunk-idx* buf) 57 | (chunk-cons 58 | (chunk buf) 59 | (lazily-parse-array jp key-fn bd? array-coerce-fn))))))))) 60 | 61 | (defn parse* [^JsonParser jp key-fn bd? array-coerce-fn] 62 | (condp identical? (.getCurrentToken jp) 63 | JsonToken/START_OBJECT (parse-object jp key-fn bd? array-coerce-fn) 64 | JsonToken/START_ARRAY (parse-array jp key-fn bd? array-coerce-fn) 65 | JsonToken/VALUE_STRING (.getText jp) 66 | JsonToken/VALUE_NUMBER_INT (.getNumberValue jp) 67 | JsonToken/VALUE_NUMBER_FLOAT (if bd? 68 | (.getDecimalValue jp) 69 | (.getNumberValue jp)) 70 | JsonToken/VALUE_TRUE true 71 | JsonToken/VALUE_FALSE false 72 | JsonToken/VALUE_NULL nil 73 | (throw 74 | (Exception. 75 | (str "Cannot parse " (pr-str (.getCurrentToken jp))))))) 76 | 77 | (defn parse-strict [^JsonParser jp key-fn eof array-coerce-fn] 78 | (let [key-fn (or (if (identical? key-fn true) keyword key-fn) identity)] 79 | (.nextToken jp) 80 | (condp identical? (.getCurrentToken jp) 81 | nil 82 | eof 83 | (parse* jp key-fn *use-bigdecimals?* array-coerce-fn)))) 84 | 85 | (defn parse [^JsonParser jp key-fn eof array-coerce-fn] 86 | (let [key-fn (or (if (identical? key-fn true) keyword key-fn) identity)] 87 | (.nextToken jp) 88 | (condp identical? (.getCurrentToken jp) 89 | nil 90 | eof 91 | 92 | JsonToken/START_ARRAY 93 | (do 94 | (.nextToken jp) 95 | (lazily-parse-array jp key-fn *use-bigdecimals?* array-coerce-fn)) 96 | 97 | (parse* jp key-fn *use-bigdecimals?* array-coerce-fn)))) 98 | -------------------------------------------------------------------------------- /benchmarks/cheshire/test/benchmark.clj: -------------------------------------------------------------------------------- 1 | (ns cheshire.test.benchmark 2 | (:use [clojure.test]) 3 | (:require [cheshire.core :as core] 4 | [cheshire.custom :as old] 5 | [cheshire.generate :as custom] 6 | [clojure.data.json :as cj] 7 | [clojure.java.io :refer [file input-stream resource]] 8 | [clj-json.core :as clj-json] 9 | [criterium.core :as bench]) 10 | (:import (java.util.zip GZIPInputStream))) 11 | 12 | ;; These tests just print out results, nothing else, they also 13 | ;; currently don't work with clojure 1.2 (but the regular tests do) 14 | 15 | (def test-obj {"int" 3 16 | "boolean" true 17 | "LongObj" (Long/parseLong "2147483647") 18 | "double" 1.23 19 | "nil" nil 20 | "string" "string" 21 | "vec" [1 2 3] 22 | "map" {"a" "b"} 23 | "list" '("a" "b") 24 | "set" #{"a" "b"} 25 | "keyword" :foo}) 26 | 27 | (def big-test-obj 28 | (-> "test/all_month.geojson.gz" 29 | file 30 | input-stream 31 | (GZIPInputStream. ) 32 | slurp 33 | core/decode)) 34 | 35 | (deftest t-bench-clj-json 36 | (println "-------- clj-json Benchmarks --------") 37 | (bench/with-progress-reporting 38 | (bench/quick-bench (clj-json/parse-string 39 | (clj-json/generate-string test-obj)) :verbose)) 40 | (println "-------------------------------------")) 41 | 42 | (deftest t-bench-clojure-json 43 | (println "-------- Data.json Benchmarks -------") 44 | (bench/with-progress-reporting 45 | (bench/quick-bench (cj/read-str (cj/write-str test-obj)) :verbose)) 46 | (println "-------------------------------------")) 47 | 48 | (deftest t-bench-core 49 | (println "---------- Core Benchmarks ----------") 50 | (bench/with-progress-reporting 51 | (bench/bench (core/decode (core/encode test-obj)) :verbose)) 52 | (println "-------------------------------------")) 53 | 54 | (deftest t-bench-custom 55 | (println "--------- Custom Benchmarks ---------") 56 | (custom/add-encoder java.net.URL custom/encode-str) 57 | (is (= "\"http://foo.com\"" (core/encode (java.net.URL. "http://foo.com")))) 58 | (let [custom-obj (assoc test-obj "url" (java.net.URL. "http://foo.com"))] 59 | (println "[+] Custom, all custom fields:") 60 | (bench/with-progress-reporting 61 | (bench/quick-bench (core/decode (core/encode custom-obj)) :verbose))) 62 | (println "-------------------------------------")) 63 | 64 | (deftest t-bench-custom-kw-coercion 65 | (println "---- Custom keyword-fn Benchmarks ---") 66 | (let [t (core/encode test-obj)] 67 | (println "[+] (fn [k] (keyword k)) decode") 68 | (bench/with-progress-reporting 69 | (bench/quick-bench (core/decode t (fn [k] (keyword k))))) 70 | (println "[+] basic 'true' keyword-fn decode") 71 | (bench/with-progress-reporting 72 | (bench/quick-bench (core/decode t true))) 73 | (println "[+] no keyword-fn decode") 74 | (bench/with-progress-reporting 75 | (bench/quick-bench (core/decode t))) 76 | (println "[+] (fn [k] (name k)) encode") 77 | (bench/with-progress-reporting 78 | (bench/quick-bench (core/encode test-obj {:key-fn (fn [k] (name k))}))) 79 | (println "[+] no keyword-fn encode") 80 | (bench/with-progress-reporting 81 | (bench/quick-bench (core/encode test-obj)))) 82 | (println "-------------------------------------")) 83 | 84 | (deftest t-large-array 85 | (println "-------- Large array parsing --------") 86 | (let [test-array-json (core/encode (range 1024))] 87 | (bench/with-progress-reporting 88 | (bench/bench (pr-str (core/decode test-array-json))))) 89 | (println "-------------------------------------")) 90 | 91 | (deftest t-large-geojson-object 92 | (println "------- Large GeoJSON parsing -------") 93 | (println "[+] large geojson custom encode") 94 | (bench/with-progress-reporting 95 | (bench/quick-bench (old/encode big-test-obj))) 96 | (println "[+] large geojson encode") 97 | (bench/with-progress-reporting 98 | (bench/quick-bench (core/encode big-test-obj))) 99 | (println "[+] large geojson decode") 100 | (let [s (core/encode big-test-obj)] 101 | (bench/with-progress-reporting 102 | (bench/quick-bench (core/decode s)))) 103 | (println "-------------------------------------")) 104 | -------------------------------------------------------------------------------- /src/cheshire/factory.clj: -------------------------------------------------------------------------------- 1 | (ns cheshire.factory 2 | "Factories used for JSON/SMILE generation, used by both the core and 3 | custom generators." 4 | (:import (com.fasterxml.jackson.dataformat.smile SmileFactory) 5 | (com.fasterxml.jackson.dataformat.cbor CBORFactory) 6 | (com.fasterxml.jackson.core JsonFactory JsonFactory$Feature 7 | JsonParser$Feature))) 8 | 9 | ;; default date format used to JSON-encode Date objects 10 | (def default-date-format "yyyy-MM-dd'T'HH:mm:ss'Z'") 11 | 12 | (defonce default-factory-options 13 | {:auto-close-source false 14 | :allow-comments false 15 | :allow-unquoted-field-names false 16 | :allow-single-quotes false 17 | :allow-unquoted-control-chars true 18 | :allow-backslash-escaping false 19 | :allow-numeric-leading-zeros false 20 | :allow-non-numeric-numbers false 21 | :intern-field-names false 22 | :canonicalize-field-names false}) 23 | 24 | ;; Factory objects that are needed to do the encoding and decoding 25 | (defn ^JsonFactory make-json-factory 26 | [opts] 27 | (let [opts (merge default-factory-options opts)] 28 | (doto (JsonFactory.) 29 | (.configure JsonParser$Feature/AUTO_CLOSE_SOURCE 30 | (boolean (:auto-close-source opts))) 31 | (.configure JsonParser$Feature/ALLOW_COMMENTS 32 | (boolean (:allow-comments opts))) 33 | (.configure JsonParser$Feature/ALLOW_UNQUOTED_FIELD_NAMES 34 | (boolean (:allow-unquoted-field-names opts))) 35 | (.configure JsonParser$Feature/ALLOW_SINGLE_QUOTES 36 | (boolean (:allow-single-quotes opts))) 37 | (.configure JsonParser$Feature/ALLOW_UNQUOTED_CONTROL_CHARS 38 | (boolean (:allow-unquoted-control-chars opts))) 39 | (.configure JsonParser$Feature/ALLOW_BACKSLASH_ESCAPING_ANY_CHARACTER 40 | (boolean (:allow-backslash-escaping opts))) 41 | (.configure JsonParser$Feature/ALLOW_NUMERIC_LEADING_ZEROS 42 | (boolean (:allow-numeric-leading-zeros opts))) 43 | (.configure JsonParser$Feature/ALLOW_NON_NUMERIC_NUMBERS 44 | (boolean (:allow-non-numeric-numbers opts))) 45 | (.configure JsonFactory$Feature/INTERN_FIELD_NAMES 46 | (boolean (:intern-field-names opts))) 47 | (.configure JsonFactory$Feature/CANONICALIZE_FIELD_NAMES 48 | (boolean (:canonicalize-field-names opts)))))) 49 | 50 | (defn ^SmileFactory make-smile-factory 51 | [opts] 52 | (let [opts (merge default-factory-options opts)] 53 | (doto (SmileFactory.) 54 | (.configure JsonParser$Feature/AUTO_CLOSE_SOURCE 55 | (boolean (:auto-close-source opts))) 56 | (.configure JsonParser$Feature/ALLOW_COMMENTS 57 | (boolean (:allow-comments opts))) 58 | (.configure JsonParser$Feature/ALLOW_UNQUOTED_FIELD_NAMES 59 | (boolean (:allow-unquoted-field-names opts))) 60 | (.configure JsonParser$Feature/ALLOW_SINGLE_QUOTES 61 | (boolean (:allow-single-quotes opts))) 62 | (.configure JsonParser$Feature/ALLOW_UNQUOTED_CONTROL_CHARS 63 | (boolean (:allow-unquoted-control-chars opts))) 64 | (.configure JsonParser$Feature/ALLOW_BACKSLASH_ESCAPING_ANY_CHARACTER 65 | (boolean (:allow-backslash-escaping opts))) 66 | (.configure JsonParser$Feature/ALLOW_NUMERIC_LEADING_ZEROS 67 | (boolean (:allow-numeric-leading-zeros opts))) 68 | (.configure JsonParser$Feature/ALLOW_NON_NUMERIC_NUMBERS 69 | (boolean (:allow-non-numeric-numbers opts))) 70 | (.configure JsonFactory$Feature/INTERN_FIELD_NAMES 71 | (boolean (:intern-field-names opts))) 72 | (.configure JsonFactory$Feature/CANONICALIZE_FIELD_NAMES 73 | (boolean (:canonicalize-field-names opts)))))) 74 | 75 | (defn ^CBORFactory make-cbor-factory 76 | [opts] 77 | (let [opts (merge default-factory-options opts)] 78 | (doto (CBORFactory.) 79 | (.configure JsonParser$Feature/AUTO_CLOSE_SOURCE 80 | (boolean (:auto-close-source opts))) 81 | (.configure JsonParser$Feature/ALLOW_COMMENTS 82 | (boolean (:allow-comments opts))) 83 | (.configure JsonParser$Feature/ALLOW_UNQUOTED_FIELD_NAMES 84 | (boolean (:allow-unquoted-field-names opts))) 85 | (.configure JsonParser$Feature/ALLOW_SINGLE_QUOTES 86 | (boolean (:allow-single-quotes opts))) 87 | (.configure JsonParser$Feature/ALLOW_UNQUOTED_CONTROL_CHARS 88 | (boolean (:allow-unquoted-control-chars opts))) 89 | (.configure JsonParser$Feature/ALLOW_BACKSLASH_ESCAPING_ANY_CHARACTER 90 | (boolean (:allow-backslash-escaping opts))) 91 | (.configure JsonParser$Feature/ALLOW_NUMERIC_LEADING_ZEROS 92 | (boolean (:allow-numeric-leading-zeros opts))) 93 | (.configure JsonParser$Feature/ALLOW_NON_NUMERIC_NUMBERS 94 | (boolean (:allow-non-numeric-numbers opts))) 95 | (.configure JsonFactory$Feature/INTERN_FIELD_NAMES 96 | (boolean (:intern-field-names opts))) 97 | (.configure JsonFactory$Feature/CANONICALIZE_FIELD_NAMES 98 | (boolean (:canonicalize-field-names opts)))))) 99 | 100 | (defonce ^JsonFactory json-factory (make-json-factory default-factory-options)) 101 | (defonce ^SmileFactory smile-factory 102 | (make-smile-factory default-factory-options)) 103 | (defonce ^CBORFactory cbor-factory (make-cbor-factory default-factory-options)) 104 | 105 | ;; dynamically rebindable json factory, if desired 106 | (def ^{:dynamic true :tag JsonFactory} *json-factory* nil) 107 | ;; dynamically rebindable smile factory, if desired 108 | (def ^{:dynamic true :tag SmileFactory} *smile-factory* nil) 109 | ;; dynamically rebindable cbor factory, if desired 110 | (def ^{:dynamic true :tag CBORFactory} *cbor-factory* nil) 111 | -------------------------------------------------------------------------------- /src/cheshire/generate_seq.clj: -------------------------------------------------------------------------------- 1 | (ns cheshire.generate-seq 2 | "Namespace used to generate JSON from Clojure data structures in a 3 | sequential way." 4 | (:use [cheshire.generate :only [tag JSONable to-json i? 5 | number-dispatch write-string 6 | fail]]) 7 | (:import (com.fasterxml.jackson.core JsonGenerator JsonGenerationException) 8 | (java.util Date Map List Set SimpleTimeZone UUID) 9 | (java.sql Timestamp) 10 | (java.text SimpleDateFormat) 11 | (java.math BigInteger) 12 | (clojure.lang IPersistentCollection Keyword Ratio Symbol))) 13 | 14 | (definline write-start-object [^JsonGenerator jg wholeness] 15 | `(if (contains? #{:all :start :start-inner} ~wholeness) 16 | (.writeStartObject ~jg))) 17 | 18 | (definline write-end-object [^JsonGenerator jg wholeness] 19 | `(if (contains? #{:all :end} ~wholeness) 20 | (.writeEndObject ~jg))) 21 | 22 | (definline write-start-array [^JsonGenerator jg wholeness] 23 | `(if (contains? #{:all :start :start-inner} ~wholeness) 24 | (.writeStartArray ~jg))) 25 | 26 | (definline write-end-array [^JsonGenerator jg wholeness] 27 | `(if (contains? #{:all :end} ~wholeness) 28 | (.writeEndArray ~jg))) 29 | 30 | (declare generate) 31 | 32 | (definline generate-basic-map 33 | [^JsonGenerator jg obj ^String date-format ^Exception e 34 | wholeness] 35 | (let [jg (tag jg)] 36 | `(do 37 | (write-start-object ~jg ~wholeness) 38 | (doseq [m# ~obj] 39 | (let [k# (key m#) 40 | v# (val m#)] 41 | (.writeFieldName ~jg (if (keyword? k#) 42 | (.substring (str k#) 1) 43 | (str k#))) 44 | (generate ~jg v# ~date-format ~e nil 45 | :wholeness (if (= ~wholeness :start-inner) 46 | :start 47 | :all)))) 48 | (write-end-object ~jg ~wholeness)))) 49 | 50 | (definline generate-key-fn-map 51 | [^JsonGenerator jg obj ^String date-format ^Exception e 52 | key-fn wholeness] 53 | (let [k (gensym 'k) 54 | name (gensym 'name) 55 | jg (tag jg)] 56 | `(do 57 | (write-start-object ~jg ~wholeness) 58 | (doseq [m# ~obj] 59 | (let [~k (key m#) 60 | v# (val m#) 61 | ^String name# (if (keyword? ~k) 62 | (~key-fn ~k) 63 | (str ~k))] 64 | (.writeFieldName ~jg name#) 65 | (generate ~jg v# ~date-format ~e ~key-fn 66 | :wholeness (if (= ~wholeness :start-inner) 67 | :start 68 | :all)))) 69 | (write-end-object ~jg ~wholeness)))) 70 | 71 | (definline generate-map 72 | [^JsonGenerator jg obj ^String date-format ^Exception e 73 | key-fn wholeness] 74 | `(if (nil? ~key-fn) 75 | (generate-basic-map ~jg ~obj ~date-format ~e ~wholeness) 76 | (generate-key-fn-map ~jg ~obj ~date-format ~e ~key-fn ~wholeness))) 77 | 78 | (definline generate-array [^JsonGenerator jg obj ^String date-format 79 | ^Exception e key-fn wholeness] 80 | (let [jg (tag jg)] 81 | `(do 82 | (write-start-array ~jg ~wholeness) 83 | (doseq [item# ~obj] 84 | (generate ~jg item# ~date-format ~e ~key-fn 85 | :wholeness (if (= ~wholeness :start-inner) 86 | :start 87 | :all))) 88 | (write-end-array ~jg ~wholeness)))) 89 | 90 | (defn generate [^JsonGenerator jg obj ^String date-format 91 | ^Exception ex key-fn & {:keys [wholeness]}] 92 | (let [wholeness (or wholeness :all)] 93 | (cond 94 | (nil? obj) (.writeNull ^JsonGenerator jg) 95 | (get (:impls JSONable) (class obj)) (#'to-json obj jg) 96 | 97 | (i? IPersistentCollection obj) 98 | (condp instance? obj 99 | clojure.lang.IPersistentMap 100 | (generate-map jg obj date-format ex key-fn wholeness) 101 | clojure.lang.IPersistentVector 102 | (generate-array jg obj date-format ex key-fn wholeness) 103 | clojure.lang.IPersistentSet 104 | (generate-array jg obj date-format ex key-fn wholeness) 105 | clojure.lang.IPersistentList 106 | (generate-array jg obj date-format ex key-fn wholeness) 107 | clojure.lang.ISeq 108 | (generate-array jg obj date-format ex key-fn wholeness) 109 | clojure.lang.Associative 110 | (generate-map jg obj date-format ex key-fn wholeness)) 111 | 112 | (i? Number obj) (number-dispatch ^JsonGenerator jg obj ex) 113 | (i? Boolean obj) (.writeBoolean ^JsonGenerator jg ^Boolean obj) 114 | (i? String obj) (write-string ^JsonGenerator jg ^String obj) 115 | (i? Character obj) (write-string ^JsonGenerator jg ^String (str obj)) 116 | (i? Keyword obj) (write-string ^JsonGenerator jg (.substring (str obj) 1)) 117 | (i? Map obj) (generate-map jg obj date-format ex key-fn wholeness) 118 | (i? List obj) (generate-array jg obj date-format ex key-fn wholeness) 119 | (i? Set obj) (generate-array jg obj date-format ex key-fn wholeness) 120 | (i? UUID obj) (write-string ^JsonGenerator jg (.toString ^UUID obj)) 121 | (i? Symbol obj) (write-string ^JsonGenerator jg (.toString ^Symbol obj)) 122 | (i? Date obj) (let [sdf (doto (SimpleDateFormat. date-format) 123 | (.setTimeZone (SimpleTimeZone. 0 "UTC")))] 124 | (write-string ^JsonGenerator jg (.format sdf obj))) 125 | (i? Timestamp obj) (let [date (Date. (.getTime ^Timestamp obj)) 126 | sdf (doto (SimpleDateFormat. date-format) 127 | (.setTimeZone (SimpleTimeZone. 0 "UTC")))] 128 | (write-string ^JsonGenerator jg (.format sdf obj))) 129 | :else (fail obj jg ex)))) 130 | -------------------------------------------------------------------------------- /test/cheshire/test/custom.clj: -------------------------------------------------------------------------------- 1 | (ns cheshire.test.custom 2 | "DEPRECATED, kept here to ensure backward compatibility." 3 | (:use [clojure.test] 4 | [clojure.java.io :only [reader]]) 5 | (:require [cheshire.custom :as json] :reload 6 | [cheshire.factory :as fact] 7 | [cheshire.parse :as parse]) 8 | (:import (java.io StringReader StringWriter 9 | BufferedReader BufferedWriter) 10 | (java.sql Timestamp) 11 | (java.util Date UUID))) 12 | 13 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 14 | ;;;;;; DEPRECATED, DO NOT USE ;;;;;; 15 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 16 | 17 | 18 | ;; Generally, all tests in here should use json/encode*, unless you 19 | ;; know what you're doing and what you're trying to test. 20 | 21 | (def test-obj {"int" 3 "long" (long -2147483647) "boolean" true 22 | "LongObj" (Long/parseLong "2147483647") "double" 1.23 23 | "nil" nil "string" "string" "vec" [1 2 3] "map" {"a" "b"} 24 | "list" (list "a" "b") "short" (short 21) "byte" (byte 3)}) 25 | 26 | (deftest t-ratio 27 | (let [n 1/2] 28 | (is (= (double n) (:num (json/decode (json/encode* {:num n}) true)))))) 29 | 30 | (deftest t-long-wrap-around 31 | (is (= 2147483648 (json/decode (json/encode* 2147483648))))) 32 | 33 | (deftest t-bigint 34 | (let [n 9223372036854775808] 35 | (is (= n (:num (json/decode (json/encode* {:num n}) true)))))) 36 | 37 | (deftest t-biginteger 38 | (let [n (BigInteger. "42")] 39 | (is (= n (:num (json/decode (json/encode* {:num n}) true)))))) 40 | 41 | (deftest t-bigdecimal 42 | (let [n (BigDecimal. "42.5")] 43 | (is (= (.doubleValue n) (:num (json/decode (json/encode* {:num n}) true)))) 44 | (binding [parse/*use-bigdecimals?* true] 45 | (is (= n (:num (json/decode (json/encode* {:num n}) true))))))) 46 | 47 | (deftest test-string-round-trip 48 | (is (= test-obj (json/decode (json/encode* test-obj))))) 49 | 50 | (deftest test-generate-accepts-float 51 | (is (= "3.14" (json/encode* 3.14)))) 52 | 53 | (deftest test-keyword-encode 54 | (is (= {"key" "val"} 55 | (json/decode (json/encode* {:key "val"}))))) 56 | 57 | (deftest test-generate-set 58 | (is (= {"set" ["a" "b"]} 59 | (json/decode (json/encode* {"set" #{"a" "b"}}))))) 60 | 61 | (deftest test-generate-empty-set 62 | (is (= {"set" []} 63 | (json/decode (json/encode* {"set" #{}}))))) 64 | 65 | (deftest test-generate-empty-array 66 | (is (= {"array" []} 67 | (json/decode (json/encode* {"array" []}))))) 68 | 69 | (deftest test-key-coercion 70 | (is (= {"foo" "bar" "1" "bat" "2" "bang" "3" "biz"} 71 | (json/decode 72 | (json/encode 73 | {:foo "bar" 1 "bat" (long 2) "bang" (bigint 3) "biz"}))))) 74 | 75 | (deftest test-keywords 76 | (is (= {:foo "bar" :bat 1} 77 | (json/decode (json/encode* {:foo "bar" :bat 1}) true)))) 78 | 79 | (deftest test-symbols 80 | (is (= {"foo" "clojure.core/map"} 81 | (json/decode (json/encode* {"foo" 'clojure.core/map}))))) 82 | 83 | (deftest test-accepts-java-map 84 | (is (= {"foo" 1} 85 | (json/decode 86 | (json/encode (doto (java.util.HashMap.) (.put "foo" 1))))))) 87 | 88 | (deftest test-accepts-java-list 89 | (is (= [1 2 3] 90 | (json/decode (json/encode (doto (java.util.ArrayList. 3) 91 | (.add 1) 92 | (.add 2) 93 | (.add 3))))))) 94 | 95 | (deftest test-accepts-java-set 96 | (is (= {"set" [1 2 3]} 97 | (json/decode (json/encode {"set" (doto (java.util.HashSet. 3) 98 | (.add 1) 99 | (.add 2) 100 | (.add 3))}))))) 101 | 102 | (deftest test-accepts-empty-java-set 103 | (is (= {"set" []} 104 | (json/decode (json/encode {"set" (java.util.HashSet. 3)}))))) 105 | 106 | (deftest test-nil 107 | (is (nil? (json/decode nil true)))) 108 | 109 | (deftest test-parsed-seq 110 | (let [br (BufferedReader. (StringReader. "1\n2\n3\n"))] 111 | (is (= (list 1 2 3) (json/parsed-seq br))))) 112 | 113 | (deftest test-smile-round-trip 114 | (is (= test-obj (json/parse-smile (json/generate-smile test-obj))))) 115 | 116 | (deftest test-aliases 117 | (is (= {"foo" "bar" "1" "bat" "2" "bang" "3" "biz"} 118 | (json/decode 119 | (json/encode 120 | {:foo "bar" 1 "bat" (long 2) "bang" (bigint 3) "biz"}))))) 121 | 122 | (deftest test-date 123 | (is (= {"foo" "1970-01-01T00:00:00Z"} 124 | (json/decode 125 | (json/encode* 126 | {:foo (Date. (long 0))})))) 127 | (is (= {"foo" "1970-01-01"} 128 | (json/decode 129 | (json/encode* 130 | {:foo (Date. (long 0))} {:date-format "yyyy-MM-dd"}))) 131 | "encode with given date format")) 132 | 133 | (deftest test-sql-timestamp 134 | (is (= {"foo" "1970-01-01T00:00:00Z"} 135 | (json/decode (json/encode* {:foo (Timestamp. (long 0))})))) 136 | (is (= {"foo" "1970-01-01"} 137 | (json/decode (json/encode* {:foo (Timestamp. (long 0))} 138 | {:date-format "yyyy-MM-dd"}))) 139 | "encode with given date format")) 140 | 141 | (deftest test-uuid 142 | (let [id (UUID/randomUUID) 143 | id-str (str id)] 144 | (is (= {"foo" id-str} (json/decode (json/encode* {:foo id})))))) 145 | 146 | (deftest test-streams 147 | (is (= {"foo" "bar"} 148 | (json/parse-stream 149 | (BufferedReader. (StringReader. "{\"foo\":\"bar\"}\n"))))) 150 | (let [sw (StringWriter.) 151 | bw (BufferedWriter. sw)] 152 | (json/generate-stream {"foo" "bar"} bw) 153 | (is (= "{\"foo\":\"bar\"}" (.toString sw)))) 154 | (is (= {(keyword "foo baz") "bar"} 155 | (with-open [rdr (StringReader. "{\"foo baz\":\"bar\"}\n")] 156 | (json/parse-stream rdr true))))) 157 | 158 | (deftest test-multiple-objs-in-file 159 | (is (= {"one" 1, "foo" "bar"} 160 | (first (json/parsed-seq (reader "test/multi.json"))))) 161 | (is (= {"two" 2, "foo" "bar"} 162 | (second (json/parsed-seq (reader "test/multi.json")))))) 163 | 164 | (deftest test-jsondotorg-pass1 165 | (let [string (slurp "test/pass1.json") 166 | decoded-json (json/decode string) 167 | encoded-json (json/encode* decoded-json) 168 | re-decoded-json (json/decode encoded-json)] 169 | (is (= decoded-json re-decoded-json)))) 170 | 171 | (deftest test-namespaced-keywords 172 | (is (= "{\"foo\":\"user/bar\"}" 173 | (json/encode* {:foo :user/bar}))) 174 | (is (= {:foo/bar "baz/eggplant"} 175 | (json/decode (json/encode* {:foo/bar :baz/eggplant}) true)))) 176 | 177 | (deftest test-array-coerce-fn 178 | (is (= {"set" #{"a" "b"} "array" ["a" "b"] "map" {"a" 1}} 179 | (json/decode 180 | (json/encode* {"set" #{"a" "b"} 181 | "array" ["a" "b"] 182 | "map" {"a" 1}}) false 183 | (fn [field-name] (if (= "set" field-name) #{} [])))))) 184 | 185 | (deftest t-symbol-encoding-for-non-resolvable-symbols 186 | (is (= "{\"bar\":\"clojure.core/pam\",\"foo\":\"clojure.core/map\"}" 187 | (json/encode* (sorted-map :foo 'clojure.core/map :bar 'clojure.core/pam)))) 188 | (is (= "{\"bar\":\"clojure.core/pam\",\"foo\":\"foo.bar/baz\"}" 189 | (json/encode* (sorted-map :foo 'foo.bar/baz :bar 'clojure.core/pam))))) 190 | 191 | (deftest t-bindable-factories 192 | (binding [fact/*json-factory* (fact/make-json-factory 193 | {:allow-non-numeric-numbers true})] 194 | (is (= (type Double/NaN) 195 | (type (:foo (json/decode "{\"foo\":NaN}" true))))))) 196 | 197 | (deftest t-persistent-queue 198 | (let [q (conj (clojure.lang.PersistentQueue/EMPTY) 1 2 3)] 199 | (is (= q (json/decode (json/encode* q)))))) 200 | 201 | (deftest t-pretty-print 202 | (is (= (str "{\n \"bar\" : [ {\n \"baz\" : 2\n }, " 203 | "\"quux\", [ 1, 2, 3 ] ],\n \"foo\" : 1\n}") 204 | (json/encode* (sorted-map :foo 1 :bar [{:baz 2} :quux [1 2 3]]) 205 | {:pretty true})))) 206 | 207 | (deftest t-unicode-escaping 208 | (is (= "{\"foo\":\"It costs \\u00A3100\"}" 209 | (json/encode* {:foo "It costs £100"} {:escape-non-ascii true})))) 210 | 211 | (deftest t-custom-keyword-fn 212 | (is (= {:FOO "bar"} (json/decode "{\"foo\": \"bar\"}" 213 | (fn [k] (keyword (.toUpperCase k)))))) 214 | (is (= {"foo" "bar"} (json/decode "{\"foo\": \"bar\"}" nil))) 215 | (is (= {"foo" "bar"} (json/decode "{\"foo\": \"bar\"}" false))) 216 | (is (= {:foo "bar"} (json/decode "{\"foo\": \"bar\"}" true)))) 217 | 218 | ;; Begin custom-only tests 219 | 220 | (deftest test-add-remove-encoder 221 | (json/remove-encoder java.net.URL) 222 | (json/add-encoder java.net.URL json/encode-str) 223 | (is (= "\"http://foo.com\"" 224 | (json/encode (java.net.URL. "http://foo.com")))) 225 | (json/remove-encoder java.net.URL) 226 | (is (thrown? IllegalArgumentException 227 | (json/encode (java.net.URL. "http://foo.com"))))) 228 | 229 | ;; Test that default encoders can be bypassed if so desired. 230 | (deftest test-shadowing-default-encoder 231 | (json/remove-encoder java.util.Date) 232 | (json/add-encoder java.util.Date 233 | (fn [d jg] (json/encode-str "foo" jg))) 234 | (is (= "\"foo\"" (json/encode* (java.util.Date.)))) 235 | (is (= "\"foo\"" (json/encode* :foo))) 236 | (json/remove-encoder java.util.Date) 237 | (json/add-encoder java.util.Date json/encode-date) 238 | (is (json/encode (java.util.Date.)) 239 | "shouldn't throw an exception after adding back the default.")) 240 | -------------------------------------------------------------------------------- /src/cheshire/generate.clj: -------------------------------------------------------------------------------- 1 | (ns cheshire.generate 2 | "Namespace used to generate JSON from Clojure data structures." 3 | (:import (com.fasterxml.jackson.core JsonGenerator JsonGenerationException) 4 | (java.util Date Map List Set SimpleTimeZone UUID) 5 | (java.sql Timestamp) 6 | (java.text SimpleDateFormat) 7 | (java.math BigInteger) 8 | (clojure.lang IPersistentCollection Keyword Ratio Symbol))) 9 | 10 | ;; date format rebound for custom encoding 11 | (def ^{:dynamic true :private true} *date-format*) 12 | 13 | (defmacro tag 14 | ([obj] 15 | `(vary-meta ~obj assoc :tag `JsonGenerator))) 16 | 17 | (defprotocol JSONable 18 | (to-json [t jg])) 19 | 20 | (definline write-string [^JsonGenerator jg ^String str] 21 | `(.writeString ~(tag jg) ~str)) 22 | 23 | (defmacro fail [obj jg ^Exception e] 24 | `(try 25 | (to-json ~obj ~jg) 26 | (catch IllegalArgumentException _# 27 | (throw (or ~e (JsonGenerationException. 28 | (str "Cannot JSON encode object of class: " 29 | (class ~obj) ": " ~obj))))))) 30 | 31 | (defmacro number-dispatch [jg obj e] 32 | (let [g (tag (gensym 'jg)) 33 | o (gensym 'obj) 34 | common-clauses `[Integer (.writeNumber ~g (int ~o)) 35 | Long (.writeNumber ~g (long ~o)) 36 | Double (.writeNumber ~g (double ~o)) 37 | Float (.writeNumber ~g (double ~o)) 38 | BigInteger (.writeNumber 39 | ~g ~(with-meta o {:tag `BigInteger})) 40 | BigDecimal (.writeNumber 41 | ~g ~(with-meta o {:tag `BigDecimal})) 42 | Ratio (.writeNumber ~g (double ~o)) 43 | Short (.writeNumber ~g (int ~o)) 44 | Byte (.writeNumber ~g (int ~o))]] 45 | `(let [~g ~jg 46 | ~o ~obj] 47 | (condp instance? ~o 48 | ~@(if (< 2 (:minor *clojure-version*)) 49 | `[~@common-clauses 50 | clojure.lang.BigInt (.writeNumber 51 | ~g (.toBigInteger 52 | ~(vary-meta obj assoc :tag 53 | `clojure.lang.BigInt)))] 54 | common-clauses) 55 | (fail ~o ~g ~e))))) 56 | 57 | (declare generate) 58 | 59 | (definline generate-basic-map 60 | [^JsonGenerator jg obj ^String date-format ^Exception e] 61 | (let [jg (tag jg)] 62 | `(do 63 | (.writeStartObject ~jg) 64 | (doseq [m# ~obj] 65 | (let [k# (key m#) 66 | v# (val m#)] 67 | (.writeFieldName ~jg (if (keyword? k#) 68 | (.substring (str k#) 1) 69 | (str k#))) 70 | (generate ~jg v# ~date-format ~e nil))) 71 | (.writeEndObject ~jg)))) 72 | 73 | (definline generate-key-fn-map 74 | [^JsonGenerator jg obj ^String date-format ^Exception e key-fn] 75 | (let [k (gensym 'k) 76 | name (gensym 'name) 77 | jg (tag jg)] 78 | `(do 79 | (.writeStartObject ~jg) 80 | (doseq [m# ~obj] 81 | (let [~k (key m#) 82 | v# (val m#) 83 | ^String name# (if (keyword? ~k) 84 | (~key-fn ~k) 85 | (str ~k))] 86 | (.writeFieldName ~jg name#) 87 | (generate ~jg v# ~date-format ~e ~key-fn))) 88 | (.writeEndObject ~jg)))) 89 | 90 | (definline generate-map 91 | [^JsonGenerator jg obj ^String date-format ^Exception e key-fn] 92 | `(if (nil? ~key-fn) 93 | (generate-basic-map ~jg ~obj ~date-format ~e) 94 | (generate-key-fn-map ~jg ~obj ~date-format ~e ~key-fn))) 95 | 96 | (definline generate-array [^JsonGenerator jg obj ^String date-format 97 | ^Exception e key-fn] 98 | (let [jg (tag jg)] 99 | `(do 100 | (.writeStartArray ~jg) 101 | (doseq [item# ~obj] 102 | (generate ~jg item# ~date-format ~e ~key-fn)) 103 | (.writeEndArray ~jg)))) 104 | 105 | (defmacro i? 106 | "Just to shorten 'instance?' and for debugging." 107 | [k obj] 108 | ;;(println :inst? k obj) 109 | `(instance? ~k ~obj)) 110 | 111 | (defn generate [^JsonGenerator jg obj ^String date-format ^Exception ex key-fn] 112 | (cond 113 | (nil? obj) (.writeNull ^JsonGenerator jg) 114 | (get (:impls JSONable) (class obj)) (#'to-json obj jg) 115 | 116 | (i? IPersistentCollection obj) 117 | (condp instance? obj 118 | clojure.lang.IPersistentMap 119 | (generate-map jg obj date-format ex key-fn) 120 | clojure.lang.IPersistentVector 121 | (generate-array jg obj date-format ex key-fn) 122 | clojure.lang.IPersistentSet 123 | (generate-array jg obj date-format ex key-fn) 124 | clojure.lang.IPersistentList 125 | (generate-array jg obj date-format ex key-fn) 126 | clojure.lang.ISeq 127 | (generate-array jg obj date-format ex key-fn) 128 | clojure.lang.Associative 129 | (generate-map jg obj date-format ex key-fn)) 130 | 131 | (i? Number obj) (number-dispatch ^JsonGenerator jg obj ex) 132 | (i? Boolean obj) (.writeBoolean ^JsonGenerator jg ^Boolean obj) 133 | (i? String obj) (write-string ^JsonGenerator jg ^String obj) 134 | (i? Character obj) (write-string ^JsonGenerator jg ^String (str obj)) 135 | (i? Keyword obj) (write-string ^JsonGenerator jg (.substring (str obj) 1)) 136 | (i? Map obj) (generate-map jg obj date-format ex key-fn) 137 | (i? List obj) (generate-array jg obj date-format ex key-fn) 138 | (i? Set obj) (generate-array jg obj date-format ex key-fn) 139 | (i? UUID obj) (write-string ^JsonGenerator jg (.toString ^UUID obj)) 140 | (i? Symbol obj) (write-string ^JsonGenerator jg (.toString ^Symbol obj)) 141 | (i? Date obj) (let [sdf (doto (SimpleDateFormat. date-format) 142 | (.setTimeZone (SimpleTimeZone. 0 "UTC")))] 143 | (write-string ^JsonGenerator jg (.format sdf obj))) 144 | (i? Timestamp obj) (let [date (Date. (.getTime ^Timestamp obj)) 145 | sdf (doto (SimpleDateFormat. date-format) 146 | (.setTimeZone (SimpleTimeZone. 0 "UTC")))] 147 | (write-string ^JsonGenerator jg (.format sdf obj))) 148 | :else (fail obj jg ex))) 149 | 150 | ;; Generic encoders, these can be used by someone writing a custom 151 | ;; encoder if so desired, after transforming an arbitrary data 152 | ;; structure into a clojure one, these can just be called. 153 | (defn encode-nil 154 | "Encode null to the json generator." 155 | [_ ^JsonGenerator jg] 156 | (.writeNull jg)) 157 | 158 | (defn encode-str 159 | "Encode a string to the json generator." 160 | [^String s ^JsonGenerator jg] 161 | (.writeString jg (str s))) 162 | 163 | (defn encode-number 164 | "Encode anything implementing java.lang.Number to the json generator." 165 | [^java.lang.Number n ^JsonGenerator jg] 166 | (number-dispatch jg n nil)) 167 | 168 | (defn encode-long 169 | "Encode anything implementing java.lang.Number to the json generator." 170 | [^Long n ^JsonGenerator jg] 171 | (.writeNumber jg (long n))) 172 | 173 | (defn encode-int 174 | "Encode anything implementing java.lang.Number to the json generator." 175 | [n ^JsonGenerator jg] 176 | (.writeNumber jg (long n))) 177 | 178 | (defn encode-ratio 179 | "Encode a clojure.lang.Ratio to the json generator." 180 | [^clojure.lang.Ratio n ^JsonGenerator jg] 181 | (.writeNumber jg (double n))) 182 | 183 | (defn encode-seq 184 | "Encode a seq to the json generator." 185 | [s ^JsonGenerator jg] 186 | (.writeStartArray jg) 187 | (doseq [i s] 188 | (generate jg i *date-format* nil nil)) 189 | (.writeEndArray jg)) 190 | 191 | (defn encode-date 192 | "Encode a date object to the json generator." 193 | [^Date d ^JsonGenerator jg] 194 | (let [sdf (SimpleDateFormat. *date-format*)] 195 | (.setTimeZone sdf (SimpleTimeZone. 0 "UTC")) 196 | (.writeString jg (.format sdf d)))) 197 | 198 | (defn encode-bool 199 | "Encode a Boolean object to the json generator." 200 | [^Boolean b ^JsonGenerator jg] 201 | (.writeBoolean jg b)) 202 | 203 | (defn encode-named 204 | "Encode a keyword to the json generator." 205 | [^clojure.lang.Keyword k ^JsonGenerator jg] 206 | (.writeString jg (if-let [ns (namespace k)] 207 | (str ns "/" (name k)) 208 | (name k)))) 209 | 210 | (defn encode-map 211 | "Encode a clojure map to the json generator." 212 | [^clojure.lang.IPersistentMap m ^JsonGenerator jg] 213 | (.writeStartObject jg) 214 | (doseq [[k v] m] 215 | (.writeFieldName jg (if (instance? clojure.lang.Keyword k) 216 | (if-let [ns (namespace k)] 217 | (str ns "/" (name k)) 218 | (name k)) 219 | (str k))) 220 | (generate jg v *date-format* nil nil)) 221 | (.writeEndObject jg)) 222 | 223 | (defn encode-symbol 224 | "Encode a clojure symbol to the json generator." 225 | [^clojure.lang.Symbol s ^JsonGenerator jg] 226 | (.writeString jg (str s))) 227 | 228 | ;; Utility methods to add and remove encoders 229 | (defn add-encoder 230 | "Provide an encoder for a type not handled by Cheshire. 231 | 232 | ex. (add-encoder java.net.URL encode-string) 233 | 234 | See encode-str, encode-map, etc, in the cheshire.custom 235 | namespace for encoder examples." 236 | [cls encoder] 237 | (extend cls 238 | JSONable 239 | {:to-json encoder})) 240 | 241 | (defn remove-encoder 242 | "Remove encoder for a given type. 243 | 244 | ex. (remove-encoder java.net.URL)" 245 | [cls] 246 | (alter-var-root #'JSONable #(assoc % :impls (dissoc (:impls %) cls))) 247 | (clojure.core/-reset-methods JSONable)) 248 | -------------------------------------------------------------------------------- /src/cheshire/custom.clj: -------------------------------------------------------------------------------- 1 | (ns cheshire.custom 2 | "DEPRECATED 3 | 4 | Methods used for extending JSON generation to different Java classes. 5 | Has the same public API as core.clj so they can be swapped in and out." 6 | (:use [cheshire.factory]) 7 | (:require [cheshire.core :as core] 8 | [cheshire.generate :as generate]) 9 | (:import (java.io BufferedWriter ByteArrayOutputStream StringWriter) 10 | (java.util Date SimpleTimeZone) 11 | (java.text SimpleDateFormat) 12 | (java.sql Timestamp) 13 | (com.fasterxml.jackson.dataformat.smile SmileFactory) 14 | (com.fasterxml.jackson.core JsonFactory JsonGenerator 15 | JsonGenerator$Feature 16 | JsonGenerationException JsonParser))) 17 | 18 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 19 | ;;;;;; DEPRECATED, DO NOT USE ;;;;;; 20 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 21 | 22 | 23 | ;; date format rebound for custom encoding 24 | (def ^{:dynamic true :private true} *date-format*) 25 | 26 | ;; pre-allocated exception for fast-failing core attempt for custom encoding 27 | (def ^{:private true} core-failure (JsonGenerationException. 28 | "Cannot custom JSON encode object")) 29 | 30 | (defprotocol JSONable 31 | (to-json [t jg])) 32 | 33 | (defn ^String encode* 34 | ([obj] 35 | (encode* obj nil)) 36 | ([obj opt-map] 37 | (binding [*date-format* (or (:date-format opt-map) default-date-format)] 38 | (let [sw (StringWriter.) 39 | generator (.createJsonGenerator 40 | ^JsonFactory (or *json-factory* json-factory) sw)] 41 | (when (:pretty opt-map) 42 | (.useDefaultPrettyPrinter generator)) 43 | (when (:escape-non-ascii opt-map) 44 | (.enable generator JsonGenerator$Feature/ESCAPE_NON_ASCII)) 45 | (if obj 46 | (to-json obj generator) 47 | (.writeNull generator)) 48 | (.flush generator) 49 | (.toString sw))))) 50 | 51 | (def ^String encode encode*) 52 | 53 | (defn ^String encode-stream* 54 | ([obj ^BufferedWriter w] 55 | (encode-stream* obj w nil)) 56 | ([obj ^BufferedWriter w opt-map] 57 | (binding [*date-format* (or (:date-format opt-map) default-date-format)] 58 | (let [generator (.createJsonGenerator 59 | ^JsonFactory (or *json-factory* json-factory) w)] 60 | (when (:pretty opt-map) 61 | (.useDefaultPrettyPrinter generator)) 62 | (when (:escape-non-ascii opt-map) 63 | (.enable generator JsonGenerator$Feature/ESCAPE_NON_ASCII)) 64 | (to-json obj generator) 65 | (.flush generator) 66 | w)))) 67 | 68 | (def ^String encode-stream encode-stream*) 69 | 70 | (defn encode-smile* 71 | ([obj] 72 | (encode-smile* obj nil)) 73 | ([obj opt-map] 74 | (binding [*date-format* (or (:date-format opt-map) default-date-format)] 75 | (let [baos (ByteArrayOutputStream.) 76 | generator (.createJsonGenerator ^SmileFactory 77 | (or *smile-factory* smile-factory) 78 | baos)] 79 | (to-json obj generator) 80 | (.flush generator) 81 | (.toByteArray baos))))) 82 | 83 | (def encode-smile encode-smile*) 84 | 85 | ;; there are no differences in parsing, but these are here to make 86 | ;; this a self-contained namespace if desired 87 | (def parse core/decode) 88 | (def parse-string core/decode) 89 | (def parse-stream core/decode-stream) 90 | (def parse-smile core/decode-smile) 91 | (def parsed-seq core/parsed-seq) 92 | (def decode core/parse-string) 93 | (def decode-stream parse-stream) 94 | (def decode-smile parse-smile) 95 | 96 | ;; aliases for encoding 97 | (def generate-string encode*) 98 | (def generate-string* encode*) 99 | (def generate-stream encode-stream*) 100 | (def generate-stream* encode-stream*) 101 | (def generate-smile encode-smile*) 102 | (def generate-smile* encode-smile*) 103 | 104 | 105 | ;; Generic encoders, these can be used by someone writing a custom 106 | ;; encoder if so desired, after transforming an arbitrary data 107 | ;; structure into a clojure one, these can just be called. 108 | (defn encode-nil 109 | "Encode null to the json generator." 110 | [_ ^JsonGenerator jg] 111 | (.writeNull jg)) 112 | 113 | (defn encode-str 114 | "Encode a string to the json generator." 115 | [^String s ^JsonGenerator jg] 116 | (.writeString jg (str s))) 117 | 118 | (defn encode-number 119 | "Encode anything implementing java.lang.Number to the json generator." 120 | [^java.lang.Number n ^JsonGenerator jg] 121 | (generate/encode-number n jg)) 122 | 123 | (defn encode-long 124 | "Encode anything implementing java.lang.Number to the json generator." 125 | [^Long n ^JsonGenerator jg] 126 | (.writeNumber jg (long n))) 127 | 128 | (defn encode-int 129 | "Encode anything implementing java.lang.Number to the json generator." 130 | [n ^JsonGenerator jg] 131 | (.writeNumber jg (long n))) 132 | 133 | (defn encode-ratio 134 | "Encode a clojure.lang.Ratio to the json generator." 135 | [^clojure.lang.Ratio n ^JsonGenerator jg] 136 | (.writeNumber jg (double n))) 137 | 138 | (defn encode-seq 139 | "Encode a seq to the json generator." 140 | [s ^JsonGenerator jg] 141 | (.writeStartArray jg) 142 | (doseq [i s] 143 | (to-json i jg)) 144 | (.writeEndArray jg)) 145 | 146 | (defn encode-date 147 | "Encode a date object to the json generator." 148 | [^Date d ^JsonGenerator jg] 149 | (let [sdf (SimpleDateFormat. *date-format*)] 150 | (.setTimeZone sdf (SimpleTimeZone. 0 "UTC")) 151 | (.writeString jg (.format sdf d)))) 152 | 153 | (defn encode-bool 154 | "Encode a Boolean object to the json generator." 155 | [^Boolean b ^JsonGenerator jg] 156 | (.writeBoolean jg b)) 157 | 158 | (defn encode-named 159 | "Encode a keyword to the json generator." 160 | [^clojure.lang.Keyword k ^JsonGenerator jg] 161 | (.writeString jg (if-let [ns (namespace k)] 162 | (str ns "/" (name k)) 163 | (name k)))) 164 | 165 | (defn encode-map 166 | "Encode a clojure map to the json generator." 167 | [^clojure.lang.IPersistentMap m ^JsonGenerator jg] 168 | (.writeStartObject jg) 169 | (doseq [[k v] m] 170 | (.writeFieldName jg (if (instance? clojure.lang.Keyword k) 171 | (if-let [ns (namespace k)] 172 | (str ns "/" (name k)) 173 | (name k)) 174 | (str k))) 175 | (to-json v jg)) 176 | (.writeEndObject jg)) 177 | 178 | (defn encode-symbol 179 | "Encode a clojure symbol to the json generator." 180 | [^clojure.lang.Symbol s ^JsonGenerator jg] 181 | (.writeString jg (str s))) 182 | 183 | ;; extended implementations for clojure datastructures 184 | (extend nil 185 | JSONable 186 | {:to-json encode-nil}) 187 | 188 | (extend java.lang.String 189 | JSONable 190 | {:to-json encode-str}) 191 | 192 | ;; This is lame, thanks for changing all the BigIntegers to BigInts 193 | ;; in 1.3 clojure/core :-/ 194 | (defmacro handle-bigint [] 195 | (when (not= {:major 1 :minor 2} (select-keys *clojure-version* 196 | [:major :minor])) 197 | `(extend clojure.lang.BigInt 198 | JSONable 199 | {:to-json ~'(fn encode-bigint 200 | [^clojure.lang.BigInt n ^JsonGenerator jg] 201 | (.writeNumber jg (.toBigInteger n)))}))) 202 | (handle-bigint) 203 | 204 | (extend clojure.lang.Ratio 205 | JSONable 206 | {:to-json encode-ratio}) 207 | 208 | (extend Long 209 | JSONable 210 | {:to-json encode-long}) 211 | 212 | (extend Short 213 | JSONable 214 | {:to-json encode-int}) 215 | 216 | (extend Byte 217 | JSONable 218 | {:to-json encode-int}) 219 | 220 | (extend java.lang.Number 221 | JSONable 222 | {:to-json encode-number}) 223 | 224 | (extend clojure.lang.ISeq 225 | JSONable 226 | {:to-json encode-seq}) 227 | 228 | (extend clojure.lang.IPersistentVector 229 | JSONable 230 | {:to-json encode-seq}) 231 | 232 | (extend clojure.lang.IPersistentSet 233 | JSONable 234 | {:to-json encode-seq}) 235 | 236 | (extend clojure.lang.IPersistentList 237 | JSONable 238 | {:to-json encode-seq}) 239 | 240 | (extend java.util.Date 241 | JSONable 242 | {:to-json encode-date}) 243 | 244 | (extend java.sql.Timestamp 245 | JSONable 246 | {:to-json #(encode-date (Date. (.getTime ^java.sql.Timestamp %1)) %2)}) 247 | 248 | (extend java.util.UUID 249 | JSONable 250 | {:to-json encode-str}) 251 | 252 | (extend java.lang.Boolean 253 | JSONable 254 | {:to-json encode-bool}) 255 | 256 | (extend clojure.lang.Keyword 257 | JSONable 258 | {:to-json encode-named}) 259 | 260 | (extend clojure.lang.IPersistentMap 261 | JSONable 262 | {:to-json encode-map}) 263 | 264 | (extend clojure.lang.Symbol 265 | JSONable 266 | {:to-json encode-symbol}) 267 | 268 | (extend clojure.lang.Associative 269 | JSONable 270 | {:to-json encode-map}) 271 | 272 | (extend java.util.Map 273 | JSONable 274 | {:to-json encode-map}) 275 | 276 | (extend java.util.Set 277 | JSONable 278 | {:to-json encode-seq}) 279 | 280 | (extend java.util.List 281 | JSONable 282 | {:to-json encode-seq}) 283 | ;; Utility methods to add and remove encoders 284 | (defn add-encoder 285 | "Provide an encoder for a type not handled by Cheshire. 286 | 287 | ex. (add-encoder java.net.URL encode-string) 288 | 289 | See encode-str, encode-map, etc, in the cheshire.custom 290 | namespace for encoder examples." 291 | [cls encoder] 292 | (extend cls 293 | JSONable 294 | {:to-json encoder})) 295 | 296 | (defn remove-encoder 297 | "Remove encoder for a given type. 298 | 299 | ex. (remove-encoder java.net.URL)" 300 | [cls] 301 | (alter-var-root #'JSONable #(assoc % :impls (dissoc (:impls %) cls))) 302 | (clojure.core/-reset-methods JSONable)) 303 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Cheshire 2 | 3 | 5 | 6 | 'Cheshire Puss,' she began, rather timidly, as she did not at all know 7 | whether it would like the name: however, it only grinned a little 8 | wider. 'Come, it's pleased so far,' thought Alice, and she went 9 | on. 'Would you tell me, please, which way I ought to go from here?' 10 | 11 | 'That depends a good deal on where you want to get to,' said the Cat. 12 | 13 | 'I don't much care where--' said Alice. 14 | 15 | 'Then it doesn't matter which way you go,' said the Cat. 16 | 17 | '--so long as I get SOMEWHERE,' Alice added as an explanation. 18 | 19 | 'Oh, you're sure to do that,' said the Cat, 'if you only walk long 20 | enough.' 21 | 22 |

23 | Cheshire is fast JSON encoding, based off of clj-json and 24 | clojure-json, with additional features like Date/UUID/Set/Symbol 25 | encoding and SMILE support. 26 | 27 | [Clojure code with docs](http://dakrone.github.com/cheshire/) 28 | 29 | [![Continuous Integration status](https://secure.travis-ci.org/dakrone/cheshire.png)](http://travis-ci.org/dakrone/cheshire) 30 | 31 | ## Why? 32 | 33 | clojure-json had really nice features (custom encoders), but was slow; 34 | clj-json had no features, but was fast. Cheshire encodes JSON fast, 35 | with added support for more types and the ability to use custom 36 | encoders. 37 | 38 | ## Usage 39 | 40 | ```clojure 41 | [cheshire "5.5.0"] 42 | 43 | ;; Cheshire v5.5.0 uses Jackson 2.5.3 44 | 45 | ;; In your ns statement: 46 | (ns my.ns 47 | (:require [cheshire.core :refer :all])) 48 | ``` 49 | 50 | ### Encoding 51 | 52 | ```clojure 53 | ;; generate some json 54 | (generate-string {:foo "bar" :baz 5}) 55 | 56 | ;; write some json to a stream 57 | (generate-stream {:foo "bar" :baz 5} (clojure.java.io/writer "/tmp/foo")) 58 | 59 | ;; generate some SMILE 60 | (generate-smile {:foo "bar" :baz 5}) 61 | 62 | ;; generate some JSON with Dates 63 | ;; the Date will be encoded as a string using 64 | ;; the default date format: yyyy-MM-dd'T'HH:mm:ss'Z' 65 | (generate-string {:foo "bar" :baz (java.util.Date. 0)}) 66 | 67 | ;; generate some JSON with Dates with custom Date encoding 68 | (generate-string {:baz (java.util.Date. 0)} {:date-format "yyyy-MM-dd"}) 69 | 70 | ;; generate some JSON with pretty formatting 71 | (generate-string {:foo "bar" :baz {:eggplant [1 2 3]}} {:pretty true}) 72 | ;; { 73 | ;; "foo" : "bar", 74 | ;; "baz" : { 75 | ;; "eggplant" : [ 1, 2, 3 ] 76 | ;; } 77 | ;; } 78 | 79 | ;; generate JSON escaping UTF-8 80 | (generate-string {:foo "It costs £100"} {:escape-non-ascii true}) 81 | ;; => "{\"foo\":\"It costs \\u00A3100\"}" 82 | 83 | ;; generate JSON and munge keys with a custom function 84 | (generate-string {:foo "bar"} {:key-fn (fn [k] (.toUpperCase (name k)))}) 85 | ;; => "{\"FOO\":\"bar\"}" 86 | ``` 87 | 88 | In the event encoding fails, Cheshire will throw a JsonGenerationException. 89 | 90 | ### Decoding 91 | 92 | ```clojure 93 | ;; parse some json 94 | (parse-string "{\"foo\":\"bar\"}") 95 | ;; => {"foo" "bar"} 96 | 97 | ;; parse some json and get keywords back 98 | (parse-string "{\"foo\":\"bar\"}" true) 99 | ;; => {:foo "bar"} 100 | 101 | ;; parse some json and munge keywords with a custom function 102 | (parse-string "{\"foo\":\"bar\"}" (fn [k] (keyword (.toUpperCase k)))) 103 | ;; => {:FOO "bar"} 104 | 105 | 106 | ;; parse some SMILE (keywords option also supported) 107 | (parse-smile ) 108 | 109 | ;; parse a stream (keywords option also supported) 110 | (parse-stream (clojure.java.io/reader "/tmp/foo")) 111 | 112 | ;; parse a stream lazily (keywords option also supported) 113 | (parsed-seq (clojure.java.io/reader "/tmp/foo")) 114 | 115 | ;; parse a SMILE stream lazily (keywords option also supported) 116 | (parsed-smile-seq (clojure.java.io/reader "/tmp/foo")) 117 | ``` 118 | 119 | In 2.0.4 and up, Cheshire allows passing in a 120 | function to specify what kind of types to return, like so: 121 | 122 | ```clojure 123 | ;; In this example a function that checks for a certain key 124 | (decode "{\"myarray\":[2,3,3,2],\"myset\":[1,2,2,1]}" true 125 | (fn [field-name] 126 | (if (= field-name "myset") 127 | #{} 128 | []))) 129 | ;; => {:myarray [2 3 3 2], :myset #{1 2}} 130 | ``` 131 | The type must be "transient-able", so use either #{} or [] 132 | 133 | 134 | ### Custom Encoders 135 | 136 | Custom encoding is supported from 2.0.0 and up, if you encounter a 137 | bug, please open a github issue. From 5.0.0 onwards, custom encoding 138 | has been moved to be part of the core namespace (not requiring a 139 | namespace change) 140 | 141 | ```clojure 142 | ;; Custom encoders allow you to swap out the api for the fast 143 | ;; encoder with one that is slightly slower, but allows custom 144 | ;; things to be encoded: 145 | (ns myns 146 | (:require [cheshire.core :refer :all] 147 | [cheshire.generate :refer [add-encoder encode-str remove-encoder]])) 148 | 149 | ;; First, add a custom encoder for a class: 150 | (add-encoder java.awt.Color 151 | (fn [c jsonGenerator] 152 | (.writeString jsonGenerator (str c)))) 153 | 154 | ;; There are also helpers for common encoding actions: 155 | (add-encoder java.net.URL encode-str) 156 | 157 | ;; List of common encoders that can be used: (see generate.clj) 158 | ;; encode-nil 159 | ;; encode-number 160 | ;; encode-seq 161 | ;; encode-date 162 | ;; encode-bool 163 | ;; encode-named 164 | ;; encode-map 165 | ;; encode-symbol 166 | ;; encode-ratio 167 | 168 | ;; Then you can use encode from the custom namespace as normal 169 | (encode (java.awt.Color. 1 2 3)) 170 | ;; => "java.awt.Color[r=1,g=2,b=3]" 171 | 172 | ;; Custom encoders can also be removed: 173 | (remove-encoder java.awt.Color) 174 | 175 | ;; Decoding remains the same, you are responsible for doing custom decoding. 176 | ``` 177 | 178 |

NOTE: `cheshire.custom` has been deprecated in version 5.0.0

179 | 180 | Custom and Core encoding have been combined in Cheshire 5.0.0, so 181 | there is no longer any need to require a different namespace depending 182 | on what you would like to use. 183 | 184 | ### Aliases 185 | 186 | There are also a few aliases for commonly used functions: 187 | 188 | encode -> generate-string 189 | encode-stream -> generate-stream 190 | encode-smile -> generate-smile 191 | decode -> parse-string 192 | decode-stream -> parse-stream 193 | decode-smile -> parse-smile 194 | 195 | ## Features 196 | Cheshire supports encoding standard clojure datastructures, with a few 197 | additions. 198 | 199 | Cheshire encoding supports: 200 | 201 | ### Clojure data structures 202 | - strings 203 | - lists 204 | - vectors 205 | - sets 206 | - maps 207 | - symbols 208 | - booleans 209 | - keywords (qualified and unqualified) 210 | - numbers (Integer, Long, BigInteger, BigInt, Double, Float, Ratio, 211 | Short, Byte, primatives) 212 | - clojure.lang.PersistentQueue 213 | 214 | ### Java classes 215 | - Date 216 | - UUID 217 | - java.sql.Timestamp 218 | - any java.util.Set 219 | - any java.util.Map 220 | - any java.util.List 221 | 222 | ### Custom class encoding while still being fast 223 | 224 | ### Also supports 225 | - Stream encoding/decoding 226 | - Lazy decoding 227 | - Pretty-printing JSON generation 228 | - Unicode escaping 229 | - Custom keyword coercion 230 | - Pretty-printing JSON generation 231 | - Unicode escaping 232 | - Arbitrary precision for decoded values: 233 | 234 | Cheshire will automatically use a BigInteger if needed for 235 | non-floating-point numbers, however, for floating-point numbers, 236 | Doubles will be used unless the `*use-bigdecimals?*` symbol is bound 237 | to true: 238 | 239 | ```clojure 240 | (ns foo.bar 241 | (require [cheshire.core :as json] 242 | [cheshire.parse :as parse])) 243 | 244 | (json/decode "111111111111111111111111111111111.111111111111111111111111111111111111") 245 | ;; => 1.1111111111111112E32 (a Double) 246 | 247 | (binding [parse/*use-bigdecimals?* true] 248 | (json/decode "111111111111111111111111111111111.111111111111111111111111111111111111")) 249 | ;; => 111111111111111111111111111111111.111111111111111111111111111111111111M (a BigDecimal) 250 | ``` 251 | 252 | - Replacing default encoders for builtin types 253 | - [SMILE encoding/decoding](http://wiki.fasterxml.com/SmileFormatSpec) 254 | 255 | ## Change Log 256 | 257 | [Change log](https://github.com/dakrone/cheshire/blob/master/ChangeLog.md) is available on GitHub. 258 | 259 | ## Speed 260 | 261 | Cheshire is about twice as fast as data.json. 262 | 263 | Check out the benchmarks in `cheshire.test.benchmark`; or run `lein 264 | benchmark`. If you have scenarios where Cheshire is not performing as 265 | well as expected (compared to a different library), please let me 266 | know. 267 | 268 | ## Experimental things 269 | 270 | In the `cheshire.experimental` namespace: 271 | 272 | ``` 273 | $ echo "Hi. \"THIS\" is a string.\\yep." > /tmp/foo 274 | 275 | $ lein repl 276 | user> (use 'cheshire.experimental) 277 | nil 278 | user> (use 'clojure.java.io) 279 | nil 280 | user> (println (slurp (encode-large-field-in-map {:id "10" 281 | :things [1 2 3] 282 | :body "I'll be removed"} 283 | :body 284 | (input-stream (file "/tmp/foo"))))) 285 | {"things":[1,2,3],"id":"10","body":"Hi. \"THIS\" is a string.\\yep.\n"} 286 | nil 287 | ``` 288 | 289 | `encode-large-field-in-map` is used for streamy JSON encoding where 290 | you want to JSON encode a map, but don't want the map in memory all at 291 | once (it returns a stream). Check out the docstring for full usage. 292 | 293 | It's experimental, like the name says. Based on [Tigris](http://github.com/dakrone/tigris). 294 | 295 | ## Advanced customization for factories 296 | See 297 | [this](http://fasterxml.github.com/jackson-core/javadoc/2.1.1/com/fasterxml/jackson/core/JsonFactory.Feature.html) 298 | and 299 | [this](http://fasterxml.github.com/jackson-core/javadoc/2.1.1/com/fasterxml/jackson/core/JsonParser.Feature.html) 300 | for a list of features that can be customized if desired. A custom 301 | factory can be used like so: 302 | 303 | ```clojure 304 | (ns myns 305 | (:require [cheshire.core :as core] 306 | [cheshire.factory :as factory])) 307 | 308 | (binding [factory/*json-factory* (factory/make-json-factory 309 | {:allow-non-numeric-numbers true})] 310 | (json/decode "{\"foo\":NaN}" true)))))) 311 | ``` 312 | 313 | See the `default-factory-options` map in 314 | [factory.clj](https://github.com/dakrone/cheshire/blob/master/src/cheshire/factory.clj) 315 | for a full list of configurable options. Smile factories can also be 316 | created, and factories work exactly the same with custom encoding. 317 | 318 | ## Future Ideas/TODOs 319 | - move away from using Java entirely, use Protocols for the 320 | custom encoder (see custom.clj) 321 | - allow custom encoders (see custom.clj) 322 | - figure out a way to encode namespace-qualified keywords 323 | - look into overriding the default encoding handlers with custom handlers 324 | - better handling when java numbers overflow ECMAScript's numbers 325 | (-2^31 to (2^31 - 1)) 326 | - handle encoding java.sql.Timestamp the same as 327 | java.util.Date 328 | - add benchmarking 329 | - get criterium benchmarking ignored for 1.2.1 profile 330 | - look into faster exception handling by pre-allocating an exception 331 | object instead of creating one on-the-fly (maybe ask Steve?) 332 | - make it as fast as possible (ongoing) 333 | 334 | ## License 335 | Release under the MIT license. See LICENSE for the full license. 336 | 337 | ## Thanks 338 | Thanks go to Mark McGranaghan for clj-json and Jim Duey for the name 339 | suggestion. :) 340 | -------------------------------------------------------------------------------- /src/cheshire/core.clj: -------------------------------------------------------------------------------- 1 | (ns cheshire.core 2 | "Main encoding and decoding namespace." 3 | (:require [cheshire.factory :as factory] 4 | [cheshire.generate :as gen] 5 | [cheshire.generate-seq :as gen-seq] 6 | [cheshire.parse :as parse]) 7 | (:import (com.fasterxml.jackson.core JsonParser JsonFactory 8 | JsonGenerator 9 | JsonGenerator$Feature) 10 | (com.fasterxml.jackson.dataformat.smile SmileFactory) 11 | (java.io StringWriter StringReader BufferedReader BufferedWriter 12 | ByteArrayOutputStream OutputStream Reader Writer))) 13 | 14 | ;; Generators 15 | (defn ^String generate-string 16 | "Returns a JSON-encoding String for the given Clojure object. Takes an 17 | optional date format string that Date objects will be encoded with. 18 | 19 | The default date format (in UTC) is: yyyy-MM-dd'T'HH:mm:ss'Z'" 20 | ([obj] 21 | (generate-string obj nil)) 22 | ([obj opt-map] 23 | (let [sw (StringWriter.) 24 | generator (.createGenerator 25 | ^JsonFactory (or factory/*json-factory* 26 | factory/json-factory) 27 | ^Writer sw)] 28 | (when (:pretty opt-map) 29 | (.useDefaultPrettyPrinter generator)) 30 | (when (:escape-non-ascii opt-map) 31 | (.enable generator JsonGenerator$Feature/ESCAPE_NON_ASCII)) 32 | (gen/generate generator obj 33 | (or (:date-format opt-map) factory/default-date-format) 34 | (:ex opt-map) 35 | (:key-fn opt-map)) 36 | (.flush generator) 37 | (.toString sw)))) 38 | 39 | (defn ^String generate-stream 40 | "Returns a BufferedWriter for the given Clojure object with the. 41 | JSON-encoded data written to the writer. Takes an optional date 42 | format string that Date objects will be encoded with. 43 | 44 | The default date format (in UTC) is: yyyy-MM-dd'T'HH:mm:ss'Z'" 45 | ([obj ^BufferedWriter writer] 46 | (generate-stream obj writer nil)) 47 | ([obj ^BufferedWriter writer opt-map] 48 | (let [generator (.createGenerator 49 | ^JsonFactory (or factory/*json-factory* 50 | factory/json-factory) 51 | ^Writer writer)] 52 | (when (:pretty opt-map) 53 | (.useDefaultPrettyPrinter generator)) 54 | (when (:escape-non-ascii opt-map) 55 | (.enable generator JsonGenerator$Feature/ESCAPE_NON_ASCII)) 56 | (gen/generate generator obj (or (:date-format opt-map) 57 | factory/default-date-format) 58 | (:ex opt-map) 59 | (:key-fn opt-map)) 60 | (.flush generator) 61 | writer))) 62 | 63 | (defn create-generator [writer] 64 | "Returns JsonGenerator for given writer." 65 | (.createGenerator 66 | ^JsonFactory (or factory/*json-factory* 67 | factory/json-factory) 68 | ^Writer writer)) 69 | 70 | (def ^:dynamic ^JsonGenerator *generator*) 71 | (def ^:dynamic *opt-map*) 72 | 73 | (defmacro with-writer [[writer opt-map] & body] 74 | "Start writing for series objects using the same json generator. 75 | Takes writer and options map as arguments. 76 | Expects it's body as sequence of write calls. 77 | Returns a given writer." 78 | `(let [c-wr# ~writer] 79 | (binding [*generator* (create-generator c-wr#) 80 | *opt-map* ~opt-map] 81 | ~@body 82 | (.flush *generator*) 83 | c-wr#))) 84 | 85 | (defn write 86 | "Write given Clojure object as a piece of data within with-writer. 87 | List of wholeness acceptable values: 88 | - no value - the same as :all 89 | - :all - write object in a regular way with start and end borders 90 | - :start - write object with start border only 91 | - :start-inner - write object and it's inner object with start border only 92 | - :end - write object with end border only." 93 | ([obj] (write obj nil)) 94 | ([obj wholeness] 95 | (gen-seq/generate *generator* obj (or (:date-format *opt-map*) 96 | factory/default-date-format) 97 | (:ex *opt-map*) 98 | (:key-fn *opt-map*) 99 | :wholeness wholeness))) 100 | 101 | (defn generate-smile 102 | "Returns a SMILE-encoded byte-array for the given Clojure object. 103 | Takes an optional date format string that Date objects will be encoded with. 104 | 105 | The default date format (in UTC) is: yyyy-MM-dd'T'HH:mm:ss'Z'" 106 | ([obj] 107 | (generate-smile obj nil)) 108 | ([obj opt-map] 109 | (let [baos (ByteArrayOutputStream.) 110 | generator (.createGenerator ^SmileFactory 111 | (or factory/*smile-factory* 112 | factory/smile-factory) 113 | ^OutputStream baos)] 114 | (gen/generate generator obj (or (:date-format opt-map) 115 | factory/default-date-format) 116 | (:ex opt-map) 117 | (:key-fn opt-map)) 118 | (.flush generator) 119 | (.toByteArray baos)))) 120 | 121 | (defn generate-cbor 122 | "Returns a CBOR-encoded byte-array for the given Clojure object. 123 | Takes an optional date format string that Date objects will be encoded with. 124 | 125 | The default date format (in UTC) is: yyyy-MM-dd'T'HH:mm:ss'Z'" 126 | ([obj] 127 | (generate-cbor obj nil)) 128 | ([obj opt-map] 129 | (let [baos (ByteArrayOutputStream.) 130 | generator (.createGenerator ^CBORFactory 131 | (or factory/*cbor-factory* 132 | factory/cbor-factory) 133 | ^OutputStream baos)] 134 | (gen/generate generator obj (or (:date-format opt-map) 135 | factory/default-date-format) 136 | (:ex opt-map) 137 | (:key-fn opt-map)) 138 | (.flush generator) 139 | (.toByteArray baos)))) 140 | 141 | ;; Parsers 142 | (defn parse-string 143 | "Returns the Clojure object corresponding to the given JSON-encoded string. 144 | An optional key-fn argument can be either true (to coerce keys to keywords), 145 | false to leave them as strings, or a function to provide custom coercion. 146 | 147 | The array-coerce-fn is an optional function taking the name of an array field, 148 | and returning the collection to be used for array values. 149 | 150 | If the top-level object is an array, it will be parsed lazily (use 151 | `parse-strict' if strict parsing is required for top-level arrays." 152 | ([string] (parse-string string nil nil)) 153 | ([string key-fn] (parse-string string key-fn nil)) 154 | ([^String string key-fn array-coerce-fn] 155 | (when string 156 | (parse/parse 157 | (.createParser ^JsonFactory (or factory/*json-factory* 158 | factory/json-factory) 159 | ^Reader (StringReader. string)) 160 | key-fn nil array-coerce-fn)))) 161 | 162 | ;; Parsing strictly 163 | (defn parse-string-strict 164 | "Returns the Clojure object corresponding to the given JSON-encoded string. 165 | An optional key-fn argument can be either true (to coerce keys to keywords), 166 | false to leave them as strings, or a function to provide custom coercion. 167 | 168 | The array-coerce-fn is an optional function taking the name of an array field, 169 | and returning the collection to be used for array values. 170 | 171 | Does not lazily parse top-level arrays." 172 | ([string] (parse-string-strict string nil nil)) 173 | ([string key-fn] (parse-string-strict string key-fn nil)) 174 | ([^String string key-fn array-coerce-fn] 175 | (when string 176 | (parse/parse-strict 177 | (.createParser ^JsonFactory (or factory/*json-factory* 178 | factory/json-factory) 179 | ^Writer (StringReader. string)) 180 | key-fn nil array-coerce-fn)))) 181 | 182 | (defn parse-stream 183 | "Returns the Clojure object corresponding to the given reader, reader must 184 | implement BufferedReader. An optional key-fn argument can be either true (to 185 | coerce keys to keywords),false to leave them as strings, or a function to 186 | provide custom coercion. 187 | 188 | The array-coerce-fn is an optional function taking the name of an array field, 189 | and returning the collection to be used for array values. 190 | 191 | If the top-level object is an array, it will be parsed lazily (use 192 | `parse-strict' if strict parsing is required for top-level arrays. 193 | 194 | If multiple objects (enclosed in a top-level `{}' need to be parsed lazily, 195 | see parsed-seq." 196 | ([rdr] (parse-stream rdr nil nil)) 197 | ([rdr key-fn] (parse-stream rdr key-fn nil)) 198 | ([^BufferedReader rdr key-fn array-coerce-fn] 199 | (when rdr 200 | (parse/parse 201 | (.createParser ^JsonFactory (or factory/*json-factory* 202 | factory/json-factory) 203 | ^Reader rdr) 204 | key-fn nil array-coerce-fn)))) 205 | 206 | (defn parse-smile 207 | "Returns the Clojure object corresponding to the given SMILE-encoded bytes. 208 | An optional key-fn argument can be either true (to coerce keys to keywords), 209 | false to leave them as strings, or a function to provide custom coercion. 210 | 211 | The array-coerce-fn is an optional function taking the name of an array field, 212 | and returning the collection to be used for array values." 213 | ([bytes] (parse-smile bytes nil nil)) 214 | ([bytes key-fn] (parse-smile bytes key-fn nil)) 215 | ([^bytes bytes key-fn array-coerce-fn] 216 | (when bytes 217 | (parse/parse 218 | (.createParser ^SmileFactory (or factory/*smile-factory* 219 | factory/smile-factory) bytes) 220 | key-fn nil array-coerce-fn)))) 221 | 222 | (defn parse-cbor 223 | "Returns the Clojure object corresponding to the given CBOR-encoded bytes. 224 | An optional key-fn argument can be either true (to coerce keys to keywords), 225 | false to leave them as strings, or a function to provide custom coercion. 226 | 227 | The array-coerce-fn is an optional function taking the name of an array field, 228 | and returning the collection to be used for array values." 229 | ([bytes] (parse-cbor bytes nil nil)) 230 | ([bytes key-fn] (parse-cbor bytes key-fn nil)) 231 | ([^bytes bytes key-fn array-coerce-fn] 232 | (when bytes 233 | (parse/parse 234 | (.createParser ^CBORFactory (or factory/*cbor-factory* 235 | factory/cbor-factory) bytes) 236 | key-fn nil array-coerce-fn)))) 237 | 238 | (def ^{:doc "Object used to determine end of lazy parsing attempt."} 239 | eof (Object.)) 240 | 241 | ;; Lazy parsers 242 | (defn- parsed-seq* 243 | "Internal lazy-seq parser" 244 | [^JsonParser parser key-fn array-coerce-fn] 245 | (lazy-seq 246 | (let [elem (parse/parse-strict parser key-fn eof array-coerce-fn)] 247 | (when-not (identical? elem eof) 248 | (cons elem (parsed-seq* parser key-fn array-coerce-fn)))))) 249 | 250 | (defn parsed-seq 251 | "Returns a lazy seq of Clojure objects corresponding to the JSON read from 252 | the given reader. The seq continues until the end of the reader is reached. 253 | 254 | The array-coerce-fn is an optional function taking the name of an array field, 255 | and returning the collection to be used for array values. 256 | If non-laziness is needed, see parse-stream." 257 | ([reader] (parsed-seq reader nil nil)) 258 | ([reader key-fn] (parsed-seq reader key-fn nil)) 259 | ([^BufferedReader reader key-fn array-coerce-fn] 260 | (when reader 261 | (parsed-seq* (.createParser ^JsonFactory 262 | (or factory/*json-factory* 263 | factory/json-factory) 264 | ^Reader reader) 265 | key-fn array-coerce-fn)))) 266 | 267 | (defn parsed-smile-seq 268 | "Returns a lazy seq of Clojure objects corresponding to the SMILE read from 269 | the given reader. The seq continues until the end of the reader is reached. 270 | 271 | The array-coerce-fn is an optional function taking the name of an array field, 272 | and returning the collection to be used for array values." 273 | ([reader] (parsed-smile-seq reader nil nil)) 274 | ([reader key-fn] (parsed-smile-seq reader key-fn nil)) 275 | ([^BufferedReader reader key-fn array-coerce-fn] 276 | (when reader 277 | (parsed-seq* (.createParser ^SmileFactory 278 | (or factory/*smile-factory* 279 | factory/smile-factory) 280 | ^Reader reader) 281 | key-fn array-coerce-fn)))) 282 | 283 | ;; aliases for clojure-json users 284 | (def encode "Alias to generate-string for clojure-json users" generate-string) 285 | (def encode-stream "Alias to generate-stream for clojure-json users" generate-stream) 286 | (def encode-smile "Alias to generate-smile for clojure-json users" generate-smile) 287 | (def decode "Alias to parse-string for clojure-json users" parse-string) 288 | (def decode-strict "Alias to parse-string-strict for clojure-json users" parse-string-strict) 289 | (def decode-stream "Alias to parse-stream for clojure-json users" parse-stream) 290 | (def decode-smile "Alias to parse-smile for clojure-json users" parse-smile) 291 | -------------------------------------------------------------------------------- /test/cheshire/test/core.clj: -------------------------------------------------------------------------------- 1 | (ns cheshire.test.core 2 | (:use [clojure.test] 3 | [clojure.java.io :only [file reader]]) 4 | (:require [cheshire.core :as json] 5 | [cheshire.generate :as gen] 6 | [cheshire.factory :as fact] 7 | [cheshire.parse :as parse]) 8 | (:import (com.fasterxml.jackson.core JsonGenerationException) 9 | (java.io FileInputStream StringReader StringWriter 10 | BufferedReader BufferedWriter) 11 | (java.sql Timestamp) 12 | (java.util Date UUID))) 13 | 14 | (def test-obj {"int" 3 "long" (long -2147483647) "boolean" true 15 | "LongObj" (Long/parseLong "2147483647") "double" 1.23 16 | "nil" nil "string" "string" "vec" [1 2 3] "map" {"a" "b"} 17 | "list" (list "a" "b") "short" (short 21) "byte" (byte 3)}) 18 | 19 | (deftest t-ratio 20 | (let [n 1/2] 21 | (is (= (double n) (:num (json/decode (json/encode {:num n}) true)))))) 22 | 23 | (deftest t-long-wrap-around 24 | (is (= 2147483648 (json/decode (json/encode 2147483648))))) 25 | 26 | (deftest t-bigint 27 | (let [n 9223372036854775808] 28 | (is (= n (:num (json/decode (json/encode {:num n}) true)))))) 29 | 30 | (deftest t-biginteger 31 | (let [n (BigInteger. "42")] 32 | (is (= n (:num (json/decode (json/encode {:num n}) true)))))) 33 | 34 | (deftest t-bigdecimal 35 | (let [n (BigDecimal. "42.5")] 36 | (is (= (.doubleValue n) (:num (json/decode (json/encode {:num n}) true)))) 37 | (binding [parse/*use-bigdecimals?* true] 38 | (is (= n (:num (json/decode (json/encode {:num n}) true))))))) 39 | 40 | (deftest test-string-round-trip 41 | (is (= test-obj (json/decode (json/encode test-obj))))) 42 | 43 | (deftest test-generate-accepts-float 44 | (is (= "3.14" (json/encode 3.14)))) 45 | 46 | (deftest test-keyword-encode 47 | (is (= {"key" "val"} 48 | (json/decode (json/encode {:key "val"}))))) 49 | 50 | (deftest test-generate-set 51 | (is (= {"set" ["a" "b"]} 52 | (json/decode (json/encode {"set" #{"a" "b"}}))))) 53 | 54 | (deftest test-generate-empty-set 55 | (is (= {"set" []} 56 | (json/decode (json/encode {"set" #{}}))))) 57 | 58 | (deftest test-generate-empty-array 59 | (is (= {"array" []} 60 | (json/decode (json/encode {"array" []}))))) 61 | 62 | (deftest test-key-coercion 63 | (is (= {"foo" "bar" "1" "bat" "2" "bang" "3" "biz"} 64 | (json/decode 65 | (json/encode 66 | {:foo "bar" 1 "bat" (long 2) "bang" (bigint 3) "biz"}))))) 67 | 68 | (deftest test-keywords 69 | (is (= {:foo "bar" :bat 1} 70 | (json/decode (json/encode {:foo "bar" :bat 1}) true)))) 71 | 72 | (deftest test-symbols 73 | (is (= {"foo" "clojure.core/map"} 74 | (json/decode (json/encode {"foo" 'clojure.core/map}))))) 75 | 76 | (deftest test-accepts-java-map 77 | (is (= {"foo" 1} 78 | (json/decode 79 | (json/encode (doto (java.util.HashMap.) (.put "foo" 1))))))) 80 | 81 | (deftest test-accepts-java-list 82 | (is (= [1 2 3] 83 | (json/decode (json/encode (doto (java.util.ArrayList. 3) 84 | (.add 1) 85 | (.add 2) 86 | (.add 3))))))) 87 | 88 | (deftest test-accepts-java-set 89 | (is (= {"set" [1 2 3]} 90 | (json/decode (json/encode {"set" (doto (java.util.HashSet. 3) 91 | (.add 1) 92 | (.add 2) 93 | (.add 3))}))))) 94 | 95 | (deftest test-accepts-empty-java-set 96 | (is (= {"set" []} 97 | (json/decode (json/encode {"set" (java.util.HashSet. 3)}))))) 98 | 99 | (deftest test-nil 100 | (is (nil? (json/decode nil true)))) 101 | 102 | (deftest test-parsed-seq 103 | (let [br (BufferedReader. (StringReader. "1\n2\n3\n"))] 104 | (is (= (list 1 2 3) (json/parsed-seq br))))) 105 | 106 | (deftest test-smile-round-trip 107 | (is (= test-obj (json/parse-smile (json/generate-smile test-obj))))) 108 | 109 | (deftest test-aliases 110 | (is (= {"foo" "bar" "1" "bat" "2" "bang" "3" "biz"} 111 | (json/decode 112 | (json/encode 113 | {:foo "bar" 1 "bat" (long 2) "bang" (bigint 3) "biz"}))))) 114 | 115 | (deftest test-date 116 | (is (= {"foo" "1970-01-01T00:00:00Z"} 117 | (json/decode (json/encode {:foo (Date. (long 0))})))) 118 | (is (= {"foo" "1970-01-01"} 119 | (json/decode (json/encode {:foo (Date. (long 0))} 120 | {:date-format "yyyy-MM-dd"}))) 121 | "encode with given date format")) 122 | 123 | (deftest test-sql-timestamp 124 | (is (= {"foo" "1970-01-01T00:00:00Z"} 125 | (json/decode (json/encode {:foo (Timestamp. (long 0))})))) 126 | (is (= {"foo" "1970-01-01"} 127 | (json/decode (json/encode {:foo (Timestamp. (long 0))} 128 | {:date-format "yyyy-MM-dd"}))) 129 | "encode with given date format")) 130 | 131 | (deftest test-uuid 132 | (let [id (UUID/randomUUID) 133 | id-str (str id)] 134 | (is (= {"foo" id-str} (json/decode (json/encode {:foo id})))))) 135 | 136 | (deftest test-char-literal 137 | (is (= "{\"foo\":\"a\"}" (json/encode {:foo \a})))) 138 | 139 | (deftest test-streams 140 | (is (= {"foo" "bar"} 141 | (json/parse-stream 142 | (BufferedReader. (StringReader. "{\"foo\":\"bar\"}\n"))))) 143 | (let [sw (StringWriter.) 144 | bw (BufferedWriter. sw)] 145 | (json/generate-stream {"foo" "bar"} bw) 146 | (is (= "{\"foo\":\"bar\"}" (.toString sw)))) 147 | (is (= {(keyword "foo baz") "bar"} 148 | (with-open [rdr (StringReader. "{\"foo baz\":\"bar\"}\n")] 149 | (json/parse-stream rdr true))))) 150 | 151 | (deftest serial-writing 152 | (is (= "[\"foo\",\"bar\"]" 153 | (.toString 154 | (json/with-writer [(StringWriter.) nil] 155 | (json/write [] :start) 156 | (json/write "foo") 157 | (json/write "bar") 158 | (json/write [] :end))))) 159 | (is (= "[1,[2,3],4]" 160 | (.toString 161 | (json/with-writer [(StringWriter.) nil] 162 | (json/write [1 [2]] :start-inner) 163 | (json/write 3) 164 | (json/write [] :end) 165 | (json/write 4) 166 | (json/write [] :end))))) 167 | (is (= "{\"a\":1,\"b\":2,\"c\":3}" 168 | (.toString 169 | (json/with-writer [(StringWriter.) nil] 170 | (json/write {:a 1} :start) 171 | (json/write {:b 2} :bare) 172 | (json/write {:c 3} :end))))) 173 | (is (= (str "[\"start\",\"continue\",[\"implicitly-nested\"]," 174 | "[\"explicitly-nested\"],\"flatten\",\"end\"]") 175 | (.toString 176 | (json/with-writer [(StringWriter.) nil] 177 | (json/write ["start"] :start) 178 | (json/write "continue") 179 | (json/write ["implicitly-nested"]) 180 | (json/write ["explicitly-nested"] :all) 181 | (json/write ["flatten"] :bare) 182 | (json/write ["end"] :end))))) 183 | (is (= "{\"head\":\"head info\",\"data\":[1,2,3],\"tail\":\"tail info\"}" 184 | (.toString 185 | (json/with-writer [(StringWriter.) nil] 186 | (json/write {:head "head info" :data []} :start-inner) 187 | (json/write 1) 188 | (json/write 2) 189 | (json/write 3) 190 | (json/write [] :end) 191 | (json/write {:tail "tail info"} :end)))))) 192 | 193 | (deftest test-multiple-objs-in-file 194 | (is (= {"one" 1, "foo" "bar"} 195 | (first (json/parsed-seq (reader "test/multi.json"))))) 196 | (is (= {"two" 2, "foo" "bar"} 197 | (second (json/parsed-seq (reader "test/multi.json"))))) 198 | (with-open [s (FileInputStream. (file "test/multi.json"))] 199 | (let [r (reader s)] 200 | (is (= [{"one" 1, "foo" "bar"} {"two" 2, "foo" "bar"}] 201 | (json/parsed-seq r)))))) 202 | 203 | (deftest test-jsondotorg-pass1 204 | (let [string (slurp "test/pass1.json") 205 | decoded-json (json/decode string) 206 | encoded-json (json/encode decoded-json) 207 | re-decoded-json (json/decode encoded-json)] 208 | (is (= decoded-json re-decoded-json)))) 209 | 210 | (deftest test-namespaced-keywords 211 | (is (= "{\"foo\":\"user/bar\"}" 212 | (json/encode {:foo :user/bar}))) 213 | (is (= {:foo/bar "baz/eggplant"} 214 | (json/decode (json/encode {:foo/bar :baz/eggplant}) true)))) 215 | 216 | (deftest test-array-coerce-fn 217 | (is (= {"set" #{"a" "b"} "array" ["a" "b"] "map" {"a" 1}} 218 | (json/decode 219 | (json/encode {"set" #{"a" "b"} "array" ["a" "b"] "map" {"a" 1}}) false 220 | (fn [field-name] (if (= "set" field-name) #{} [])))))) 221 | 222 | (deftest t-symbol-encoding-for-non-resolvable-symbols 223 | (is (= "{\"bar\":\"clojure.core/pam\",\"foo\":\"clojure.core/map\"}" 224 | (json/encode (sorted-map :foo 'clojure.core/map :bar 'clojure.core/pam)))) 225 | (is (= "{\"bar\":\"clojure.core/pam\",\"foo\":\"foo.bar/baz\"}" 226 | (json/encode (sorted-map :foo 'foo.bar/baz :bar 'clojure.core/pam))))) 227 | 228 | (deftest t-bindable-factories 229 | (binding [fact/*json-factory* (fact/make-json-factory 230 | {:allow-non-numeric-numbers true})] 231 | (is (= (type Double/NaN) 232 | (type (:foo (json/decode "{\"foo\":NaN}" true))))))) 233 | 234 | (deftest t-persistent-queue 235 | (let [q (conj (clojure.lang.PersistentQueue/EMPTY) 1 2 3)] 236 | (is (= q (json/decode (json/encode q)))))) 237 | 238 | (deftest t-pretty-print 239 | (is (= (str "{\n \"bar\" : [ {\n \"baz\" : 2\n }, " 240 | "\"quux\", [ 1, 2, 3 ] ],\n \"foo\" : 1\n}") 241 | (json/encode (sorted-map :foo 1 :bar [{:baz 2} :quux [1 2 3]]) 242 | {:pretty true})))) 243 | 244 | (deftest t-unicode-escaping 245 | (is (= "{\"foo\":\"It costs \\u00A3100\"}" 246 | (json/encode {:foo "It costs £100"} {:escape-non-ascii true})))) 247 | 248 | (deftest t-custom-keyword-fn 249 | (is (= {:FOO "bar"} (json/decode "{\"foo\": \"bar\"}" 250 | (fn [k] (keyword (.toUpperCase k)))))) 251 | (is (= {"foo" "bar"} (json/decode "{\"foo\": \"bar\"}" nil))) 252 | (is (= {"foo" "bar"} (json/decode "{\"foo\": \"bar\"}" false))) 253 | (is (= {:foo "bar"} (json/decode "{\"foo\": \"bar\"}" true)))) 254 | 255 | (deftest t-custom-encode-key-fn 256 | (is (= "{\"FOO\":\"bar\"}" 257 | (json/encode {:foo :bar} 258 | {:key-fn (fn [k] (.toUpperCase (name k)))})))) 259 | 260 | (deftest test-add-remove-encoder 261 | (gen/remove-encoder java.net.URL) 262 | (gen/add-encoder java.net.URL gen/encode-str) 263 | (is (= "\"http://foo.com\"" 264 | (json/encode (java.net.URL. "http://foo.com")))) 265 | (gen/remove-encoder java.net.URL) 266 | (is (thrown? JsonGenerationException 267 | (json/encode (java.net.URL. "http://foo.com"))))) 268 | 269 | (defprotocol TestP 270 | (foo [this] "foo method")) 271 | 272 | (defrecord TestR [state]) 273 | 274 | (extend TestR 275 | TestP 276 | {:foo (constantly "bar")}) 277 | 278 | (deftest t-custom-protocol-encoder 279 | (let [rec (TestR. :quux)] 280 | (is (= {:state "quux"} (json/decode (json/encode rec) true))) 281 | (gen/add-encoder cheshire.test.core.TestR 282 | (fn [obj jg] 283 | (.writeString jg (foo obj)))) 284 | (is (= "bar" (json/decode (json/encode rec)))) 285 | (gen/remove-encoder cheshire.test.core.TestR) 286 | (is (= {:state "quux"} (json/decode (json/encode rec) true))))) 287 | 288 | (defprotocol CTestP 289 | (thing [this] "thing method")) 290 | (defrecord CTestR [state]) 291 | (extend CTestR 292 | CTestP 293 | {:thing (constantly "thing")}) 294 | 295 | (deftest t-custom-helpers 296 | (let [thing (CTestR. :state) 297 | remove #(gen/remove-encoder CTestR)] 298 | (gen/add-encoder CTestR (fn [obj jg] (gen/encode-nil nil jg))) 299 | (is (= nil (json/decode (json/encode thing) true))) 300 | (remove) 301 | (gen/add-encoder CTestR (fn [obj jg] (gen/encode-str "foo" jg))) 302 | (is (= "foo" (json/decode (json/encode thing) true))) 303 | (remove) 304 | (gen/add-encoder CTestR (fn [obj jg] (gen/encode-number 5 jg))) 305 | (is (= 5 (json/decode (json/encode thing) true))) 306 | (remove) 307 | (gen/add-encoder CTestR (fn [obj jg] (gen/encode-long 4 jg))) 308 | (is (= 4 (json/decode (json/encode thing) true))) 309 | (remove) 310 | (gen/add-encoder CTestR (fn [obj jg] (gen/encode-int 3 jg))) 311 | (is (= 3 (json/decode (json/encode thing) true))) 312 | (remove) 313 | (gen/add-encoder CTestR (fn [obj jg] (gen/encode-ratio 1/2 jg))) 314 | (is (= 0.5 (json/decode (json/encode thing) true))) 315 | (remove) 316 | (gen/add-encoder CTestR (fn [obj jg] (gen/encode-seq [:foo :bar] jg))) 317 | (is (= ["foo" "bar"] (json/decode (json/encode thing) true))) 318 | (remove) 319 | (gen/add-encoder CTestR (fn [obj jg] (gen/encode-date (Date. (long 0)) jg))) 320 | (binding [gen/*date-format* "yyyy-MM-dd'T'HH:mm:ss'Z'"] 321 | (is (= "1970-01-01T00:00:00Z" (json/decode (json/encode thing) true)))) 322 | (remove) 323 | (gen/add-encoder CTestR (fn [obj jg] (gen/encode-bool true jg))) 324 | (is (= true (json/decode (json/encode thing) true))) 325 | (remove) 326 | (gen/add-encoder CTestR (fn [obj jg] (gen/encode-named :foo jg))) 327 | (is (= "foo" (json/decode (json/encode thing) true))) 328 | (remove) 329 | (gen/add-encoder CTestR (fn [obj jg] (gen/encode-map {:foo "bar"} jg))) 330 | (is (= {:foo "bar"} (json/decode (json/encode thing) true))) 331 | (remove) 332 | (gen/add-encoder CTestR (fn [obj jg] (gen/encode-symbol 'foo jg))) 333 | (is (= "foo" (json/decode (json/encode thing) true))) 334 | (remove))) 335 | --------------------------------------------------------------------------------