Package & docs
Check out allenap/elm-json-decode-broken. It uses elm/parser to construct the parser. I put “broken” in the name to make you think twice before using it, but also because it can parse “broken” JSON.
Parsing invalid JSON
On the face of it I built something pointless: a JSON parser in Elm. After all, Json.Decode
in elm/json makes use of JSON.parse
, which is native code, battle tested, fast. But it won’t parse broken JSON. In my particular case, that was JSON with strings containing carriage returns and new lines. These must be escaped in proper JSON, and JSON.parse
and Json.Decode.decodeString
rejected the data I needed to work with.
The right thing to do was talk to the people who were generating the invalid JSON and ask them to fix it, and I am in touch. In the meantime I use this parser to work with their data. I used the situation as an excuse to learn how to use elm/parser which was arguably more interesting than working on the end product.
An example
Below is how I used it for my particular situation. The parseAndDecode
function is almost a drop-in replacement for Json.Decode.decodeString
(the error types are different). You can see the basic mechanism for creating a custom parser.
import Json.Decode as Decode exposing (Decoder)
import Json.Decode.Broken as Broken
import Json.Encode as Encode
import Parser exposing ((|.), (|=), Parser)
relaxedStringUnescaped : Parser String
relaxedStringUnescaped =
Parser.oneOf
[ Broken.unescaped
, Parser.symbol "\r" |> Broken.yields "\r"
, Parser.symbol "\n" |> Broken.yields "\n"
]
relaxedStringLiteral : Parser String
relaxedStringLiteral =
Broken.stringLiteral relaxedStringUnescaped Broken.escape
relaxedString : Parser Encode.Value
relaxedString =
Parser.map Encode.string relaxedStringLiteral
relaxedStringConfig : Broken.Config
relaxedStringConfig =
case Broken.defaultConfig of
Broken.Config c ->
Broken.Config { c | string = relaxedString }
parseAndDecode : Decoder a -> String -> Result String a
parseAndDecode decoder json =
case Broken.parseWith relaxedStringConfig json of
Ok value ->
Decode.decodeValue decoder value
|> Result.mapError Decode.errorToString
Err error ->
Err <| Parser.deadEndsToString error