Skip to content

Instantly share code, notes, and snippets.

@vemv
Last active August 12, 2020 11:10
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save vemv/7d9489cee6745b55f637e64fb95d4113 to your computer and use it in GitHub Desktop.
Save vemv/7d9489cee6745b55f637e64fb95d4113 to your computer and use it in GitHub Desktop.
json-libs-equivalence-test
(ns json-libs-equivalence-test
"Includes code derived from:
https://github.com/metosin/jsonista/blob/211306f04bb15d7232b536cf6c6d8ecfeae0512d/LICENSE
https://github.com/dakrone/cheshire/blob/4525b23da1c17decba363202402a8a195d21705f/LICENSE"
(:require
[cheshire.core :as cheshire]
[clojure.data.json]
[clojure.java.io :as io]
[clojure.test :refer [are deftest is testing]]
[jsonista.core :as jsonista]
[nedap.speced.def :as speced])
(:import
(clojure.lang PersistentVector LazySeq)
(java.sql Timestamp)
(java.time Instant LocalDateTime LocalTime ZoneOffset)
(java.util Date UUID)
(java.util.zip GZIPInputStream)))
(def read-fns
[jsonista/read-value
cheshire/parse-string
cheshire/parse-string-strict
clojure.data.json/read-str])
(def write-fns
[jsonista/write-value-as-string
cheshire/generate-string
clojure.data.json/write-str])
(defn read+write-combinations [exclude-clojure-data-json?]
(cond->> read-fns
exclude-clojure-data-json? drop-last
true (mapcat (fn [x]
(cond->> write-fns
exclude-clojure-data-json? drop-last
true (map (fn [y]
[x y])))))))
(speced/defn read+write= [^string? x, ^string? y]
(->> (for [[read-fn write-fn] (read+write-combinations false)]
(->> [x y]
(map (fn [a]
(-> a read-fn write-fn)))
(apply =)))
(every? true?)))
(speced/defn write+read= [^map? x, ^boolean? exclude-clojure-data-json?]
(->> (for [[read-fn write-fn] (read+write-combinations exclude-clojure-data-json?)]
(->> [x]
(map (fn [a]
(-> a write-fn read-fn)))))
(apply =)))
(def data
{:numbers {:integer (int 1)
:long (long 2)
:double (double 1.2)
:float (float 3.14)
:big-integer (biginteger 3)
:big-decimal (bigdec 4)
:ratio 3/4
:short (short 5)
:byte (byte 6)
:big-int (bigint 7)}
:boolean true
:string "string"
:character \c
:keyword :keyword
:q-keyword :qualified/:keyword
:set #{1 2 3}
:queue (conj (clojure.lang.PersistentQueue/EMPTY) 1 2 3)
:list (list 1 2 3)
:bytes (.getBytes "bytes")
:uuid (UUID/fromString "fbe5a1e8-6c91-42f6-8147-6cde3188fd25")
:symbol 'symbol
:java-set (doto (java.util.HashSet.) (.add 1) (.add 2) (.add 3))
:java-map (doto (java.util.HashMap.) (.put :foo "bar"))
:java-list (doto (java.util.ArrayList.) (.add 1) (.add 2) (.add 3))
:dates {:date (Date. 0)
:timestamp (Timestamp. 0)
:instant (Instant/ofEpochMilli 0)
:local-time (LocalTime/ofNanoOfDay 0)
:local-date-time (LocalDateTime/ofEpochSecond 0 0 ZoneOffset/UTC)}})
(def big-test-obj
;; https://github.com/dakrone/cheshire/blob/4525b23da1c17decba363202402a8a195d21705f/test/all_month.geojson.gz
(-> "all_month.geojson.gz"
io/resource
io/input-stream
GZIPInputStream.
slurp))
(deftest roundrobin
(let [without-java-time #(update % :dates dissoc :instant :local-time :local-date-time)]
(testing "Cheshire reads+writes identically to jsonista"
(let [data (-> data without-java-time)]
(is (read+write= (cheshire/generate-string data)
(jsonista/write-value-as-string data)))
(is (read+write= (cheshire/generate-string data)
(jsonista/write-value-as-string data)))
(is (write+read= data true))))
(testing "clojure.data.json reads+writes analogously to jsonista and Cheshire"
(let [data (-> data
without-java-time
;; All these throw exceptions in c.d.j (except for :q-keyword - c.j.d drops the namespace):
(dissoc :uuid :character :bytes :q-keyword)
;; All these throw exceptions in c.d.j:
(update :dates dissoc :date :timestamp))]
(is (read+write= (clojure.data.json/write-str data)
(jsonista/write-value-as-string data)))
(is (read+write= (clojure.data.json/write-str data)
(cheshire/generate-string data)))
(is (write+read= data false))))
(testing "A big object is read identically by all libs"
(is (->> read-fns
(map (fn [f]
(f big-test-obj)))
(apply =))))))
(deftest top-level-array
(testing "Only `#'cheshire/parse-string` (and not `#'cheshire/parse-string-strict`, nor the other libs)
parse top-level arrays lazily"
(are [f expected] (testing f
(is (instance? expected
(f "[1, 2, 3]")))
true)
jsonista/read-value PersistentVector
cheshire/parse-string LazySeq
cheshire/parse-string-strict PersistentVector
clojure.data.json/read-str PersistentVector)))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment