JsonParser (Polyglot)¶
In [ ]:
#!import ../../lib/fsharp/Notebooks.dib
#!import ../../lib/fsharp/Testing.dib
In [ ]:
#!import ../../lib/fsharp/Common.fs
#!import Parser.fs
In [ ]:
open Common
open Parser
JsonParser¶
In [ ]:
(*
// --------------------------------
JSON spec from http://www.json.org/
// --------------------------------
The JSON spec is available at [json.org](http://www.json.org/). I'll paraphase it here:
* A `value` can be a `string` or a `number` or a `bool` or `null` or an `object` or an `array`.
* These structures can be nested.
* A `string` is a sequence of zero or more Unicode characters, wrapped in double quotes, using backslash escapes.
* A `number` is very much like a C or Java number, except that the octal and hexadecimal formats are not used.
* A `boolean` is the literal `true` or `false`
* A `null` is the literal `null`
* An `object` is an unordered set of name/value pairs.
* An object begins with { (left brace) and ends with } (right brace).
* Each name is followed by : (colon) and the name/value pairs are separated by , (comma).
* An `array` is an ordered collection of values.
* An array begins with [ (left bracket) and ends with ] (right bracket).
* Values are separated by , (comma).
* Whitespace can be inserted between any pair of tokens.
*)
In [ ]:
//// test
let inline parserEqual (expected : ParseResult<'a>) (actual : ParseResult<'a * Input>) =
match actual, expected with
| Success (_actual, _), Success _expected ->
printResult actual
_actual |> _assertEqual _expected
| Failure (l1, e1, p1), Failure (l2, e2, p2) when l1 = l2 && e1 = e2 && p1 = p2 ->
printResult actual
| _ ->
printfn $"Actual: {actual}"
printfn $"Expected: {expected}"
failwith "Parse failed"
actual
JValue¶
In [ ]:
type JValue =
| JString of string
| JNumber of float
| JBool of bool
| JNull
| JObject of Map<string, JValue>
| JArray of JValue list
In [ ]:
let jValue, jValueRef = createParserForwardedToRef<JValue> ()
jNull¶
In [ ]:
let jNull =
pstring "null"
>>% JNull
<?> "null"
In [ ]:
//// test
jValueRef <|
choice
[
jNull
]
In [ ]:
//// test
run jValue "null"
|> parserEqual (Success JNull)
Success (JNull, { lines = [|"null"|]\n position = { line = 0\n column = 4 } })
Item |
|
Item1 |
|
IsJString | false |
IsJNumber | false |
IsJBool | false |
IsJNull | true |
IsJObject | false |
IsJArray | false |
{ lines = [|"null"|]\n position = { line = 0\n column = 4 } }
lines | [ null ] | ||||
position |
|
line | 0 |
column | 4 |
true
false
JNull JNull
In [ ]:
//// test
run jNull "nulp"
|> parserEqual (
Failure (
"null",
"Unexpected 'p'",
{ currentLine = "nulp"; line = 0; column = 3 }
)
)
Failure ("null", "Unexpected 'p'", { currentLine = "nulp"\n line = 0\n column = 3 })
Item1 | "null" | ||||||
Item2 | "Unexpected 'p'" | ||||||
Item3 |
|
currentLine | "nulp" |
line | 0 |
column | 3 |
false
true
Line:0 Col:3 Error parsing null nulp ^Unexpected 'p'
jBool¶
In [ ]:
let jBool =
let jtrue =
pstring "true"
>>% JBool true
let jfalse =
pstring "false"
>>% JBool false
jtrue <|> jfalse
<?> "bool"
In [ ]:
//// test
jValueRef <|
choice
[
jNull
jBool
]
In [ ]:
//// test
run jBool "true"
|> parserEqual (Success (JBool true))
Success (JBool true, { lines = [|"true"|]\n position = { line = 0\n column = 4 } })
Item |
|
Item1 |
|
Item | true |
IsJString | false |
IsJNumber | false |
IsJBool | true |
IsJNull | false |
IsJObject | false |
IsJArray | false |
{ lines = [|"true"|]\n position = { line = 0\n column = 4 } }
lines | [ true ] | ||||
position |
|
line | 0 |
column | 4 |
true
false
JBool true JBool true
In [ ]:
//// test
run jBool "false"
|> parserEqual (Success (JBool false))
Success (JBool false, { lines = [|"false"|]\n position = { line = 0\n column = 5 } })
Item |
|
Item1 |
|
Item | false |
IsJString | false |
IsJNumber | false |
IsJBool | true |
IsJNull | false |
IsJObject | false |
IsJArray | false |
{ lines = [|"false"|]\n position = { line = 0\n column = 5 } }
lines | [ false ] | ||||
position |
|
line | 0 |
column | 5 |
true
false
JBool false JBool false
In [ ]:
//// test
run jBool "truX"
|> parserEqual (
Failure (
"bool",
"Unexpected 't'",
{ currentLine = "truX"; line = 0; column = 0 }
)
)
Failure ("bool", "Unexpected 't'", { currentLine = "truX"\n line = 0\n column = 0 })
Item1 | "bool" | ||||||
Item2 | "Unexpected 't'" | ||||||
Item3 |
|
currentLine | "truX" |
line | 0 |
column | 0 |
false
true
Line:0 Col:0 Error parsing bool truX ^Unexpected 't'
jUnescapedChar¶
In [ ]:
let jUnescapedChar =
satisfy (fun ch -> ch <> '\\' && ch <> '\"') "char"
In [ ]:
//// test
run jUnescapedChar "a"
|> parserEqual (Success 'a')
Success ('a', { lines = [|"a"|]\n position = { line = 0\n column = 1 } })
Item |
|
Item1 | 'a' | ||||||||
Item2 |
|
lines | [ a ] | ||||
position |
|
line | 0 |
column | 1 |
true
false
'a' 'a'
In [ ]:
//// test
run jUnescapedChar "\\"
|> parserEqual (
Failure (
"char",
"Unexpected '\\'",
{ currentLine = "\\"; line = 0; column = 0 }
)
)
Failure ("char", "Unexpected '\'", { currentLine = "\"\n line = 0\n column = 0 })
Item1 | "char" | ||||||
Item2 | "Unexpected '\'" | ||||||
Item3 |
|
currentLine | "\" |
line | 0 |
column | 0 |
false
true
Line:0 Col:0 Error parsing char \ ^Unexpected '\'
jEscapedChar¶
In [ ]:
let jEscapedChar =
[
("\\\"",'\"')
("\\\\",'\\')
("\\/",'/')
("\\b",'\b')
("\\f",'\f')
("\\n",'\n')
("\\r",'\r')
("\\t",'\t')
]
|> List.map (fun (toMatch, result) ->
pstring toMatch >>% result
)
|> choice
<?> "escaped char"
In [ ]:
//// test
run jEscapedChar "\\\\"
|> parserEqual (Success '\\')
Success ('\\', { lines = [|"\\"|]\n position = { line = 0\n column = 2 } })
Item |
|
Item1 | '\\' | ||||||||
Item2 |
|
lines | [ \\ ] | ||||
position |
|
line | 0 |
column | 2 |
true
false
'\\' '\\'
In [ ]:
//// test
run jEscapedChar "\\t"
|> parserEqual (Success '\t')
Success ('\009', { lines = [|"\t"|]\n position = { line = 0\n column = 2 } })
Item |
|
Item1 | '\009' | ||||||||
Item2 |
|
lines | [ \t ] | ||||
position |
|
line | 0 |
column | 2 |
true
false
'\009' '\009'
In [ ]:
//// test
run jEscapedChar @"\\"
|> parserEqual (Success '\\')
Success ('\\', { lines = [|"\\"|]\n position = { line = 0\n column = 2 } })
Item |
|
Item1 | '\\' | ||||||||
Item2 |
|
lines | [ \\ ] | ||||
position |
|
line | 0 |
column | 2 |
true
false
'\\' '\\'
In [ ]:
//// test
run jEscapedChar @"\n"
|> parserEqual (Success '\n')
Success ('\010', { lines = [|"\n"|]\n position = { line = 0\n column = 2 } })
Item |
|
Item1 | '\010' | ||||||||
Item2 |
|
lines | [ \n ] | ||||
position |
|
line | 0 |
column | 2 |
true
false
'\010' '\010'
In [ ]:
//// test
run jEscapedChar "a"
|> parserEqual (
Failure (
"escaped char",
"Unexpected 'a'",
{ currentLine = "a"; line = 0; column = 0 }
)
)
Failure ("escaped char", "Unexpected 'a'", { currentLine = "a"\n line = 0\n column = 0 })
Item1 | "escaped char" | ||||||
Item2 | "Unexpected 'a'" | ||||||
Item3 |
|
currentLine | "a" |
line | 0 |
column | 0 |
false
true
Line:0 Col:0 Error parsing escaped char a ^Unexpected 'a'
jUnicodeChar¶
In [ ]:
let jUnicodeChar =
let backslash = pchar '\\'
let uChar = pchar 'u'
let hexdigit = anyOf ([ '0' .. '9' ] @ [ 'A' .. 'F' ] @ [ 'a' .. 'f' ])
let fourHexDigits = hexdigit .>>. hexdigit .>>. hexdigit .>>. hexdigit
let inline convertToChar (((h1, h2), h3), h4) =
let str = $"%c{h1}%c{h2}%c{h3}%c{h4}"
Int32.Parse (str, Globalization.NumberStyles.HexNumber) |> char
backslash >>. uChar >>. fourHexDigits
|>> convertToChar
In [ ]:
//// test
run jUnicodeChar "\\u263A"
|> parserEqual (Success '☺')
Success ('☺', { lines = [|"\u263A"|]\n position = { line = 0\n column = 6 } })
Item |
|
Item1 | '☺' | ||||||||
Item2 |
|
lines | [ \u263A ] | ||||
position |
|
line | 0 |
column | 6 |
true
false
'☺' '☺'
jString¶
In [ ]:
let quotedString =
let quote = pchar '\"' <?> "quote"
let jchar = jUnescapedChar <|> jEscapedChar <|> jUnicodeChar
quote >>. manyChars jchar .>> quote
In [ ]:
let jString =
quotedString
|>> JString
<?> "quoted string"
In [ ]:
//// test
jValueRef <|
choice
[
jNull
jBool
jString
]
In [ ]:
//// test
run jString "\"\""
|> parserEqual (Success (JString ""))
Success (JString "", { lines = [|""""|]\n position = { line = 0\n column = 2 } })
Item |
|
Item1 |
|
Item | "" |
IsJString | true |
IsJNumber | false |
IsJBool | false |
IsJNull | false |
IsJObject | false |
IsJArray | false |
{ lines = [|""""|]\n position = { line = 0\n column = 2 } }
lines | [ "" ] | ||||
position |
|
line | 0 |
column | 2 |
true
false
JString "" JString ""
In [ ]:
//// test
run jString "\"a\""
|> parserEqual (Success (JString "a"))
Success (JString "a", { lines = [|""a""|]\n position = { line = 0\n column = 3 } })
Item |
|
Item1 |
|
Item | "a" |
IsJString | true |
IsJNumber | false |
IsJBool | false |
IsJNull | false |
IsJObject | false |
IsJArray | false |
{ lines = [|""a""|]\n position = { line = 0\n column = 3 } }
lines | [ "a" ] | ||||
position |
|
line | 0 |
column | 3 |
true
false
JString "a" JString "a"
In [ ]:
//// test
run jString "\"ab\""
|> parserEqual (Success (JString "ab"))
Success (JString "ab", { lines = [|""ab""|]\n position = { line = 0\n column = 4 } })
Item |
|
Item1 |
|
Item | "ab" |
IsJString | true |
IsJNumber | false |
IsJBool | false |
IsJNull | false |
IsJObject | false |
IsJArray | false |
{ lines = [|""ab""|]\n position = { line = 0\n column = 4 } }
lines | [ "ab" ] | ||||
position |
|
line | 0 |
column | 4 |
true
false
JString "ab" JString "ab"
In [ ]:
//// test
run jString "\"ab\\tde\""
|> parserEqual (Success (JString "ab\tde"))
Success (JString "ab de", { lines = [|""ab\tde""|]\n position = { line = 0\n column = 8 } })
Item |
|
Item1 |
|
Item | "ab de" |
IsJString | true |
IsJNumber | false |
IsJBool | false |
IsJNull | false |
IsJObject | false |
IsJArray | false |
{ lines = [|""ab\tde""|]\n position = { line = 0\n column = 8 } }
lines | [ "ab\tde" ] | ||||
position |
|
line | 0 |
column | 8 |
true
false
JString "ab de" JString "ab de"
In [ ]:
//// test
run jString "\"ab\\u263Ade\""
|> parserEqual (Success (JString "ab☺de"))
Success (JString "ab☺de", { lines = [|""ab\u263Ade""|]\n position = { line = 0\n column = 12 } })
Item |
|
Item1 |
|
Item | "ab☺de" |
IsJString | true |
IsJNumber | false |
IsJBool | false |
IsJNull | false |
IsJObject | false |
IsJArray | false |
{ lines = [|""ab\u263Ade""|]\n position = { line = 0\n column = 12 } }
lines | [ "ab\u263Ade" ] | ||||
position |
|
line | 0 |
column | 12 |
true
false
JString "ab☺de" JString "ab☺de"
jNumber¶
In [ ]:
let jNumber =
let optSign = opt (pchar '-')
let zero = pstring "0"
let digitOneNine =
satisfy (fun ch -> Char.IsDigit ch && ch <> '0') "1-9"
let digit =
satisfy Char.IsDigit "digit"
let point = pchar '.'
let e = pchar 'e' <|> pchar 'E'
let optPlusMinus = opt (pchar '-' <|> pchar '+')
let nonZeroInt =
digitOneNine .>>. manyChars digit
|>> fun (first, rest) -> string first + rest
let intPart = zero <|> nonZeroInt
let fractionPart = point >>. manyChars1 digit
let exponentPart = e >>. optPlusMinus .>>. manyChars1 digit
let inline (|>?) opt f =
match opt with
| None -> ""
| Some x -> f x
let inline convertToJNumber (((optSign, intPart), fractionPart), expPart) =
let signStr =
optSign
|>? string
let fractionPartStr =
fractionPart
|>? (fun digits -> "." + digits)
let expPartStr =
expPart
|>? fun (optSign, digits) ->
let sign = optSign |>? string
"e" + sign + digits
(signStr + intPart + fractionPartStr + expPartStr)
|> float
|> JNumber
optSign .>>. intPart .>>. opt fractionPart .>>. opt exponentPart
|>> convertToJNumber
<?> "number"
In [ ]:
//// test
jValueRef <|
choice
[
jNull
jBool
jString
jNumber
]
In [ ]:
//// test
run jNumber "123"
|> parserEqual (Success (JNumber 123.0))
Success (JNumber 123.0, { lines = [|"123"|]\n position = { line = 0\n column = 3 } })
Item |
|
Item1 |
|
Item | 123.0 |
IsJString | false |
IsJNumber | true |
IsJBool | false |
IsJNull | false |
IsJObject | false |
IsJArray | false |
{ lines = [|"123"|]\n position = { line = 0\n column = 3 } }
lines | [ 123 ] | ||||
position |
|
line | 0 |
column | 3 |
true
false
JNumber 123.0 JNumber 123.0
In [ ]:
//// test
run jNumber "-123"
|> parserEqual (Success (JNumber -123.0))
Success (JNumber -123.0, { lines = [|"-123"|]\n position = { line = 0\n column = 4 } })
Item |
|
Item1 |
|
Item | -123.0 |
IsJString | false |
IsJNumber | true |
IsJBool | false |
IsJNull | false |
IsJObject | false |
IsJArray | false |
{ lines = [|"-123"|]\n position = { line = 0\n column = 4 } }
lines | [ -123 ] | ||||
position |
|
line | 0 |
column | 4 |
true
false
JNumber -123.0 JNumber -123.0
In [ ]:
//// test
run jNumber "123.4"
|> parserEqual (Success (JNumber 123.4))
Success (JNumber 123.4, { lines = [|"123.4"|]\n position = { line = 0\n column = 5 } })
Item |
|
Item1 |
|
Item | 123.4 |
IsJString | false |
IsJNumber | true |
IsJBool | false |
IsJNull | false |
IsJObject | false |
IsJArray | false |
{ lines = [|"123.4"|]\n position = { line = 0\n column = 5 } }
lines | [ 123.4 ] | ||||
position |
|
line | 0 |
column | 5 |
true
false
JNumber 123.4 JNumber 123.4
In [ ]:
//// test
run jNumber "-123."
|> parserEqual (Success (JNumber -123.0))
Success (JNumber -123.0, { lines = [|"-123."|]\n position = { line = 0\n column = 4 } })
Item |
|
Item1 |
|
Item | -123.0 |
IsJString | false |
IsJNumber | true |
IsJBool | false |
IsJNull | false |
IsJObject | false |
IsJArray | false |
{ lines = [|"-123."|]\n position = { line = 0\n column = 4 } }
lines | [ -123. ] | ||||
position |
|
line | 0 |
column | 4 |
true
false
JNumber -123.0 JNumber -123.0
In [ ]:
//// test
run jNumber "00.1"
|> parserEqual (Success (JNumber 0.0))
Success (JNumber 0.0, { lines = [|"00.1"|]\n position = { line = 0\n column = 1 } })
Item |
|
Item1 |
|
Item | 0.0 |
IsJString | false |
IsJNumber | true |
IsJBool | false |
IsJNull | false |
IsJObject | false |
IsJArray | false |
{ lines = [|"00.1"|]\n position = { line = 0\n column = 1 } }
lines | [ 00.1 ] | ||||
position |
|
line | 0 |
column | 1 |
true
false
JNumber 0.0 JNumber 0.0
In [ ]:
//// test
let jNumber_ = jNumber .>> spaces1
In [ ]:
//// test
run jNumber_ "123"
|> parserEqual (Success (JNumber 123.0))
Success (JNumber 123.0, { lines = [|"123"|]\n position = { line = 1\n column = 0 } })
Item |
|
Item1 |
|
Item | 123.0 |
IsJString | false |
IsJNumber | true |
IsJBool | false |
IsJNull | false |
IsJObject | false |
IsJArray | false |
{ lines = [|"123"|]\n position = { line = 1\n column = 0 } }
lines | [ 123 ] | ||||
position |
|
line | 1 |
column | 0 |
true
false
JNumber 123.0 JNumber 123.0
In [ ]:
//// test
run jNumber_ "-123"
|> parserEqual (Success (JNumber -123.0))
Success (JNumber -123.0, { lines = [|"-123"|]\n position = { line = 1\n column = 0 } })
Item |
|
Item1 |
|
Item | -123.0 |
IsJString | false |
IsJNumber | true |
IsJBool | false |
IsJNull | false |
IsJObject | false |
IsJArray | false |
{ lines = [|"-123"|]\n position = { line = 1\n column = 0 } }
lines | [ -123 ] | ||||
position |
|
line | 1 |
column | 0 |
true
false
JNumber -123.0 JNumber -123.0
In [ ]:
//// test
run jNumber_ "-123."
|> parserEqual (
Failure (
"number andThen many1 whitespace",
"Unexpected '.'",
{ currentLine = "-123."; line = 0; column = 4 }
)
)
Failure\n ("number andThen many1 whitespace", "Unexpected '.'", { currentLine = "-123."\n line = 0\n column = 4 })
Item1 | "number andThen many1 whitespace" | ||||||
Item2 | "Unexpected '.'" | ||||||
Item3 |
|
currentLine | "-123." |
line | 0 |
column | 4 |
false
true
Line:0 Col:4 Error parsing number andThen many1 whitespace -123. ^Unexpected '.'
In [ ]:
//// test
run jNumber_ "123.4"
|> parserEqual (Success (JNumber 123.4))
Success (JNumber 123.4, { lines = [|"123.4"|]\n position = { line = 1\n column = 0 } })
Item |
|
Item1 |
|
Item | 123.4 |
IsJString | false |
IsJNumber | true |
IsJBool | false |
IsJNull | false |
IsJObject | false |
IsJArray | false |
{ lines = [|"123.4"|]\n position = { line = 1\n column = 0 } }
lines | [ 123.4 ] | ||||
position |
|
line | 1 |
column | 0 |
true
false
JNumber 123.4 JNumber 123.4
In [ ]:
//// test
run jNumber_ "00.4"
|> parserEqual (
Failure (
"number andThen many1 whitespace",
"Unexpected '0'",
{ currentLine = "00.4"; line = 0; column = 1 }
)
)
Failure\n ("number andThen many1 whitespace", "Unexpected '0'", { currentLine = "00.4"\n line = 0\n column = 1 })
Item1 | "number andThen many1 whitespace" | ||||||
Item2 | "Unexpected '0'" | ||||||
Item3 |
|
currentLine | "00.4" |
line | 0 |
column | 1 |
false
true
Line:0 Col:1 Error parsing number andThen many1 whitespace 00.4 ^Unexpected '0'
In [ ]:
//// test
run jNumber_ "123e4"
|> parserEqual (Success (JNumber 1230000.0))
Success (JNumber 1230000.0, { lines = [|"123e4"|]\n position = { line = 1\n column = 0 } })
Item |
|
Item1 |
|
Item | 1230000.0 |
IsJString | false |
IsJNumber | true |
IsJBool | false |
IsJNull | false |
IsJObject | false |
IsJArray | false |
{ lines = [|"123e4"|]\n position = { line = 1\n column = 0 } }
lines | [ 123e4 ] | ||||
position |
|
line | 1 |
column | 0 |
true
false
JNumber 1230000.0 JNumber 1230000.0
In [ ]:
//// test
run jNumber_ "123.4e5"
|> parserEqual (Success (JNumber 12340000.0))
Success (JNumber 12340000.0, { lines = [|"123.4e5"|]\n position = { line = 1\n column = 0 } })
Item |
|
Item1 |
|
Item | 12340000.0 |
IsJString | false |
IsJNumber | true |
IsJBool | false |
IsJNull | false |
IsJObject | false |
IsJArray | false |
{ lines = [|"123.4e5"|]\n position = { line = 1\n column = 0 } }
lines | [ 123.4e5 ] | ||||
position |
|
line | 1 |
column | 0 |
true
false
JNumber 12340000.0 JNumber 12340000.0
In [ ]:
//// test
run jNumber_ "123.4e-5"
|> parserEqual (Success (JNumber 0.001234))
Success (JNumber 0.001234, { lines = [|"123.4e-5"|]\n position = { line = 1\n column = 0 } })
Item |
|
Item1 |
|
Item | 0.001234 |
IsJString | false |
IsJNumber | true |
IsJBool | false |
IsJNull | false |
IsJObject | false |
IsJArray | false |
{ lines = [|"123.4e-5"|]\n position = { line = 1\n column = 0 } }
lines | [ 123.4e-5 ] | ||||
position |
|
line | 1 |
column | 0 |
true
false
JNumber 0.001234 JNumber 0.001234
jArray¶
In [ ]:
let jArray =
let left = pchar '[' .>> spaces
let right = pchar ']' .>> spaces
let comma = pchar ',' .>> spaces
let value = jValue .>> spaces
let values = sepBy value comma
between left values right
|>> JArray
<?> "array"
In [ ]:
//// test
jValueRef <|
choice
[
jNull
jBool
jString
jNumber
jArray
]
In [ ]:
//// test
run jArray "[ 1, 2 ]"
|> parserEqual (Success (JArray [ JNumber 1.0; JNumber 2.0 ]))
Success (JArray [JNumber 1.0; JNumber 2.0], { lines = [|"[ 1, 2 ]"|]\n position = { line = 1\n column = 0 } })
Item |
|
Item1 |
|
Item |
1 |
|
Item | 2.0 |
IsJString | false |
IsJNumber | true |
IsJBool | false |
IsJNull | false |
IsJObject | false |
IsJArray | false |
false
false
false
false
false
true
{ lines = [|"[ 1, 2 ]"|]\n position = { line = 1\n column = 0 } }
lines | [ [ 1, 2 ] ] | ||||
position |
|
line | 1 |
column | 0 |
true
false
JArray [JNumber 1.0; JNumber 2.0] JArray [JNumber 1.0; JNumber 2.0]
In [ ]:
//// test
run jArray "[ 1, 2, ]"
|> parserEqual (
Failure (
"array",
"Unexpected ','",
{ currentLine = "[ 1, 2, ]"; line = 0; column = 6 }
)
)
Failure ("array", "Unexpected ','", { currentLine = "[ 1, 2, ]"\n line = 0\n column = 6 })
Item1 | "array" | ||||||
Item2 | "Unexpected ','" | ||||||
Item3 |
|
currentLine | "[ 1, 2, ]" |
line | 0 |
column | 6 |
false
true
Line:0 Col:6 Error parsing array [ 1, 2, ] ^Unexpected ','
jObject¶
In [ ]:
let jObject =
let left = spaces >>. pchar '{' .>> spaces
let right = pchar '}' .>> spaces
let colon = pchar ':' .>> spaces
let comma = pchar ',' .>> spaces
let key = quotedString .>> spaces
let value = jValue .>> spaces
let keyValue = (key .>> colon) .>>. value
let keyValues = sepBy keyValue comma
between left keyValues right
|>> Map.ofList
|>> JObject
<?> "object"
In [ ]:
jValueRef <|
choice
[
jNull
jBool
jString
jNumber
jArray
jObject
]
In [ ]:
//// test
run jObject """{ "a":1, "b" : 2 }"""
|> parserEqual (
Success (
JObject (
Map.ofList [
"a", JNumber 1.0
"b", JNumber 2.0
]
)
)
)
Success\n (JObject (map [("a", JNumber 1.0); ("b", JNumber 2.0)]),\n { lines = [|"{ "a":1, "b" : 2 }"|]\n position = { line = 1\n column = 0 } })
Item |
|
Item1 |
|
Item |
"b"
|
Item | 2.0 |
IsJString | false |
IsJNumber | true |
IsJBool | false |
IsJNull | false |
IsJObject | false |
IsJArray | false |
false
false
false
false
true
false
{ lines = [|"{ "a":1, "b" : 2 }"|]\n position = { line = 1\n column = 0 } }
lines | [ { "a":1, "b" : 2 } ] | ||||
position |
|
line | 1 |
column | 0 |
true
false
JObject (map [("a", JNumber 1.0); ("b", JNumber 2.0)]) JObject (map [("a", JNumber 1.0); ("b", JNumber 2.0)])
In [ ]:
//// test
run jObject """{ "a":1, "b" : 2, }"""
|> parserEqual (
Failure (
"object",
"Unexpected ','",
{ currentLine = """{ "a":1, "b" : 2, }"""; line = 0; column = 18 }
)
)
Failure ("object", "Unexpected ','", { currentLine = "{ "a":1, "b" : 2, }"\n line = 0\n column = 18 })
Item1 | "object" | ||||||
Item2 | "Unexpected ','" | ||||||
Item3 |
|
currentLine | "{ "a":1, "b" : 2, }" |
line | 0 |
column | 18 |
false
true
Line:0 Col:18 Error parsing object { "a":1, "b" : 2, } ^Unexpected ','
jValue¶
In [ ]:
//// test
let example1 = """{
"name" : "Scott",
"isMale" : true,
"bday" : {"year":2001, "month":12, "day":25 },
"favouriteColors" : ["blue", "green"],
"emptyArray" : [],
"emptyObject" : {}
}"""
run jValue example1
|> parserEqual (
Success (
JObject (
Map.ofList [
"name", JString "Scott"
"isMale", JBool true
"bday", JObject (
Map.ofList [
"year", JNumber 2001.0
"month", JNumber 12.0
"day", JNumber 25.0
]
)
"favouriteColors", JArray [ JString "blue"; JString "green" ]
"emptyArray", JArray []
"emptyObject", JObject Map.empty
]
)
)
)
Success\n (JObject\n (map\n [("bday",\n JObject\n (map\n [("day", JNumber 25.0); ("month", JNumber 12.0);\n ("year", JNumber 2001.0)])); ("emptyArray", JArray []);\n ("emptyObject", JObject (map []));\n ("favouriteColors", ...
Item |
|
Item1 |
|
Item |
"emptyArray" FSI_0026+JValue+JArray
|
|
Item | (empty) |
IsJString | false |
IsJNumber | false |
IsJBool | false |
IsJNull | false |
IsJObject | false |
IsJArray | true |
"emptyObject"
JObject (map [])
Item | (empty) |
IsJString | false |
IsJNumber | false |
IsJBool | false |
IsJNull | false |
IsJObject | true |
IsJArray | false |
"favouriteColors"
JArray [JString "blue"; JString "green"]
Item |
| ||||||
IsJString | false | ||||||
IsJNumber | false | ||||||
IsJBool | false | ||||||
IsJNull | false | ||||||
IsJObject | false | ||||||
IsJArray | true |
"isMale"
JBool true
Item | true |
IsJString | false |
IsJNumber | false |
IsJBool | true |
IsJNull | false |
IsJObject | false |
IsJArray | false |
"name"
JString "Scott"
Item | "Scott" |
IsJString | true |
IsJNumber | false |
IsJBool | false |
IsJNull | false |
IsJObject | false |
IsJArray | false |
false
false
false
false
true
false
{ lines =\n [|"{"; " "name" : "Scott","; " "isMale" : true,";\n " "bday" : {"year":2001, "month":12, "day":25 },";\n " "favouriteColors" : ["blue", "green"],"; " "emptyArray" : [],";\n " "emptyObject" : {}"; "}"|]\n position = { line = 8\n column = 0 } ...
lines | [ {, "name" : "Scott",, "isMale" : true,, "bday" : {"year":2001, "month":12, "day":25 },, "favouriteColors" : ["blue", "green"],, "emptyArray" : [],, "emptyObject" : {}, } ] | ||||
position |
|
line | 8 |
column | 0 |
true
false
JObject (map [("bday", JObject (map [("day", JNumber 25.0); ("month", JNumber 12.0); ("year", JNumber 2001.0)])); ("emptyArray", JArray []); ("emptyObject", JObject (map [])); ("favouriteColors", JArray [JString "blue"; JString "green"]); ("isMale", JBool true); ("name", JString "Scott")]) JObject (map [("bday", JObject (map [("day", JNumber 25.0); ("month", JNumber 12.0); ("year", JNumber 2001.0)])); ("emptyArray", JArray []); ("emptyObject", JObject (map [])); ("favouriteColors", JArray [JString "blue"; JString "green"]); ("isMale", JBool true); ("name", JString "Scott")])
In [ ]:
//// test
let example2 = """{"widget": {
"debug": "on",
"window": {
"title": "Sample Konfabulator Widget",
"name": "main_window",
"width": 500,
"height": 500
},
"image": {
"src": "Images/Sun.png",
"name": "sun1",
"hOffset": 250,
"vOffset": 250,
"alignment": "center"
},
"text": {
"data": "Click Here",
"size": 36,
"style": "bold",
"name": "text1",
"hOffset": 250,
"vOffset": 100,
"alignment": "center",
"onMouseUp": "sun1.opacity = (sun1.opacity / 100) * 90;"
}
}}"""
run jValue example2
|> parserEqual (
Success (
JObject (
Map.ofList [
"widget", JObject (
Map.ofList [
"debug", JString "on"
"window", JObject (
Map.ofList [
"title", JString "Sample Konfabulator Widget"
"name", JString "main_window"
"width", JNumber 500.0
"height", JNumber 500.0
]
)
"image", JObject (
Map.ofList [
"src", JString "Images/Sun.png"
"name", JString "sun1"
"hOffset", JNumber 250.0
"vOffset", JNumber 250.0
"alignment", JString "center"
]
)
"text", JObject (
Map.ofList [
"data", JString "Click Here"
"size", JNumber 36.0
"style", JString "bold"
"name", JString "text1"
"hOffset", JNumber 250.0
"vOffset", JNumber 100.0
"alignment", JString "center"
"onMouseUp", JString "sun1.opacity = (sun1.opacity / 100) * 90;"
]
)
]
)
]
)
)
)
Success\n (JObject\n (map\n [("widget",\n JObject\n (map\n [("debug", JString "on");\n ("image",\n JObject\n (map\n [("alignment", JString "center");\n ("hOffset"...
Item |
|
Item1 |
|
Item |
| |||||||||||||||||||||||||||||||||
IsJString | false | |||||||||||||||||||||||||||||||||
IsJNumber | false | |||||||||||||||||||||||||||||||||
IsJBool | false | |||||||||||||||||||||||||||||||||
IsJNull | false | |||||||||||||||||||||||||||||||||
IsJObject | true | |||||||||||||||||||||||||||||||||
IsJArray | false |
{ lines =\n [|"{"widget": {"; " "debug": "on","; " "window": {";\n " "title": "Sample Konfabulator Widget",";\n " "name": "main_window","; " "width": 500,";\n " "height": 500"; " },"; " "image": {";\n " "src": "Images/Sun.png","; " ...
lines | [ {"widget": {, "debug": "on",, "window": {, "title": "Sample Konfabulator Widget",, "name": "main_window",, "width": 500,, "height": 500, },, "image": {, "src": "Images/Sun.png",, "name": "sun1",, "hOffset": 250,, "vOffset": 250,, "alignment": "center", },, "text": {, "data": "Click Here",, "size": 36,, "style": "bold",, "name": "text1",, "hOffset": 250,, "vOffset": 100,, "alignment": "center",, "onMouseUp": "sun1.opacity = (sun1.opacity / 100) * 90;", }, }} ] | ||||
position |
|
line | 26 |
column | 0 |
true
false
JObject (map [("widget", JObject (map [("debug", JString "on"); ("image", JObject (map [("alignment", JString "center"); ("hOffset", JNumber 250.0); ("name", JString "sun1"); ("src", JString "Images/Sun.png"); ("vOffset", JNumber 250.0)])); ("text", JObject (map [("alignment", JString "center"); ("data", JString "Click Here"); ("hOffset", JNumber 250.0); ("name", JString "text1"); ("onMouseUp", JString "sun1.opacity = (sun1.opacity / 100) * 90;"); ("size", JNumber 36.0); ("style", JString "bold"); ("vOffset", JNumber 100.0)])); ("window", JObject (map [("height", JNumber 500.0); ("name", JString "main_window"); ("title", JString "Sample Konfabulator Widget"); ("width", JNumber 500.0)]))]))]) JObject (map [("widget", JObject (map [("debug", JString "on"); ("image", JObject (map [("alignment", JString "center"); ("hOffset", JNumber 250.0); ("name", JString "sun1"); ("src", JString "Images/Sun.png"); ("vOffset", JNumber 250.0)])); ("text", JObject (map [("alignment", JString "center"); ("data", JString "Click Here"); ("hOffset", JNumber 250.0); ("name", JString "text1"); ("onMouseUp", JString "sun1.opacity = (sun1.opacity / 100) * 90;"); ("size", JNumber 36.0); ("style", JString "bold"); ("vOffset", JNumber 100.0)])); ("window", JObject (map [("height", JNumber 500.0); ("name", JString "main_window"); ("title", JString "Sample Konfabulator Widget"); ("width", JNumber 500.0)]))]))])
In [ ]:
//// test
let example3 = """{
"string": "Hello, \"World\"!",
"escapedString": "This string contains \\/\\\\\\b\\f\\n\\r\\t\\\"\\'",
"number": 42,
"scientificNumber": 3.14e-10,
"boolean": true,
"nullValue": null,
"array": [1, 2, 3, 4, 5],
"unicodeString1": "프리마",
"unicodeString2": "\u0048\u0065\u006C\u006C\u006F, \u0022\u0057\u006F\u0072\u006C\u0064\u0022!",
"specialCharacters": "!@#$%^&*()",
"emptyArray": [],
"emptyObject": {},
"nestedArrays": [[1, 2, 3], [4, 5, 6]],
"object": {
"nestedString": "Nested Value",
"nestedNumber": 3.14,
"nestedBoolean": false,
"nestedNull": null,
"nestedArray": ["a", "b", "c"],
"nestedObject": {
"nestedProperty": "Nested Object Value"
}
},
"nestedObjects": [
{"name": "Alice", "age": 25},
{"name": "Bob", "age": 30}
]
}"""
run jValue example3
|> parserEqual (
Success (
JObject (
Map.ofList [
"string", JString @"Hello, ""World""!"
"escapedString", JString @"This string contains \/\\\b\f\n\r\t\""\'"
"number", JNumber 42.0
"scientificNumber", JNumber 3.14e-10
"boolean", JBool true
"nullValue", JNull
"array", JArray [
JNumber 1.0; JNumber 2.0; JNumber 3.0; JNumber 4.0; JNumber 5.0
]
"unicodeString1", JString "프리마"
"unicodeString2", JString @"Hello, ""World""!"
"specialCharacters", JString "!@#$%^&*()"
"emptyArray", JArray []
"emptyObject", JObject Map.empty
"nestedArrays", JArray [
JArray [ JNumber 1.0; JNumber 2.0; JNumber 3.0 ]
JArray [ JNumber 4.0; JNumber 5.0; JNumber 6.0 ]
]
"object", JObject (
Map.ofList [
"nestedString", JString "Nested Value"
"nestedNumber", JNumber 3.14
"nestedBoolean", JBool false
"nestedNull", JNull
"nestedArray", JArray [JString "a"; JString "b"; JString "c"]
"nestedObject", JObject (
Map.ofList [
"nestedProperty", JString "Nested Object Value"
]
)
]
)
"nestedObjects", JArray [
JObject (Map.ofList [ "name", JString "Alice"; "age", JNumber 25.0 ])
JObject (Map.ofList [ "name", JString "Bob"; "age", JNumber 30.0 ])
]
]
)
)
)
Success\n (JObject\n (map\n [("array",\n JArray\n [JNumber 1.0; JNumber 2.0; JNumber 3.0; JNumber 4.0; JNumber 5.0]);\n ("boolean", JBool true); ("emptyArray", JArray []);\n ("emptyObject", JObject (map []));\n ("escapedString", JString "This s...
Item |
|
Item1 |
|
Item |
"boolean" FSI_0026+JValue+JBool
|
|
Item | true |
IsJString | false |
IsJNumber | false |
IsJBool | true |
IsJNull | false |
IsJObject | false |
IsJArray | false |
"emptyArray"
JArray []
Item | (empty) |
IsJString | false |
IsJNumber | false |
IsJBool | false |
IsJNull | false |
IsJObject | false |
IsJArray | true |
"emptyObject"
JObject (map [])
Item | (empty) |
IsJString | false |
IsJNumber | false |
IsJBool | false |
IsJNull | false |
IsJObject | true |
IsJArray | false |
"escapedString"
JString "This string contains \/\\\b\f\n\r\t\"\'"
Item | "This string contains \/\\\b\f\n\r\t\"\'" |
IsJString | true |
IsJNumber | false |
IsJBool | false |
IsJNull | false |
IsJObject | false |
IsJArray | false |
"nestedArrays"
JArray\n [JArray [JNumber 1.0; JNumber 2.0; JNumber 3.0];\n JArray [JNumber 4.0; JNumber 5.0; JNumber 6.0]]
Item |
| ||||||
IsJString | false | ||||||
IsJNumber | false | ||||||
IsJBool | false | ||||||
IsJNull | false | ||||||
IsJObject | false | ||||||
IsJArray | true |
"nestedObjects"
JArray\n [JObject (map [("age", JNumber 25.0); ("name", JString "Alice")]);\n JObject (map [("age", JNumber 30.0); ("name", JString "Bob")])]
Item |
| ||||||
IsJString | false | ||||||
IsJNumber | false | ||||||
IsJBool | false | ||||||
IsJNull | false | ||||||
IsJObject | false | ||||||
IsJArray | true |
"nullValue"
JNull
IsJString | false |
IsJNumber | false |
IsJBool | false |
IsJNull | true |
IsJObject | false |
IsJArray | false |
"number"
JNumber 42.0
Item | 42.0 |
IsJString | false |
IsJNumber | true |
IsJBool | false |
IsJNull | false |
IsJObject | false |
IsJArray | false |
"object"
JObject\n (map\n [("nestedArray", JArray [JString "a"; JString "b"; JString "c"]);\n ("nestedBoolean", JBool false); ("nestedNull", JNull);\n ("nestedNumber", JNumber 3.14);\n ("nestedObject",\n JObject (map [("nestedProperty", JString "Nested Object Value")]));\n ("ne...
Item |
| |||||||||||||||||||||
IsJString | false | |||||||||||||||||||||
IsJNumber | false | |||||||||||||||||||||
IsJBool | false | |||||||||||||||||||||
IsJNull | false | |||||||||||||||||||||
IsJObject | true | |||||||||||||||||||||
IsJArray | false |
"scientificNumber"
JNumber 3.14e-10
Item | 3.14e-10 |
IsJString | false |
IsJNumber | true |
IsJBool | false |
IsJNull | false |
IsJObject | false |
IsJArray | false |
"specialCharacters"
JString "!@#$%^&*()"
Item | "!@#$%^&*()" |
IsJString | true |
IsJNumber | false |
IsJBool | false |
IsJNull | false |
IsJObject | false |
IsJArray | false |
"string"
JString "Hello, "World"!"
Item | "Hello, "World"!" |
IsJString | true |
IsJNumber | false |
IsJBool | false |
IsJNull | false |
IsJObject | false |
IsJArray | false |
"unicodeString1"
JString "프리마"
Item | "프리마" |
IsJString | true |
IsJNumber | false |
IsJBool | false |
IsJNull | false |
IsJObject | false |
IsJArray | false |
"unicodeString2"
JString "Hello, "World"!"
Item | "Hello, "World"!" |
IsJString | true |
IsJNumber | false |
IsJBool | false |
IsJNull | false |
IsJObject | false |
IsJArray | false |
false
false
false
false
true
false
{ lines =\n [|"{"; " "string": "Hello, \"World\"!",";\n " "escapedString": "This string contains \\/\\\\\\b\\f\\n\\r\\t\\\"\\'",";\n " "number": 42,"; " "scientificNumber": 3.14e-10,"; " "boolean": true,";\n " "nullValue": null,"; " "array": [1, 2, 3, 4, 5],";\n " "unicodeS...
lines | [ {, "string": "Hello, \"World\"!",, "escapedString": "This string contains \\/\\\\\\b\\f\\n\\r\\t\\\"\\'",, "number": 42,, "scientificNumber": 3.14e-10,, "boolean": true,, "nullValue": null,, "array": [1, 2, 3, 4, 5],, "unicodeString1": "프리마",, "unicodeString2": "\u0048\u0065\u006C\u006C\u006F, \u0022\u0057\u006F\u0072\u006C\u0064\u0022!",, "specialCharacters": "!@#$%^&*()",, "emptyArray": [],, "emptyObject": {},, "nestedArrays": [[1, 2, 3], [4, 5, 6]],, "object": {, "nestedString": "Nested Value",, "nestedNumber": 3.14,, "nestedBoolean": false,, "nestedNull": null,, "nestedArray": ["a", "b", "c"],, "nestedObject": {, "nestedProperty": "Nested Object Value", }, },, "nestedObjects": [, {"name": "Alice", "age": 25},, {"name": "Bob", "age": 30}, ], } ] | ||||
position |
|
line | 29 |
column | 0 |
true
false
JObject (map [("array", JArray [JNumber 1.0; JNumber 2.0; JNumber 3.0; JNumber 4.0; JNumber 5.0]); ("boolean", JBool true); ("emptyArray", JArray []); ("emptyObject", JObject (map [])); ("escapedString", JString "This string contains \/\\\b\f\n\r\t\"\'"); ("nestedArrays", JArray [JArray [JNumber 1.0; JNumber 2.0; JNumber 3.0]; JArray [JNumber 4.0; JNumber 5.0; JNumber 6.0]]); ("nestedObjects", JArray [JObject (map [("age", JNumber 25.0); ("name", JString "Alice")]); JObject (map [("age", JNumber 30.0); ("name", JString "Bob")])]); ("nullValue", JNull); ("number", JNumber 42.0); ...]) JObject (map [("array", JArray [JNumber 1.0; JNumber 2.0; JNumber 3.0; JNumber 4.0; JNumber 5.0]); ("boolean", JBool true); ("emptyArray", JArray []); ("emptyObject", JObject (map [])); ("escapedString", JString "This string contains \/\\\b\f\n\r\t\"\'"); ("nestedArrays", JArray [JArray [JNumber 1.0; JNumber 2.0; JNumber 3.0]; JArray [JNumber 4.0; JNumber 5.0; JNumber 6.0]]); ("nestedObjects", JArray [JObject (map [("age", JNumber 25.0); ("name", JString "Alice")]); JObject (map [("age", JNumber 30.0); ("name", JString "Bob")])]); ("nullValue", JNull); ("number", JNumber 42.0); ...])