I've started working on a library to help JSON cre...
# codereview
d
I've started working on a library to help JSON creation. It has 2 parts: 1. Json building DSL (which I'm calling
kson
for now). (Example code here) 2. Type safe Json wrapper with JsonSchema generation and validation. (Example code here). This feature was heavily inspired by Exposed DAO. I will add examples of the DSLs in 🧵 too. Please let me know any thoughts or suggestions.
Type safe json schema example.cpp
Kson DSL for building JSON
c
There are many things named
kson
https://search.maven.org/search?q=kson
If you're making your own JSON library, why the dependency on KotlinX.Serialization?
d
Proof of concept for the type safety aspect. Also, the name is a placeholder.
Any thoughts on the dsl itself?
c
I don't like the usage of
/=
here. If possible, I try to avoid using operators for something that symbol is not closely associated with. Here, I would probably just use
set
I would also use this to simplify the nullable case. In your type unsafe example, you're using
?.field()
which is the opposite order as the rest of the DSL. Instead, you could have something like:
Copy code
kson {
    …
    "isMarried" set (spouse != null)
    "spouse" setNotNull spouse
    "spouseDetails" setNotNull details
    …
}
an example of this pattern in one of my DSLs: https://opensavvy.gitlab.io/ktmongo/api-docs/dsl/opensavvy.ktmongo.dsl.expr/-filter-expression/eq-not-null.html?query=infix%20fun%20%3CV%3E%20Field%3CT,%20V%3E.eqNotNull(value:%20V?) In general, I'm not a fan of the existence of unsafe DSLs, but sometimes they are necessary…
About the typesafe DSL: it looks quite verbose. The
companion object Schema
is nice, but the outer class is just boilerplate. Maybe have something like:
Copy code
Example(…)[Schema.foo]
? but that's verbose on the call-site, which is not necessarily better
d
Thanks for the feedback! I like the idea of "set" and "setNotNull", I might incorporate that. I only chose `/=" because I once saw it in another similar library.
c
Alternative design:
Copy code
object Example : CompoundClass<Example>({ Example(it) }) {
    val foo = string("foo")
    val bar = int("bar")
    val baz = ExampleSubObject("baz")
    val qux = jsonArray("qux").withItems(string)
    val quux = union("quux")
    val quuxString = quux[string].nullable()
    val quuxInt = quux[int]
    val quuxObj = quux[jsonObject]
}

class WithData(private val wrapper: JsonObject) {
    operator fun Field.invoke() = wrapper[this]
}

fun withData(obj: JsonObject, block: WithData() -> Unit) {
    WithData(obj).apply(block)
}

val wrapper: JsonObject = TODO()

withData(wrapper) {
    println(Example.foo())
}
This would be similar to the way I implemented
suspend
scoped properties here: https://opensavvy.gitlab.io/groundwork/prepared/api-docs/suite/opensavvy.prepared.suite/-prepared/index.html
d
As for the schema and its outer class, they serve separate purposes, and I think it's okay to have them be a bit verbose (it is similar to the Table vs Entity in Exposed.)