Most EOFs produce helpful error messages like JSON error (end-of-file inside string)
. But the object and array readers don't handle EOF and throw unhelpful exceptions.
(clojure.data.json/read-str "{")
throws Value out of range for char: -1
due to this line in read-key
:
(throw (Exception. (str "JSON error (non-string key in object), found
" (char c) ", expected
\"")))
The (char c)
throws because c is -1.
This is fixed by handling the -1 case in read-key:
(defn- read-key [^PushbackReader stream]
(let [c (int (next-token stream))]
(if (= c (codepoint \"))
(let [key (read-quoted-string stream)]
(if (= (codepoint \:) (int (next-token stream)))
key
(throw (Exception. "JSON error (missing `:` in object)"))))
(codepoint-case c
\} nil
-1 (throw (Exception. "JSON error (end-of-file inside object)"))
(throw (Exception. (str "JSON error (non-string key in object), found `" (char c) "`, expected `\"`")))))))
(clojure.data.json/read-str "{\"\":\"\"")
gives an incorrect error message: JSON error (missing entry in object)
This is fixed by handling EOF in read-object
:
(codepoint-case (int (next-token stream))
\, (recur r)
\} (persistent! r)
-1 (throw (Exception. "JSON error (end-of-file inside object)"))
(throw (Exception. "JSON error (missing entry in object)"))))
(clojure.data.json/read-str "[")
throws JSON error (unexpected character):
(the unexpected character is (char 65535)). This is because read-array
pushes -1 back onto the stream, and -1 gets pushed as 65535.
The fix is to handle EOF inside read-array
:
(defn- read-array [^PushbackReader stream options]
;; Expects to be called with the head of the stream AFTER the
;; opening bracket.
;; Only handles array value.
(let [c (int (next-token stream))]
(codepoint-case c
\] []
\, (throw (invalid-array-exception))
-1 (throw (Exception. "JSON error (end-of-file inside array)"))
(do (.unread stream c)
(read-array* stream options)))))