Kotlin implementation of JSON Pointer.
Note – Breaking Change – from version 5.0 of the library, the tokens
array is no longer accessible as
a public
value.
It was an oversight to allow it to be accessible previously since array members may be modified, and a JSONPointer
is
intended to be immutable.
The array of tokens may be obtained by the functions tokensAsArray()
which returns a copy of the array, or
tokensAsList()
which returns an immutable List
.
Note – Breaking Change for users of the toURIFragment()
and fromURIFragment()
functions – from
version 4.0 of the library, these functions no longer output or expect the leading #
symbol.
To create a JSON Pointer:
val pointer = JSONPointer("/prop1/0")
This creates a pointer to the 0th element of the "prop1" property (of whatever JSON value is addressed).
To test whether such an element exists in the JSON object "obj":
if (pointer existsIn obj) {
// whatever
}
To retrieve the element:
val value = pointer.find(obj)
A pointer to the root element of any JSON value:
val pointer = JSONPointer.root
To navigate to a child property:
val newPointer = pointer.child("prop1")
To navigate to a child array element:
val newPointer2 = newPointer.child(0)
(the result of the last two operations is a pointer equivalent to the pointer in the first example).
To create a pointer from a list of pointer elements:
val pointerFromList = JSONPointer.from(listOf("prop1", "0"))
(again, the result will be a pointer equivalent to the pointer in the first example).
To create a pointer to a specified child value within a structure:
val childPointer = JSONPointer.root.locateChild(structure, target)
(This will perform a depth-first search of the JSON structure, so it should be used only when there is no alternative.)
A JSONReference
is a combination of a JSONPointer
and a JSONValue
.
This can be valuable when navigating around a complex tree – it removes the necessity to pass around both a
pointer and the base value to which it refers, and it pre-calculates the destination value (and its validity).
(JSONReference
has since been superseded by JSONRef
, and may eventually be deprecated.)
To create a JSONReference
to the root of an object:
val ref = JSONReference(base)
Or to create a reference to a location in an object specified by a pointer:
val ref = JSONPointer("/field") ref base
The parent()
and child()
operations work on JSONReference
s similarly to their JSONPointer
equivalents.
To get the value from a JSONReference
(the value within the base object pointed to by the pointer part of the
reference):
val value: JSONValue? = ref.value // may be null
To test whether the reference is valid, that is, the pointer refers to a valid location in the base object:
if (ref.valid) {
// the reference can be taken to be valid
}
To test whether the reference has a nominated child:
if (ref.hasChild(name)) { // or index
// code to make use of ref.child(name)
}
To create a reference to a specified target child value:
val childRef = baseRef.locateChild(target)
(This will perform a depth-first search of the JSON structure, so it should be used only when there is no alternative.)
JSONRef
is an evolution of the concept first implemented in JSONReference
.
Like the earlier class, it combines a JSONPointer
with the object into which the pointer navigates, but as a
parameterised class, it allows the target element to be accessed in a type-safe manner.
The parameter class may be any of the JSONValue
sealed interface types (or their nullable forms):
JSONString
JSONInt
JSONLong
JSONDecimal
JSONNumber
:JSONInt
,JSONLong
orJSONDecimal
JSONBoolean
JSONPrimitive
:JSONString
,JSONInt
,JSONLong
,JSONDecimal
orJSONBoolean
JSONArray
JSONObject
JSONStructure
:JSONArray
orJSONObject
JSONValue
: any of the above types
The usage of JSONRef
is best explained be example:
val json = JSON.parseObject("file.json")
val ref = JSONRef(json)
The value ref
will be of type JSONRef<JSONObject>
; it will be a reference to the root of the object tree.
This may be simplified by the use of the ref()
extension function:
val jsonRef = JSON.parseObject("file.json").ref()
The result is the same; the only difference is that the syntax may be easier to read.
JSONRef<J>
exposes three properties:
Name | Type | Description |
---|---|---|
base |
JSONValue |
The base JSON value |
pointer |
JSONPointer |
The JSONPointer to the referenced node |
node |
J |
The node (J a subtype of JSONValue? ) |
JSONRef
is ideally suited to the task of navigation a JSON structure.
The JSONRef
object itself is immutable, so to navigate to a nested part of a structure, the child
operation creates
a new JSONRef
pointing to the appropriate location in the structure.
To navigate to the id
property of the object in the above example, expecting it to be a string:
val idRef = ref.child<JSONString>("id")
val id = idRef.node
idRef
will be of type JSONRef<JSONString>
, and id
will be of type JSONString
.
The function will throw an exception if the property id
is missing, is null or is not a JSONString
Now imagine that the object contains a property named address
, which is an array of address line strings:
val addressRef = ref.child<JSONArray>("address")
val line0Ref = addressRef.child<JSONString>(0)
val line0 = line0Ref.node
addressRef
will be of type JSONRef<JSONArray>
, line0Ref
will be of type JSONRef<JSONString>
and line0
will be
of type JSONString
.
To navigate to the parent node:
val parentRef = addressRef.parent<JSONObject>()
In this example, parentRef
will now be identical to the original ref
.
To iterate over the address lines from the example above:
addressRef.forEach<JSONString> {
// within this code, "this" is a JSONRef<JSONString>, and "it" is an Int (the index)
println("Address line ${it + 1}: ${node.value}")
}
Or over the properties of an object:
ref.forEachKey<JSONValue> {
// within this code, "this" is a JSONRef<JSONValue>, and "it" is a String (the object key / property name)
}
This also illustrates the use of JSONValue
as the parameterised type – this is saying that the value may be any
non-null JSONValue
, to allow for the case where properties are of different types.
When using a JSONRef<JSONValue>
, the isRef()
function tests whether the reference is to a node of a specific type:
if (genericRef.isRef<JSONBoolean>()) {
// the node is Boolean
}
and the asRef()
function converts to a specified type, throwing an exception if the type is incorrect:
val stringRef = genericRef.asRef<JSONString>()
There are a number of functions provided as extension functions on JSONRef<JSONObject>
to simplify access to optional
properties of the object:
optionalString(name)
: returns theString
value of the named property, ornull
if it is not presentoptionalInt(name)
: returns theInt
value of the named property, ornull
if it is not presentoptionalLong(name)
: returns theLong
value of the named property, ornull
if it is not presentoptionalDecimal(name)
: returns theBigDecimal
value of the named property, ornull
if it is not presentoptionalBoolean(name)
: returns theBoolean
value of the named property, ornull
if it is not present
In all cases, if the property is not of the required type, an exception is thrown detailing the expected type, the
actual value and the location in the structure, in JSONPointer
form.
When the optional property is a nested sub-structure, the optionalChild()
function may be used:
ref.optionalChild<JsonObject>("address")?.apply {
// within this code, "this" is a JSONRef<JSONObject>
}
As with the other optional functions, if the property is present but of the wrong type, a detailed exception is thrown.
More documentation to follow…
The latest version of the library is 7.4, and it may be obtained from the Maven Central repository.
<dependency>
<groupId>io.kjson</groupId>
<artifactId>kjson-pointer</artifactId>
<version>7.4</version>
</dependency>
implementation 'io.kjson:kjson-pointer:7.4'
implementation("io.kjson:kjson-pointer:7.4")
Peter Wall
2024-02-18