UPDATE: now also includes some nice decoder combinators
This is a tiny but fully functional JSON parser I hacked together in Kotlin in a day just for the fun of it. One of the inspirations was Graham Hutton's nice introduction to functional parsing on YouTube.
My main goal was to build a fp-style recursive descent parser exploring Kotlin's support for functional programming techniques and I do like how it turned out. I think the code is pretty readable and the different parsers for the grammar reads almost exactly as the corresponding grammar rules on www.json.org.
Note that I have not made any effort on making it perform well and I haven't benchmarked it at all. It's probably pretty slow, it's just a toy project after all...
The source is in a single file and under 200 lines (not counting comments and blanks) and can easily be copied into your own project if you want to use it.
Suggestions are welcome if you think something can be done in a better way.
Please also check out my other JSON parsers written in Haskell and Clojure.
Happy parsing!
After finishing the parser I really wanted to have a go at implementing something similar to the Elm JSON Decode package.
I really like the Elm way of decoding JSON by describing the expected data structure using decoder combinators and mapping the data to the applications domain model.
My implementation have almost all the features of the Elm package and I think it turned out to be quite nice.
For example consider the following two JSON objects describing two different types of users, guests and registered users:
Guest:
{
"type": "guest",
"displayName": "Guest123"
}
Registered user:
{
"type": "registered",
"id": 42,
"alias": "mrsmith",
"email": "[email protected]",
"phone": null
}
Let's say we want to decode guests and registered users into the following data structure:
sealed interface User {
data class Guest(
val displayName: String
) : User
data class Registered(
val id: Int,
val alias: String,
val email: String,
val phone: String?
) : User
}
First we need to define a Decoder
for User
objects like this:
val userDecoder: Decoder<User> =
(field("type") of str).andThen { type ->
when (type) {
"guest" -> map(
field("displayName") of str,
User::Guest
)
"registered" -> map(
field("id") of int,
field("alias") of str,
field("email") of str,
field("phone") of nullable(str),
User::Registered
)
else -> fail("Invalid type: $type")
}
}
We can now use userDecoder
to parse and decode the JSON data using the decodeJson
function:
val guest = decodeJson(guestJson, userDecoder)
println(guest)
val registered = decodeJson(registeredJson, userDecoder)
println(registered)
This will render the output:
Ok(value=Guest(displayName=Guest123))
Ok(value=Registered(id=42, alias=mrsmith, [email protected], phone=null))
Note that I had to implement a simple Result
type too, of course also heavily inspired
by the Result type in Elm.