cbeust / klaxon

A JSON parser for Kotlin
Apache License 2.0
1.86k stars 121 forks source link

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

Open ejektaflex opened 4 years ago

ejektaflex commented 4 years ago

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! ❤️

cbeust commented 4 years ago

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")

    ))
ejektaflex commented 4 years ago

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 :^)

cbeust commented 4 years ago

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

ejektaflex commented 4 years ago

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.

cbeust commented 4 years ago

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

Totalus commented 2 years ago

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)