/kotlinx-serialization-jsonpath

KotlinX Serialization JsonElement DSL based on Arrow Optics

Primary LanguageKotlinApache License 2.0Apache-2.0

Module KotlinX Serialization JsonPath

Maven Central Latest snapshot

JsonPath offers a simple DSL to work with JsonElement from Kotlinx Serialization Json, this allows you to easily work with JSON in Kotlin in a typed manner. Simply add the following dependency as implementation in the build.gradle dependencies` block.

dependencies {
  implementation("io.github.nomisrev:kotlinx-serialization-jsonpath:0.2.0")
}

Let's dive right in with following JSON_STRING as input JsonElement that models a simple company.

{
  "name": "Arrow",
  "address": {
    "city": "Functional Town",
    "street": {
      "number": 1337,
      "name": "Functional street"
    }
  },
  "employees": [
    {
      "name": "John",
      "lastName": "doe"
    },
    {
      "name": "Jane",
      "lastName": "doe"
    }
  ]
}

Given this JsonElement we can select the name from the JsonElement. This gives us an Optional from the root JsonElement to the name property with type String. We can then use this JsonPath to modify the original JsonElement's name property, this gives us back a new JsonElement with the name modified according to the passed String::uppercase function.

  val json: JsonElement = Json.decodeFromString(JSON_STRING)
  val name: Optional<JsonElement, String> = JsonPath.select("name").string
  println(name.modify(json, String::uppercase))

You can get the full code here.

{"name":"ARROW","address":{"city":"Functional Town","street":{"number":1337,"name":"Functional street"}},"employees":[{"name":"John","lastName":"doe"},{"name":"Jane","lastName":"doe"}]}

As we've seen above we can select a property from a JsonObject, but what if we want to access deeply nested properties in our JsonElement? For that we can use path which allows you to select deeply nested properties using the dot (.) notation you might know from Javascript.

Below we select the address JsonObject, and from the address JsonObject we then select the street JsonObject, to then finally select the name of the street.

This again returns us an Optional<JsonElement, String>, which we use to modify the address.street.name. We then also extract the value using getOrNull which returns us the desired path in our JsonElement, or it returns null if the desired path is not available in our JsonElement.

  val json: JsonElement = Json.decodeFromString(JSON_STRING)
  val name: Optional<JsonElement, String> = JsonPath.path("address.street.name").string
  val res: JsonElement = name.modify(json, String::uppercase).also(::println)
  name.getOrNull(res)?.also(::println)

You can get the full code here.

{"name":"Arrow","address":{"city":"Functional Town","street":{"number":1337,"name":"FUNCTIONAL STREET"}},"employees":[{"name":"John","lastName":"doe"},{"name":"Jane","lastName":"doe"}]}
FUNCTIONAL STREET

In the previous examples we've seen how we can select properties out of JsonObject, but when working with JsonElement we also often have to deal with JsonArray that can contain many JsonElement. For these use-cases we can use every, which focuses into every JsonElement in our JsonArray.

In the example below we select the employees JsonArray, and then we select every JsonElement in the JsonArray. We then select the name out of every JsonElement.

Instead of Optional<JsonElement, String> it now returns Traversal<JsonElement, String>, since we selected many properties instead of a single property.

Just like before we can apply a function to it using modify, and we can also extract every property using getAll. This returns us an emptyList() when no values were found along the path, or all found values inside a List.

  val json: JsonElement = Json.decodeFromString(JSON_STRING)
  val employeesName: Traversal<JsonElement, String> = JsonPath.select("employees").every.select("name").string
  val res: JsonElement = employeesName.modify(json, String::uppercase).also(::println)
  employeesName.getAll(res).also(::println)

You can get the full code here.

{"name":"Arrow","address":{"city":"Functional Town","street":{"number":1337,"name":"Functional street"}},"employees":[{"name":"JOHN","lastName":"doe"},{"name":"JANE","lastName":"doe"}]}
[JOHN, JANE]

Alternatively we can also use the pathEvery function to select every JsonElement in our JsonArray. Like for path we can also use the dot (.) notation to select deeply nested properties. On the other hand, the star (*) notation allow us to select every JsonElement in our JsonArray.

Like before, below we select the employees JsonArray, and then we select every JsonElement in the JsonArray. We then select the name out of every JsonElement.

Again, instead of Optional<JsonElement, String> it now returns Traversal<JsonElement, String>, since we selected many properties instead of a single property.

You can then, apply a function to it using modify like before.

  val json: JsonElement = Json.decodeFromString(JSON_STRING)
  val employeesName: Traversal<JsonElement, String> = JsonPath.pathEvery("employees.*.name").string
  val res: JsonElement = employeesName.modify(json, String::uppercase).also(::println)
  employeesName.getAll(res).also(::println)

You can get the full code here.

{"name":"Arrow","address":{"city":"Functional Town","street":{"number":1337,"name":"Functional street"}},"employees":[{"name":"JOHN","lastName":"doe"},{"name":"JANE","lastName":"doe"}]}
[JOHN, JANE]