Skip to content

Commit 71ed041

Browse files
committed
DJSON-46 Add :extra-data-fn to read - if provided and a json value has been read, the strema will be checked for remaining unread data and the function called if found
Signed-off-by: Alex Miller <alex.miller@cognitect.com>
1 parent 1da5545 commit 71ed041

2 files changed

Lines changed: 67 additions & 3 deletions

File tree

src/main/clojure/clojure/data/json.clj

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,34 @@
363363
(throw (Exception.
364364
(str "JSON error (unexpected character): " (char c))))))))
365365

366+
(defn- -read1
367+
[^PushbackReader stream eof-error? eof-value options]
368+
(let [val (-read stream eof-error? eof-value options)]
369+
(if-let [extra-data-fn (:extra-data-fn options)]
370+
(if (or eof-error? (not (identical? eof-value val)))
371+
(let [c (.read stream)]
372+
(if (neg? c)
373+
val
374+
(do
375+
(.unread stream c)
376+
(extra-data-fn val stream))))
377+
val)
378+
val)))
379+
380+
(defn on-extra-throw
381+
"Pass as :extra-data-fn to `read` or `read-str` to throw if data is found
382+
after the first object."
383+
[val rdr]
384+
(throw (ex-info "Found extra data after json object" {:val val})))
385+
386+
(defn on-extra-throw-remaining
387+
"Pass as :extra-data-fn to `read` or `read-str` to throw if data is found
388+
after the first object and return the remaining data in ex-data :remaining."
389+
[val ^java.io.PushbackReader rdr]
390+
(let [remaining (slurp rdr)]
391+
(throw (ex-info (str "Found extra data after json object: " remaining)
392+
{:val val, :remaining remaining}))))
393+
366394
(def default-read-options {:bigdec false
367395
:key-fn nil
368396
:value-fn nil})
@@ -404,7 +432,14 @@
404432
in the output. If value-fn returns itself, the property will
405433
be omitted from the output. The default value-fn returns the
406434
value unchanged. This option does not apply to non-map
407-
collections."
435+
collections.
436+
437+
:extra-data-fn function
438+
439+
If :extra-data-fn is not nil, then the reader will be checked
440+
for extra data after the read. If found, the extra-data-fn will
441+
be invoked with the read value and the reader. The result of
442+
the extra-data-fn will be returned."
408443
[reader & {:as options}]
409444
(let [{:keys [eof-error? eof-value]
410445
:or {eof-error? true}} options
@@ -413,7 +448,7 @@
413448
(PushbackReader. reader 64))]
414449
(->> options
415450
(merge default-read-options)
416-
(-read pbr eof-error? eof-value))))
451+
(-read1 pbr eof-error? eof-value))))
417452

418453
(defn read-str
419454
"Reads one JSON value from input String. Options are the same as for
@@ -423,7 +458,7 @@
423458
:or {eof-error? true}} options]
424459
(->> options
425460
(merge default-read-options)
426-
(-read (PushbackReader. (StringReader. string) 64) eof-error? eof-value))))
461+
(-read1 (PushbackReader. (StringReader. string) 64) eof-error? eof-value))))
427462

428463
;;; JSON WRITER
429464

src/test/clojure/clojure/data/json_test.clj

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,35 @@
3535
(is (= 123456789012345678901234567890N
3636
(json/read-str "123456789012345678901234567890"))))
3737

38+
(deftest lenient-on-extra-data
39+
(is (= [42] (json/read-str "[42],abc")))
40+
(is (= [42] (json/read (java.io.StringReader. "[42],abc")))))
41+
42+
(deftest strict-on-extra-data
43+
;; on-extra-throw
44+
(is (thrown? clojure.lang.ExceptionInfo
45+
(json/read-str "[42],abc" :extra-data-fn json/on-extra-throw)))
46+
(is (thrown? clojure.lang.ExceptionInfo
47+
(json/read (java.io.StringReader. "[42],abc") :extra-data-fn json/on-extra-throw)))
48+
49+
;; on-extra-throw-remaining
50+
(try
51+
(json/read-str "[42],abc" :extra-data-fn json/on-extra-throw-remaining)
52+
(is false "expected exception to be thrown")
53+
(catch clojure.lang.ExceptionInfo e
54+
(is (= ",abc" (:remaining (ex-data e))))))
55+
(try
56+
(json/read-str "[1], 1]" :extra-data-fn json/on-extra-throw-remaining)
57+
(is false "expected exception to be thrown")
58+
(catch clojure.lang.ExceptionInfo e
59+
(is (= ", 1]" (:remaining (ex-data e))))))
60+
61+
;; check that empty input behavior not modified when :extra-data-fn specified
62+
(is (= :hi (json/read-str ""
63+
:eof-error? false, :eof-value :hi, :extra-data-fn json/on-extra-throw)))
64+
(is (= :hi (json/read (java.io.StringReader. "")
65+
:eof-error? false, :eof-value :hi, :extra-data-fn json/on-extra-throw))))
66+
3867
(deftest write-bigint
3968
(is (= "123456789012345678901234567890"
4069
(json/write-str 123456789012345678901234567890N))))

0 commit comments

Comments
 (0)