├── 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 | [](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 |
--------------------------------------------------------------------------------