| Safe Haskell | None |
|---|---|
| Language | GHC2024 |
Circuit.Parser.Json
Description
JSON for the circuits ecosystem: the aeson contact, rebuilt on
Circuit.Parser combinators — decodeJson parses, encodeJson renders,
and the two are exact tree inverses.
Recursive descent over the Uncons ByteString Char stream; string and
number payloads are captured as zero-copy slices (bs) and converted
after recognition, so UTF-8 stays bytes until unescape decodes it once.
Grammar notes: object and array tails commit on , — a trailing comma
is a parse failure, not a short list. Numbers follow the JSON grammar
exactly (-?(0|[1-9][0-9]*)(\.[0-9]+)?([eE][+-]?[0-9]+)?), leading
zeros rejected.
Two paths, one tree: this module is the combinator path (decodeJson is
the boundary); Circuit.Parser.Json.Lexer is the fast path, a flat
zero-copy token stream for when composition is not the point.
Synopsis
- data Json
- value :: Parser Identity ByteString Char Json
- jstring :: Parser Identity ByteString Char Text
- jnumber :: Parser Identity ByteString Char Scientific
- ws :: Parser Identity ByteString Char ()
- bsToScientific :: ByteString -> Scientific
- decodeJson :: ByteString -> Either String Json
- encodeJson :: Json -> ByteString
- data JsonToken
- runJsonLexerBS :: ByteString -> Either (Int, String) [JsonToken]
- unescape :: ByteString -> Either String Text
The tree
A JSON value.
JNumber is exact (Scientific: coefficient times ten to an exponent),
so parsing loses nothing. JObject is an association list in source
order.
Parsing
value :: Parser Identity ByteString Char Json Source #
Parse any JSON value, leading whitespace included.
jnumber :: Parser Identity ByteString Char Scientific Source #
Parse a JSON number to Scientific.
ws :: Parser Identity ByteString Char () Source #
JSON whitespace: space, newline, carriage return, tab. Nothing else.
Conversion
bsToScientific :: ByteString -> Scientific Source #
Convert a recognised JSON number slice to Scientific.
The slice is trusted to match the JSON number grammar (this is what
jnumber recognises); anything else is garbage in, garbage out.
>>>bsToScientific (C.pack "123")123.0
>>>bsToScientific (C.pack "-0.5")-0.5
>>>bsToScientific (C.pack "1e-3")1.0e-3
Boundary
decodeJson :: ByteString -> Either String Json Source #
Parse a JSON document: a value, optional trailing whitespace, and nothing else.
Failure is a flat Left — the combinator runner backtracks to the
original stream, so there is no honest offset to report. For offsets,
tokenize first with Circuit.Parser.Json.Lexer.
>>>decodeJson (C.pack "{\"a\": [1, true, null]}")Right (JObject [("a",JArray [JNumber 1.0,JBool True,JNull])])
>>>decodeJson (C.pack " \"a\\nb\" ")Right (JString "a\nb")
>>>decodeJson (C.pack "[1,]")Left "invalid JSON"
>>>decodeJson (C.pack "01")Left "invalid JSON"
encodeJson :: Json -> ByteString Source #
Render a JSON document, compact (no insignificant whitespace).
The other half of the boundary: decodeJson and encodeJson are exact
tree inverses —
decodeJson (encodeJson j) == Right j
>>>decodeJson (encodeJson (JObject [("a", JArray (V.fromList [JNumber 1, JBool True, JNull]))]))Right (JObject [("a",JArray [JNumber 1.0,JBool True,JNull])])
>>>decodeJson (encodeJson (JString "a\nb"))Right (JString "a\nb")
>>>decodeJson (encodeJson (JObject []))Right (JObject [])
Numbers render in a form the parser reads back to the same
Scientific (its equality is structural): zero exponent renders the
bare coefficient (10), a positive exponent renders coefficient and
exponent (1e1, not 10), and a negative exponent renders the decimal
point positionally, keeping every fractional digit (1.50, 0.0015).
Object pairs render in tree order; duplicate keys are preserved, as
they are in the tree.
>>>encodeJson (JObject [("a", JArray (V.fromList [JNumber 1, JBool True, JNull]))])"{\"a\":[1,true,null]}"
>>>encodeJson (JString "a\nb")"\"a\\nb\""
Fast path
A flat JSON structural token. TString and TNumber payloads are
zero-copy slices of the input; a TString keeps its escapes intact.
Constructors
| TBraceOpen | |
| TBraceClose | |
| TBrackOpen | |
| TBrackClose | |
| TComma | |
| TColon | |
| TString ByteString | |
| TNumber ByteString | |
| TTrue | |
| TFalse | |
| TNull |
runJsonLexerBS :: ByteString -> Either (Int, String) [JsonToken] Source #
Tokenize a strict ByteString.
On failure, returns the byte offset and a message.
>>>runJsonLexerBS (C.pack "{\"a\": [1, true]}")Right [TBraceOpen,TString "a",TColon,TBrackOpen,TNumber "1",TComma,TTrue,TBrackClose,TBraceClose]
>>>runJsonLexerBS (C.pack "[1")Right [TBrackOpen,TNumber "1"]
>>>runJsonLexerBS (C.pack "\"ab")Left (3,"unterminated string")
>>>runJsonLexerBS (C.pack "nul")Left (0,"bad literal")
>>>runJsonLexerBS (C.pack "@")Left (0,"unexpected byte 64")
Pieces
unescape :: ByteString -> Either String Text Source #
Unescape a raw JSON string slice (the bytes between the quotes) to
Text.
>>>unescape (C.pack "hello")Right "hello"
>>>unescape (C.pack "a\\nb")Right "a\nb"
>>>unescape (C.pack "\\u0041\\u00e9")Right "A\233"
>>>unescape (C.pack "\\uD834\\uDD1E")Right "\119070"
>>>unescape (C.pack "bad\\x")Left "invalid escape: \\x"