Skip to content

Instantly share code, notes, and snippets.

@rodolfo42
Last active April 20, 2021 02:00
Show Gist options
  • Save rodolfo42/762bb6cec86307ce91bcad41fe4498f9 to your computer and use it in GitHub Desktop.
Save rodolfo42/762bb6cec86307ce91bcad41fe4498f9 to your computer and use it in GitHub Desktop.
Completed JSON

For when you have incomplete JSON strings like:

{"Fault":
  {"faultcode": "StartTransmissionFail",
   "faultstring": {"content": "Internal Error"

Given an incomplete JSON-encoded string, attempts to complete it with the missing ", } and ] characters necessary for it to be valid.

Understands the backlash escape char (\), and ignores chars that open clauses (such as [ or {) inside strings.

Note: does not support incomplete JSON strings that end the middle of a map keyword (yet), right after a colon : or other unforseen cases.

(defn complete-json
[remaining inside-string? trailing-chars]
(let [stack? {\" \" \{ \} \[ \]}
pop? (zipmap (vals stack?) (keys stack?))
quote? (partial = \")
escape? (partial = \\)]
(if (empty? remaining)
trailing-chars
(let [[ch & r] remaining]
(cond
(escape? ch)
(lazy-cat (list ch (first r))
(complete-json (rest r) inside-string? trailing-chars))
(quote? ch)
(if inside-string?
(lazy-cat (list ch)
(complete-json r false (pop trailing-chars)))
(lazy-cat (list ch)
(complete-json r true (conj trailing-chars \"))))
inside-string?
(lazy-cat (list ch)
(complete-json r true trailing-chars))
(stack? ch)
(lazy-cat (list ch)
(complete-json r false (conj trailing-chars (stack? ch))))
(pop? ch)
(lazy-cat (list ch)
(complete-json r false (pop trailing-chars)))
:whatever
(lazy-cat (list ch)
(complete-json r inside-string? trailing-chars)))))))
(defn completed-json
"Given an incomplete JSON-encoded string, attempts to complete it with the missing \", } and ] characters necessary for it to be valid.
Understands the backlash escape char (\\), and ignores chars that open clauses (such as [ or { inside strings."
[s]
(apply str (complete-json s false nil)))
(require '[clojure.test :refer [deftest testing is]])
(deftest completed-json-test
(testing "short"
(is (= (completed-json "{\"Fault\":{\"faultcode\":\"StartTransmissionFail\"")
"{\"Fault\":{\"faultcode\":\"StartTransmissionFail\"}}")))
(testing "longer"
(is (= (completed-json "{\"Fault\":{\"faultcode\":\"StartTransmissionFail\",\"faultstring\":{\"xml:lang\":\"en-US\",\"content\":\"Internal error.\"\n")
"{\"Fault\":{\"faultcode\":\"StartTransmissionFail\",\"faultstring\":{\"xml:lang\":\"en-US\",\"content\":\"Internal error.\"\n}}}")))
(testing "quotes inside strings"
(is (= (completed-json "{\"dimension\":\"29\\\" total")
"{\"dimension\":\"29\\\" total\"}")))
(testing "already valid JSON"
(is (= (completed-json "{\"a\":1,\"b\":[{\"c\":1,\"d\":\"string\",\"e\":45.6}]}")
"{\"a\":1,\"b\":[{\"c\":1,\"d\":\"string\",\"e\":45.6}]}"))))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment