Created
January 31, 2022 17:22
-
-
Save Revolucent/fb3d03ec08e6e5a10c24afcc03100af6 to your computer and use it in GitHub Desktop.
Some combinators which implement a DSL for querying and deserializing Json from Strings. Extend or alter to your taste.
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
typealias Deserializer<I, O> = (I) -> O? | |
typealias JsonDeserializer<I, O> = (Json) -> Deserializer<I, O> | |
typealias JsonElementDeserializer = JsonDeserializer<JsonElement, JsonElement> | |
private fun <I, O> const(deserializer: Deserializer<I, O>): JsonDeserializer<I, O> = { | |
deserializer | |
} | |
infix fun <A, B, C> JsonDeserializer<A, B>.j( | |
next: JsonDeserializer<B, C> | |
): JsonDeserializer<A, C> = { json -> | |
{ a -> this(json)(a)?.let(next(json)) } | |
} | |
inline fun <reified T> js(json: Json): Deserializer<String, T> = | |
json::decodeFromString | |
inline fun <reified T> je(json: Json): Deserializer<JsonElement, T> = | |
json::decodeFromJsonElement | |
fun jse(json: Json): Deserializer<String, JsonElement> = | |
js(json) | |
fun ja(index: Int): JsonElementDeserializer = const { a -> | |
(a as? JsonArray)?.getOrNull(index) | |
} | |
fun jo(vararg keys: String): JsonElementDeserializer = const { o -> | |
var element: JsonElement? = o | |
for (key in keys) { | |
element = (element as? JsonObject)?.get(key) | |
} | |
element | |
} | |
infix fun <I> JsonDeserializer<I, JsonElement>.j(key: String): JsonDeserializer<I, JsonElement> = | |
this j jo(key) | |
infix fun <I> JsonDeserializer<I, JsonElement>.j(index: Int): JsonDeserializer<I, JsonElement> = | |
this j ja(index) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
How to use this? Imagine we have the following JSON:
We also have this class:
Let's say we just want the list of economists and nothing else. Here's how we do it:
If we get rid of the
typealias
, this gives us a function of the form(Json) -> ((String) -> List<Economist>?)
. In other words, it's a function which produces a function. Passing in aJson
instance, we now get back a simple function which takes us fromString
toList<Economist>
using the givenJson
instance.The
jse
function converts aString
intoJsonElement
. Theje
function deserializes aJsonElement
into a class. Thej
combinator glues everything together. It's exactly equivalent to the|
operator in shell scripting, where the output of the previous command is piped as the input of the next. However, ifj
is followed by a string and the output of the preceding command is aJsonElement
,j
assumes theJsonElement
is aJsonObject
and attempt to get the value of that attribute as aJsonElement
. If any part of that fails,null
is returned and the entire pipeline stops, producing thenull
. A very similar thing happens when thej
is followed by an integer. Most pipelines start with::jse
, have one or more uses ofj
, and end with::je
.