# JSON Parser ```idris module Parser.JSON import public Parser import public Parser.Numbers import Structures.Dependent.DList ``` ## JSON components Types a JSON value is allowed to have ```idris 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 ```idris 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 ``` ## 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. ```idris 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 ```idris whitespace : Parser Char whitespace = do pnote "Whitespace character" result <- parseChar (\x => any (== x) $ the (List _) [' ', '\n', '\r', '\t']) case result of GotChar char => pure char GotError err => throwParseError "Expected whitespace, got: \{show err}" EndOfInput => throwParseError "End of Input" ``` Convenience function ```idris dpairize : {t : JSONType} -> Parser (JSONValue t) -> Parser (t' : JSONType ** JSONValue t') dpairize x = do x <- x pure (_ ** x) ``` Top level json value parser ```idris 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 ```idris 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 _ <- parseExactChar ':' (typ ** val) <- value pure (typ ** (key, val)) restKeyValue : Parser (t : JSONType ** (String, JSONValue t)) restKeyValue = do _ <- parseExactChar ',' keyValue pairs : Parser (List1 (t : JSONType ** (String, JSONValue t))) pairs = do first <- keyValue rest <- many restKeyValue pure $ first ::: rest occupiedObject : Parser (JSONValue TObject) occupiedObject = do val <- delimited '{' '}' pairs let (types ** xs) = DList.fromList (forget val) pure $ VObject xs ``` ```idris 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 _ <- parseExactChar ',' 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 ``` ```idris string = do pnote "JSON String" str <- parseString $ delimited '"' '"' (many stringCharacter) pure $ VString str where -- TODO: Handle control characters properly stringCharacter : Parser Char stringCharacter = do result <- parseChar (not . (== '"')) case result of GotChar char => pure char GotError err => throwParseError "Expected string character, got \{show err}" EndOfInput => throwParseError "Unexpected end of input" ``` ```idris number = do pnote "JSON Number" d <- double pure $ VNumber d ``` ```idris 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 ``` ```idris null = do pnote "JSON null" _ <- exactString "null" pure VNull ``` ## Unit tests Quick smoke test ```idris -- @@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 ```