Kotlin implementation of JSON Pointer.
Note – Breaking Change – from version 8.0 of the library, the basic functions of the JSONPointer class
(along with JSONPointerException) have been moved to a new library
kjson-pointer-core, allowing the class to be used independently of
any specific JSON implementation.
That leaves this library with the responsibility of applying the functionality of the JSONPointer class to the JSON
structures created by the kjson-core library.
The find() and existsIn() and all the other functions that apply a JSONPointer to a JSONValue structure are now
implemented as extension functions on JSONPointer, leaving the kjson-pointer-core library free of dependencies that
are needed only for these operations.
For existing users of the library, the switch to the use of extension functions means that some functions will now need
to be imported, where previously they were part of the JSONPointer class.
The JSONRef and JSONReference classes both include references to JSONValue objects, so they remain part of this
library.
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.
The details on creating a JSONPointer, and then using that pointer to navigate to locations in a JSON structure
relative to the original pointer, are now described in the
kjson-pointer-core library, but to summarise:
To create a pointer from a JSON Pointer string (which may include encoding of “/” and “~”
characters, as described in the JSON Pointer Specification):
val pointer = JSONPointer("/prop1/0")Alternatively, the companion object functions JSONPonter.of() and JSONPointer.from() will create a JSONPointer
from a pointer string, a vararg array of tokens, or a List or Array of tokens.
To use a JSONPointer to create a new pointer referring to a child element:
val childPointer = pointer.child(childRef)(where childRef is a string to select a property from an object, a number to select an array item, or another
JSONPointer to create a pointer to a more deeply nested element).
To use a JSONPointer to create a new pointer referring to the parent element:
val childPointer = pointer.parent()Objects of the JSONPointer class are immutable, so the above functions all create a new instance.
The functions to use a JSONPointer to locate elements within a JSON structure created by the kjson-core library are
implemented as extension functions in this library.
To test whether an element referenced by the pointer exists in the JSON object obj:
if (pointer existsIn obj) {
doSomething(obj, pointer)
}Alternatively:
if (obj.contains(pointer)) {
doSomething(obj, pointer)
}Either of these two constructs returns false if the obj is null.
To retrieve the element:
val value = pointer.find(obj)This will throw a JSONPointerException if the pointer is not valid for the given structure, so it is generally wise to
test whether the element exist using one of the tests above.
An alternative is to use the indexing operation – this will return null if no element exists at the pointer
location:
val value = obj[pointer]To locate a specified child value within a structure:
val childPointer = pointer.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.
Also, because it is not possible to distinguish between two instances of, say, JSONBoolean.TRUE, the function may not
be successful in locating a primitive value.
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 baseThe parent() and child() operations work on JSONReferences 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 nullTo test whether the reference is valid, that is, the pointer refers to a valid location in the base object:
if (ref.valid) {
doSomething(ref)
}To test whether the reference has a nominated child:
if (ref.hasChild(name)) { // or index
doSomething(ref, name)
}To locate 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.
Also, because it is not possible to distinguish between two instances of, say, JSONBoolean.TRUE, the function may not
be successful in locating a primitive value.
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):
JSONStringJSONIntJSONLongJSONDecimalJSONNumber:JSONInt,JSONLongorJSONDecimalJSONBooleanJSONPrimitive:JSONString,JSONInt,JSONLong,JSONDecimalorJSONBooleanJSONArrayJSONObjectJSONStructure:JSONArrayorJSONObjectJSONValue: 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 navigating 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.nodeidRef 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.nodeaddressRef 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 create a new JSONRef with the current node as the base:
val rebasedRef = addressRef.rebase()In the new JSONRef, both the base node and the current node will be set to the current node of the original, and the
parameterised type will be the same as the original.
The pointer will be JSONPointer.root, so the effect will be to isolate a subtree of the original JSON structure.
To locate a specified target child value:
val childRef = baseRef.locateChild(target)The result will be a JSONRef parameterised by the type of the target, that is, if target is a JSONObject, the
result will be a JSONRef<JSONObject>?.
The function will perform a depth-first search of the JSON structure, so it should be used only when there is no
alternative.
Also, because it is not possible to distinguish between two instances of, say, JSONBoolean.TRUE, the function may not
be successful in locating a primitive value.
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>()) {
doSomething(genericRef)
}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 the
properties of the object:
childString(name): returns theStringvalue of the named propertychildInt(name): returns theIntvalue of the named propertychildLong(name): returns theLongvalue of the named propertychildDecimal(name): returns theBigDecimalvalue of the named propertychildBoolean(name): returns theBooleanvalue of the named property
In all cases, if the property is not found an exception will be thrown showing the property name, or if the property is
found but is not of the required type, an exception will be thrown detailing the expected type, the actual value and the
location in the structure, in JSONPointer form.
These are more than just convenience functions for functionality that is available in other ways; childString("name")
is more efficient than child<JSONString>("name").value.
There are also a number of functions provided as extension functions on JSONRef<JSONObject> to simplify access to
optional properties of the object:
optionalString(name): returns theStringvalue of the named property, ornullif it is not presentoptionalInt(name): returns theIntvalue of the named property, ornullif it is not presentoptionalLong(name): returns theLongvalue of the named property, ornullif it is not presentoptionalDecimal(name): returns theBigDecimalvalue of the named property, ornullif it is not presentoptionalBoolean(name): returns theBooleanvalue of the named property, ornullif it is not present
In these 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.
And to execute a block of code conditionally, depending on the presence of an optional child:
ref.withOptionalChild<JSONString>("name") {
// within this code, "this" is a JSONRef<JSONString>, and "it" is the JSONString
}And again, if the property is present but of the wrong type, a detailed exception is thrown.
The latest version of the library is 8.12, and it may be obtained from the Maven Central repository.
<dependency>
<groupId>io.kjson</groupId>
<artifactId>kjson-pointer</artifactId>
<version>8.12</version>
</dependency> implementation 'io.kjson:kjson-pointer:8.12' implementation("io.kjson:kjson-pointer:8.12")Peter Wall
2025-11-13