klaxon icon indicating copy to clipboard operation
klaxon copied to clipboard

DSL Builders only accept primitives, and are somewhat difficult to "build"

Open ejektaflex opened this issue 4 years ago • 6 comments

Building JSON is a bit difficult with Klaxon, since the DSL for building them uses nesting to create complex objects. While that's fine for Java, it would be more idiomatic to use a more "builder" style for creating JsonObjects.

This is how you make a JsonObject in Klaxon:

json {
  obj("colors" to 
    obj(
      "grey" to "#333",
      "red" to array(255, 0, 0), 
      "green" to array(0, 255, 0),
      "others" to obj(
        "white" to "#fff"
      )
    )
  )
}

This results in:

{
  "colors": {
    "grey": "#333",
    "red": [255, 0, 0],
    "green": [0, 255, 0],
    "others": {
      "white": "#fff"
    }
  }
}

The current method does not allow for any custom serialization (as far as I know?), and nests obj calls inside of parenthesis to create it's own DSL. All of the builder code must be included on adjacent lines, and you can't insert your own code between each line. This assumes that you have all of the information you need to build the JSON object before you decide to build it.

It might be beneficial to create a DSL that looks more like this:

json {
  obj("colors") {
    put("grey", "#333")
    putArray("red", listOf(255, 0, 0))
    putArray("green", listOf(0, 255, 0))
    obj("others") {
      put("white", "#fff")
    }
  }
}

This format allows arbitrary code to be ran between each call and lets you "build" a JSON object more programmatically. You can insert code between each line, making this a bit more Kotlin friendly.

Also, the ability to convert arbitrary object within the tree would also be great: Supplying a value outside of the range of primitive values could be converted to it's JSON equivalent.

Thank you, Klaxon is great! ❤️

ejektaflex avatar Jul 31 '20 09:07 ejektaflex

You can already do this:

val j = json {
    obj("colors" to obj(
            "grey" to "#333",
            "red" to listOf(255, 0, 0),
            "green" to listOf(0, 255, 0),
            "others" to obj("white" to "#fff")

    ))

cbeust avatar Jul 31 '20 16:07 cbeust

Okay, that works. However, that still uses nesting calls inside of each other (with parenthesis, not lambdas) and does not use builder methods. As such, you can't add other code inside of the DSL, since it's all wrapped in parenthesis :^)

ejektaflex avatar Aug 01 '20 01:08 ejektaflex

Not following. Can you show an example of what you'd like to do?

cbeust avatar Aug 01 '20 03:08 cbeust

Here's a modified example of what I'd written above:

json {
  obj("colors") {
    put("grey", "#333")
    putArray("red", listOf(255, 0, 0))
    putArray("green", listOf(0, 255, 0))

    val someData = klaxon.toJson(someThing)
    put("custom_data", someData)

    for (i in 0 until 10) {
      put("field$i", i * 2)
    }

    obj("others") {
      put("white", "#fff")
    }
  }
}

The lambda function builder style shown here allows you to add in arbitrary code in the middle of the DSL. This is difficult to do with the current builder implementation.

ejektaflex avatar Aug 01 '20 06:08 ejektaflex

Fair point. I just implemented this, take a look and let me know if this addresses your suggestion:

https://github.com/cbeust/klaxon/commit/0179fedab54bb9f5824d5008d6f6faedcdcf17e1

cbeust avatar Aug 04 '20 17:08 cbeust

Could we also allow to use the obj() function without specifying a key so that we can populate the root fields of the object ?

This way the following expression could work:

json {
  obj {
    put("hello", "world")
  }
}

Resulting in:

{
   "hello": "world"
}

Currently there seem to be no easy way to do that. (See #316)

Totalus avatar Apr 06 '22 18:04 Totalus