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]