Creating a parser combinator library to parse JSON
| Prev: JSON arrays | Contents | Next: The finished result |

Both JSON arrays and objects have a list of things interspersed by commas; in the case of json-object, it’s key-value pairs. In json-array, it was JSON values that were separated by commas:
(optional (cons-seq forward.json-value
(many (seq2 (skipwhite:match-is #\,)
(must "a comma must be followed by a value"
forward.json-value)))))I can extract that pattern:
(def parse-intersperse (separator parser must-message)
(optional (cons-seq parser
(many (seq2 separator
(must must-message parser))))))
The “must-message” is the error message to give if we see a separator, but it isn’t followed by another thing that the parser matches.
A comma separated list is a specific case:
(def comma-separated (parser must-message) (parse-intersperse (skipwhite:match-is #\,) parser must-message))
Now json-array is:
(= json-array
(seq2 (match-is #\[)
(comma-separated forward.json-value
"a comma must be followed by a value")
(must "a JSON array must be terminated with a closing ]"
(skipwhite:match-is #\]))))
In a json-object, key-value pairs are separated by a colon, and the key is always a JSON string:
(= json-object-kv
(with-seq (key skipwhite.json-string
colon (skipwhite:match-is #\:)
value forward.json-value)
(list key value)))
This matches a single key-value pair, and returns them as a two element list:
arc> (show-parse json-object-kv "\"abc\":[1,2,3]")
returning: ("abc" (1 2 3)) remaining:
nilArc’s listtab will convert a list of those key-value pairs into a table for us:
(= json-object
(on-result listtab
(seq2 (match-is #\{)
(comma-separated json-object-kv "comma must be followed by a key")
(skipwhite:match-is #\}))))
(= json-value
(skipwhite:alt json-true
json-false
json-null
json-number
json-string
json-array
json-object))
arc> (fromjson «{"a": [1, 2, {"b": 3}]}»)
#hash(("a" . (1 2 #hash(("b" . 3)) . nil)))As usual, we can report errors better...
arc> (fromjson «{») not a JSON value: {arc> (fromjson «{"a"}») not a JSON value: {"a"}arc> (fromjson «{"a":}») not a JSON value: {"a":}
toss in a few must’s:
(= json-object-kv
(with-seq (key skipwhite.json-string
colon (must "a JSON object key string must be followed by a :"
(skipwhite:match-is #\:))
value (must "a colon in a JSON object must be followed by a value"
forward.json-value))
(list key value)))
(= json-object
(on-result listtab
(seq2 (match-is #\{)
(comma-separated json-object-kv "comma must be followed by a key")
(must "a JSON object must be terminated with a closing }"
(skipwhite:match-is #\})))))
and now we get:
arc> (fromjson «{») a JSON object must be terminated with a closing }arc> (fromjson «{"a"}») a JSON object key string must be followed by a :arc> (fromjson «{"a":}») a colon in a JSON object must be followed by a value
| Prev: JSON arrays | Contents | Next: The finished result |
Questions? Comments? Email me andrew.wilcox [at] gmail.com