Kotlin JSON wrapper for Jackson


#1

Greeting all,

I’ve done a proof of concept JSON wrapper for Jackson based on Play’s JSON support and ended up with: https://github.com/andrewoma/kson

It’s the result of an afternoon’s hacking, so it’s definitely not a production library. However, it seemed promising, so thought I’d publish it as is.

Here’s an example from https://github.com/andrewoma/kson/blob/master/kson/src/test/java/com/github/andrewoma/kson/JsValueTest.kt

val value = JsObject(
           "firstName" to "Andrew".json,
           "lastName" to "O'Malley".json,
           "age" to 21.json,
           "adult" to true.json,
           "address" to JsObject(
                   "number" to "88".json,
                   "street" to "Chapel Street".json,
                   "suburb" to "Windsor".json,
                   "state" to "VIC".json,
                   "postCode" to "3181".json
           ),
           "pets" to JsArray(listOf(
                   JsObject(
                           "kind" to "dog".json,
                           "name" to "Rover".json
                   ),
                   JsObject(
                           "kind" to "cat".json,
                           "name" to "Kitty".json
                   )
           )
           )
  )

I’m still keen to experiment with some kind of builder syntax (or hear of other experiments - I know about klaxon).

A question for the Kotlin devs: is the conversion operator still planned for Kotlin? See http://youtrack.jetbrains.com/issue/KT-1752#comment=27-318887

If feel uneasy adding extension functions and properties to base types like String. A conversion operator (with the correct precedence) would allow things like the following without possible namespace clashes:

fun String.convert() : JsValue = JsString(this)


Then the syntax above would become (without a chance of clashing with some other library):

"firstName" to ~"Andrew"

Cheers,
Andrew


#2

Minor suggestions:

  • JsArray could take a vararg parameter, not a list
  • Maybe shorten ".json" to ".js"

The conversion operator we are not yet sure about. I'd say in this case having a ".js" suffix would be nearly the same as the conversion operator


#3

Hi Andrey,

Thanks for the feedback and update on the conversion operator status.

I actually tried vararg for JsArray first and it does make the example above prettier.

However, it means using JsArray(*list.copyToArrary()) when using it for non-builder use-cases and I figure these would be the norm for arrays. I guess I’ll add a function to support both styles.

Off-topic: I think you made a great call to drop full-blown pattern matching in Kotlin. I ported some of the serialization code from Scala that used pattern matching extensively and I think the Kotlin port is more readable without it.

Cheers,
Andrew


#4

However, it means using JsArray(*list.copyToArrary()) when using it for non-builder use-cases and I figure these would be the norm for arrays. I guess I'll add a function to support both styles.

Yes, I think you can provide two overloaded versions to avoid this problem.

Off-topic: I think you made a great call to drop full-blown pattern matching in Kotlin. I ported some of the serialization code from Scala that used pattern matching extensively and I think the Kotlin port is more readable without it.

Thanks for the feedback! Could you provide us with a side-by-side Scala and Kotlin code for comparison?


#5

Hi Andrey,

However, it means using JsArray(*list.copyToArrary()) when using it for non-builder use-cases and I figure these would be the norm for arrays. I guess I'll add a function to support both styles.

Yes, I think you can provide two overloaded versions to avoid this problem.

Yep, I've provided "secondary constructors" via functions as follows:

public fun JsArray(vararg values: JsValue): JsValue = JsArray(values.toList())

public data class JsArray(val values: List<JsValue>) : JsValue()


I’ve also renamed .json to .js and agree it’s better (and committed to github). So the current version looks like this:

  val value = JsObject(            "firstName" to "Andrew".js,            "lastName" to "O'Malley".js,            "age" to 21.js,            "adult" to true.js,            "address" to JsObject(                    "number" to "88".js,                    "street" to "Chapel Street".js,                    "suburb" to "Windsor".js,                    "state" to "VIC".js,                    "postCode" to "3181".js            ),            "pets" to JsArray(                    JsObject(                            "kind" to "dog".js,                            "name" to "Rover".js                    ),                    JsObject(                            "kind" to "cat".js,                            "name" to "Kitty".js                    )            )   )

Off-topic: I think you made a great call to drop full-blown pattern matching in Kotlin. I ported some of the serialization code from Scala that used pattern matching extensively and I think the Kotlin port is more readable without it.

Thanks for the feedback! Could you provide us with a side-by-side Scala and Kotlin code for comparison?

Kotlin: https://github.com/andrewoma/kson/blob/master/kson/src/main/java/com/github/andrewoma/kson/KsonModule.kt Scala: https://github.com/playframework/playframework/blob/master/framework/src/play-json/src/main/scala/play/api/libs/json/JsValue.scala

Compare the deserialize methods:

  • The cool thing is both are completely type safe and no casting is required
  • When porting, I found the Scala one confusing to unravel:
    • It matches on the same token multiple times (for expected and error cases)
    • It destructures lists as part of the matching, effectively popping off the stack
    • The function return is combination of matching plus guards, plus defaulting
    • The matching introduces a bunch of new variable names in the match, but the Kotlin version can just use the same variable (automatically cast)


Naturally, I’m biased as I ported the Kotlin one, but I find it easier to follow without all the matching.

Cheers,
Andrew


#6

Thanks again


#7

Probably this blogpost will be interesting for You, if you didn't see it already.


#8

Thanks for that - I hadn't seen it. There's some interesting ideas in there.

I might try to reproduce a couple and see how they fare.

Some certainly look cleaner than my experiment, but I have some doubts as to whether they guarantee integrity, allow traversal, compose etc.

Cheers,
Andrew