-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
update readme with some info about the decoder package
- Loading branch information
Showing
2 changed files
with
99 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,7 @@ | ||
# Kotlin JSON parser (from scratch) | ||
|
||
UPDATE: now also includes some nice [decoder combinators](#now-with-decoder-combinators) | ||
|
||
[](https://github.com/konnik/kotlin-json-parser/actions/workflows/gradle.yml) | ||
|
||
This is a tiny but fully functional JSON parser I hacked together in Kotlin in a | ||
|
@@ -20,8 +22,97 @@ 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 checkout my other JSON parsers written in [Haskell](https://github.com/konnik/haskell-json-parser/) | ||
Please also checkout my other JSON parsers written in [Haskell](https://github.com/konnik/haskell-json-parser/) | ||
and [Clojure](https://github.com/konnik/clojure-json-parser/). | ||
|
||
Happy parsing! | ||
|
||
## Now with decoder combinators | ||
|
||
After finishing the parser I really wanted to have a go at implementing something | ||
similar to the [Elm JSON Decode package](https://package.elm-lang.org/packages/elm/json/latest/Json.Decode). | ||
|
||
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: | ||
|
||
```json | ||
{ | ||
"type": "guest", | ||
"displayName": "Guest123" | ||
} | ||
``` | ||
|
||
Registered user: | ||
|
||
```json | ||
// registeredJson | ||
{ | ||
"type": "registered", | ||
"id": 42, | ||
"alias": "mrsmith", | ||
"email": "[email protected]", | ||
"phone": null | ||
} | ||
``` | ||
|
||
Let's say we want to decode users into the following data structure: | ||
|
||
```kotlin | ||
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 | ||
} | ||
``` | ||
|
||
One way to define a decoder for `User` objects would be like this: | ||
|
||
```kotlin | ||
val userDecoder: Decoder<User> = | ||
field("type", 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") | ||
} | ||
} | ||
``` | ||
|
||
This decoder can now be used to parse and decode the users using the `decodeJson` function: | ||
|
||
```kotlin | ||
val guest = decodeJson(guestJson, userDecoder) | ||
println(guest) | ||
|
||
val registered = decodeJson(registeredJson, userDecoder) | ||
println(registered) | ||
``` | ||
|
||
This will render the output: | ||
|
||
```text | ||
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](https://package.elm-lang.org/packages/elm/core/latest/Result). | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -167,15 +167,15 @@ class ParserKtTest { | |
field("type", str).andThen { type -> | ||
when (type) { | ||
"guest" -> map( | ||
field("displayName", str), | ||
field("displayName") of str, | ||
User::Guest | ||
) | ||
|
||
"registered" -> map( | ||
field("id", int), | ||
field("alias", str), | ||
field("email", str), | ||
field("phone", nullable(str)), | ||
field("id") of int, | ||
field("alias") of str, | ||
field("email") of str, | ||
field("phone") of nullable(str), | ||
User::Registered | ||
) | ||
|
||
|
@@ -184,7 +184,9 @@ class ParserKtTest { | |
} | ||
|
||
val guest = decodeJson(guestJson, userDecoder) | ||
println(guest) | ||
val registered = decodeJson(registeredJson, userDecoder) | ||
println(registered) | ||
|
||
assertEquals(Ok(User.Guest("Guest123")), guest) | ||
assertEquals(Ok(User.Registered(42, "mrsmith", "[email protected]", null)), registered) | ||
|