JSON Parser
module Parser.JSON
import public Parser
import public Parser.Numbers
import Control.Monad.Eval
import Structures.Dependent.DList
JSON components
Types a JSON value is allowed to have
public export
data JSONType : Type where
TObject : JSONType
TArray : JSONType
TString : JSONType
TNumber : JSONType
TBool : JSONType
TNull : JSONType
%runElab derive "JSONType" [Generic, Meta, Eq, Ord, Show, DecEq]
%name JSONType type, type2, type3
A JSON value indexed by its type
public export
data JSONValue : JSONType -> Type where
VObject : {types : List JSONType}
-> DList JSONType (\t => (String, JSONValue t)) types -> JSONValue TObject
VArray : {types : List JSONType}
-> DList JSONType JSONValue types -> JSONValue TArray
VString : (s : String) -> JSONValue TString
VNumber : (d : Double) -> JSONValue TNumber
VBool : (b : Bool) -> JSONValue TBool
VNull : JSONValue TNull
%name JSONValue value, value2, value3
JSON Functions
foldl
Analog for consuming a JSON structure by values. Ignores the keys in
objects.
export
dFoldL : {t : JSONType}
-> (acc -> (type : JSONType) -> (val : JSONValue type) -> acc)
-> acc -> JSONValue t -> acc
dFoldL f acc' (VObject xs) =
let recur : acc -> (v : JSONType) -> (String, JSONValue v) -> acc
recur acc' v (key, value) = dFoldL f acc' value
in DList.dFoldL recur acc' xs
dFoldL f acc' (VArray xs) =
let recur : acc -> (v : JSONType) -> JSONValue v -> acc
recur acc' v value = dFoldL f acc' value
in DList.dFoldL recur acc' xs
dFoldL f acc (VString s) = f acc _ (VString s)
dFoldL f acc (VNumber d) = f acc _ (VNumber d)
dFoldL f acc (VBool b) = f acc _ (VBool b)
dFoldL f acc VNull = f acc _ VNull
Look up a property in an object
export
getProperty : (prop : String) -> (object : JSONValue TObject)
-> Maybe (type : JSONType ** JSONValue type)
getProperty prop (VObject xs) =
case dFind (\_, (key, _) => key == prop) xs of
Nothing => Nothing
Just (type ** (_, val)) => Just (type ** val)
Return the values from an object.
export
getValues : (object : JSONValue TObject)
-> (types : List JSONType ** DList JSONType JSONValue types)
getValues (VObject xs) =
dMap' (\t, (k, v) => (t ** v)) xs
Recursively apply a filter to a JSON structure.
export
dFilter : (f : (type : JSONType) -> (val : JSONValue type) -> Bool)
-> {t : JSONType} -> JSONValue t -> Maybe (JSONValue t)
dFilter f value = eval $ dFilter' f value
where
dFilter' : (f : (type : JSONType) -> (val : JSONValue type) -> Bool)
-> {t : JSONType} -> JSONValue t -> Eval $ Maybe (JSONValue t)
dFilter' f (VObject xs) = do
True <- pure $ f _ (VObject xs)
| _ => pure Nothing
let xs = toList xs
xs : List (Maybe (x : JSONType ** (String, JSONValue x))) <-
traverse
(\(t ** (k, v)) => do
Just v <- dFilter' f v
| _ => pure Nothing
pure $ Just (t ** (k, v)))
xs
let (_ ** xs) : (t : List JSONType ** DList _ _ t) =
fromList $ catMaybes xs
pure . Just $ VObject xs
dFilter' f (VArray xs) = do
True <- pure $ f _ (VArray xs)
| _ => pure Nothing
let xs = toList xs
xs : List (Maybe (x : JSONType ** JSONValue x)) <-
traverse
(\(t ** v) => do
Just v <- dFilter' f v
| _ => pure Nothing
pure $ Just (t ** v))
xs
let (_ ** xs) : (t : List JSONType ** DList _ _ t) =
fromList $ catMaybes xs
pure . Just $ VArray xs
dFilter' f x =
pure $ case f _ x of
False => Nothing
True => Just x
Parsers
We are going to get mutually recursive here. Instead of using a mutual
block,
we will use the more modern style of declaring all our types ahead of our
definitions.
export
object : Parser (JSONValue TObject)
export
array : Parser (JSONValue TArray)
export
string : Parser (JSONValue TString)
export
number : Parser (JSONValue TNumber)
export
bool : Parser (JSONValue TBool)
export
null : Parser (JSONValue TNull)
Define a whitespace
character class based on the json specifications
whitespace : Parser Char
whitespace = do
pnote "Whitespace character"
theseChars [' ', '\n', '\r', '\t']
Convenience function
dpairize : {t : JSONType} ->
Parser (JSONValue t) -> Parser (t' : JSONType ** JSONValue t')
dpairize x = do
x <- x
pure (_ ** x)
Top level json value parser
export
value : Parser (t : JSONType ** JSONValue t)
value = do
pnote "JSON Value"
surround whitespace $ oneOfE
"Expected JSON Value"
[
dpairize object
, dpairize array
, dpairize string
, dpairize number
, dpairize bool
, dpairize null
]
Now go through our json value types
object = do
pnote "JSON Object"
oneOfE
"Expected Object"
[emptyObject, occupiedObject]
where
emptyObject : Parser (JSONValue TObject)
emptyObject = do
delimited '{' '}' (nom whitespace)
pure $ VObject Nil
keyValue : Parser (t : JSONType ** (String, JSONValue t))
keyValue = do
VString key <- surround whitespace string
_ <- charExact ':'
(typ ** val) <- value
pure (typ ** (key, val))
restKeyValue : Parser (t : JSONType ** (String, JSONValue t))
restKeyValue = do
_ <- charExact ','
keyValue
pairs : Parser (List1 (t : JSONType ** (String, JSONValue t)))
pairs = do
first <- keyValue
rest <- many restKeyValue
-- TODO: headTail combinator for this
pure $ first ::: rest
occupiedObject : Parser (JSONValue TObject)
occupiedObject = do
val <- delimited '{' '}' pairs
let (types ** xs) = DList.fromList (forget val)
pure $ VObject xs
array = do
pnote "JSON Array"
oneOfE
"Expected Array"
[emptyArray, occupiedArray]
where
emptyArray : Parser (JSONValue TArray)
emptyArray = do
delimited '[' ']' (nom whitespace)
pure $ VArray Nil
restValue : Parser (t : JSONType ** JSONValue t)
restValue = do
_ <- charExact ','
value
values : Parser (List1 (t : JSONType ** JSONValue t))
values = do
first <- value
rest <- many restValue
pure $ first ::: rest
occupiedArray : Parser (JSONValue TArray)
occupiedArray = do
xs <- delimited '[' ']' values
let (types ** xs) = DList.fromList (forget xs)
pure $ VArray xs
string = do
pnote "JSON String"
str <- liftString $ delimited '"' '"' (many stringCharacter)
pure $ VString str
where
-- TODO: Handle control characters properly
stringCharacter : Parser Char
stringCharacter = do
result <- charPredicate (not . (== '"'))
case result of
GotChar char => pure char
GotError err =>
throwParseError "Expected string character, got \{show err}"
EndOfInput => throwParseError "Unexpected end of input"
number = do
pnote "JSON Number"
d <- double
pure $ VNumber d
bool = do
pnote "JSON Bool"
oneOfE
"Expected Bool"
[true, false]
where
true : Parser (JSONValue TBool)
true = do
_ <- exactString "true"
pure $ VBool True
false : Parser (JSONValue TBool)
false = do
_ <- exactString "false"
pure $ VBool False
null = do
pnote "JSON null"
_ <- exactString "null"
pure VNull
Unit tests
Quick smoke test
-- @@test JSON Quick Smoke
quickSmoke : IO Bool
quickSmoke = do
let input = "{\"string\":\"string\",\"number\":5,\"true\":true,\"false\":false,\"null\":null,\"array\":[1,2,3]}"
putStrLn input
Right (type ** parsed) <- runFirstIODebug value input
| Left err => do
printLn err
pure False
putStrLn "Input: \{input}\nOutput: \{show type} -> \{show parsed}"
let reference_object =
VObject [
("string", VString "string")
, ("number", VNumber 5.0)
, ("true", VBool True)
, ("false", VBool False)
, ("null", VNull)
, ("array", VArray [
VNumber 1.0
, VNumber 2.0
, VNumber 3.0
])
]
case type of
TObject => pure $ parsed == reference_object
_ => pure False