
Supports querying JSON data using JSONPath expressions, enabling node selection and path normalization. Allows adding and removing custom function extensions while leveraging popular libraries for logging, testing, and serialization.
This is a Kotlin Multiplatform Library for using Json Paths as specified in RFC9535.
This library was built for Kotlin Multiplatform, supporting all KMP targets, except WASI.
Notable features for multiplatform are:
Antilog is registered.at.asitplus:jsonpath4k:$version)JsonPath constructor for compiling JSONPath query expressions.JsonPath.query to select nodes satisfying the JsonPath query expression from a JsonElement.nodeList containing both the selected values and their normalized paths is returned.In general, things are called what they are. Most prominently, JsonPath4K is not used anywhere in code – even though this project is called JsonPath4K, it provides JSONPath functionality for Kotlin Multiplatform, not JsonPath4K functionality because there is no such thing.
val jsonElement = buildJsonArray { add(0) }
val jsonPathQueryExpression = "$[0]"
val jsonPath = JsonPath(jsonPathQueryExpression)
val nodeList = jsonPath.query(jsonElement)
val jsonValue = nodeList[0].value.jsonPrimitive
val normalizedPath = nodeList[0].normalizedJsonPathThis library supports the function extensions specified in RFC9535 by default.
Custom function extensions can be added using JsonPath.defaultFunctionExtensionRepository.addExtension:
// adding a logical type function extension with 1 parameter of type NodesType
JsonPath.defaultFunctionExtensionRepository.addExtension("foo") {
JsonPathFunctionExtension.LogicalTypeFunctionExtension(
JsonPathFilterExpressionType.ValueType,
JsonPathFilterExpressionType.LogicalType,
JsonPathFilterExpressionType.NodesType,
) {
val value0 = it[0] as JsonPathFilterExpressionValue.ValueTypeValue
val value1 = it[1] as JsonPathFilterExpressionValue.LogicalTypeValue
val value2 = it[2] as JsonPathFilterExpressionValue.NodesTypeValue
true
}
}
// adding a value type function extension returning a JsonValue with 2 parameters of type ValueType
JsonPath.defaultFunctionExtensionRepository.addExtension("foo") {
JsonPathFunctionExtension.ValueTypeFunctionExtension(
JsonPathFilterExpressionType.ValueType,
JsonPathFilterExpressionType.ValueType,
) {
JsonPrimitive("")
}
}
// adding a logical type function extension with 2 parameters of type ValueType returning false
JsonPath.defaultFunctionExtensionRepository.addExtension("foo") {
JsonPathFunctionExtension.LogicalTypeFunctionExtension(
JsonPathFilterExpressionType.ValueType,
JsonPathFilterExpressionType.ValueType,
) {
false
}
}
// adding a value type function extension returning the special value `Nothing` with 2 parameters of type LogicalType
JsonPath.defaultFunctionExtensionRepository.addExtension("foo") {
JsonPathFunctionExtension.ValueTypeFunctionExtension(
JsonPathFilterExpressionType.LogicalType,
JsonPathFilterExpressionType.LogicalType,
) {
null
}
}
// adding a nodes type function extension with 2 parameters of type ValueType
JsonPath.defaultFunctionExtensionRepository.addExtension("foo") {
JsonPathFunctionExtension.NodesTypeFunctionExtension(
JsonPathFilterExpressionType.ValueType,
JsonPathFilterExpressionType.ValueType,
) {
listOf()
}
}
// reimplementing the count function as defined in [RFC9535](https://www.rfc-editor.org/rfc/rfc9535.html#name-function-extensions)
JsonPath.defaultFunctionExtensionRepository.addExtension("count") {
JsonPathFunctionExtension.ValueTypeFunctionExtension(
JsonPathFilterExpressionType.NodesType,
) {
val nodesTypeValue = it[0] as JsonPathFilterExpressionValue.NodesTypeValue
JsonPrimitive(nodesTypeValue.nodeList.size.toUInt())
}
}Function extensions can be removed from the default repository by setting the value of JsonPath.defaultFunctionExtensionRepository to a new repository.
Existing functions can be preserved by exporting them using JsonPath.defaultFunctionExtensionRepository.export() and selectively importing them into the new repository.
In order to test custom function extensions without polluting the default function extension repository, it is recommended to make an export and use the resulting map to build a new function extension retriever.
val testRetriever = JsonPath.defaultFunctionExtensionRepository.export().plus(
"foo" to JsonPathFunctionExtension.LogicalTypeFunctionExtension(
JsonPathFilterExpressionType.ValueType,
JsonPathFilterExpressionType.ValueType,
) {
true
}
)
val jsonPath = JsonPath(jsonPathStatement, functionExtensionRetriever = testRetriever::get)
// select from a json element
jsonPath.query(buildJsonElement {})Invalid JSONPath expressions fail during JsonPath construction with a JsonPathCompilerException. The compiler's error listener only receives diagnostic details; it does not suppress or replace that exception.
On JVM/Android, iOS, macOS, tvOS, JS, and WasmJS, the default listener sends diagnostics to Napier (provided the application has registered an Antilog). On watchOS, Linux, Windows (mingw), and Android Native, the default listener is a no-op. Compilation failures still throw on these platforms, but logging requires a custom implementation of AntlrJsonPathCompilerErrorListener.
Pass a compiler with that listener to one JsonPath, or assign it as the global default:
val compiler = AntlrJsonPathCompiler(errorListener = myErrorListener)
val jsonPath = JsonPath(jsonPathExpression, compiler = compiler)
// Optional: use it for subsequent JsonPath instances that do not specify a compiler.
JsonPath.defaultCompiler = compilerUsing a single, shared default works because operations only use an ANTLR cache as global state and this cache is guarded to enable concurrent access and mutation.
External contributions are greatly appreciated! Just be sure to observe the contribution guidelines (see CONTRIBUTING.md).
The Apache License does not apply to the logos, (including the A-SIT logo) and the project/module name(s), as these are the sole property of A-SIT/A-SIT Plus GmbH and may not be used in derivative works without explicit permission!
This is a Kotlin Multiplatform Library for using Json Paths as specified in RFC9535.
This library was built for Kotlin Multiplatform, supporting all KMP targets, except WASI.
Notable features for multiplatform are:
Antilog is registered.at.asitplus:jsonpath4k:$version)JsonPath constructor for compiling JSONPath query expressions.JsonPath.query to select nodes satisfying the JsonPath query expression from a JsonElement.nodeList containing both the selected values and their normalized paths is returned.In general, things are called what they are. Most prominently, JsonPath4K is not used anywhere in code – even though this project is called JsonPath4K, it provides JSONPath functionality for Kotlin Multiplatform, not JsonPath4K functionality because there is no such thing.
val jsonElement = buildJsonArray { add(0) }
val jsonPathQueryExpression = "$[0]"
val jsonPath = JsonPath(jsonPathQueryExpression)
val nodeList = jsonPath.query(jsonElement)
val jsonValue = nodeList[0].value.jsonPrimitive
val normalizedPath = nodeList[0].normalizedJsonPathThis library supports the function extensions specified in RFC9535 by default.
Custom function extensions can be added using JsonPath.defaultFunctionExtensionRepository.addExtension:
// adding a logical type function extension with 1 parameter of type NodesType
JsonPath.defaultFunctionExtensionRepository.addExtension("foo") {
JsonPathFunctionExtension.LogicalTypeFunctionExtension(
JsonPathFilterExpressionType.ValueType,
JsonPathFilterExpressionType.LogicalType,
JsonPathFilterExpressionType.NodesType,
) {
val value0 = it[0] as JsonPathFilterExpressionValue.ValueTypeValue
val value1 = it[1] as JsonPathFilterExpressionValue.LogicalTypeValue
val value2 = it[2] as JsonPathFilterExpressionValue.NodesTypeValue
true
}
}
// adding a value type function extension returning a JsonValue with 2 parameters of type ValueType
JsonPath.defaultFunctionExtensionRepository.addExtension("foo") {
JsonPathFunctionExtension.ValueTypeFunctionExtension(
JsonPathFilterExpressionType.ValueType,
JsonPathFilterExpressionType.ValueType,
) {
JsonPrimitive("")
}
}
// adding a logical type function extension with 2 parameters of type ValueType returning false
JsonPath.defaultFunctionExtensionRepository.addExtension("foo") {
JsonPathFunctionExtension.LogicalTypeFunctionExtension(
JsonPathFilterExpressionType.ValueType,
JsonPathFilterExpressionType.ValueType,
) {
false
}
}
// adding a value type function extension returning the special value `Nothing` with 2 parameters of type LogicalType
JsonPath.defaultFunctionExtensionRepository.addExtension("foo") {
JsonPathFunctionExtension.ValueTypeFunctionExtension(
JsonPathFilterExpressionType.LogicalType,
JsonPathFilterExpressionType.LogicalType,
) {
null
}
}
// adding a nodes type function extension with 2 parameters of type ValueType
JsonPath.defaultFunctionExtensionRepository.addExtension("foo") {
JsonPathFunctionExtension.NodesTypeFunctionExtension(
JsonPathFilterExpressionType.ValueType,
JsonPathFilterExpressionType.ValueType,
) {
listOf()
}
}
// reimplementing the count function as defined in [RFC9535](https://www.rfc-editor.org/rfc/rfc9535.html#name-function-extensions)
JsonPath.defaultFunctionExtensionRepository.addExtension("count") {
JsonPathFunctionExtension.ValueTypeFunctionExtension(
JsonPathFilterExpressionType.NodesType,
) {
val nodesTypeValue = it[0] as JsonPathFilterExpressionValue.NodesTypeValue
JsonPrimitive(nodesTypeValue.nodeList.size.toUInt())
}
}Function extensions can be removed from the default repository by setting the value of JsonPath.defaultFunctionExtensionRepository to a new repository.
Existing functions can be preserved by exporting them using JsonPath.defaultFunctionExtensionRepository.export() and selectively importing them into the new repository.
In order to test custom function extensions without polluting the default function extension repository, it is recommended to make an export and use the resulting map to build a new function extension retriever.
val testRetriever = JsonPath.defaultFunctionExtensionRepository.export().plus(
"foo" to JsonPathFunctionExtension.LogicalTypeFunctionExtension(
JsonPathFilterExpressionType.ValueType,
JsonPathFilterExpressionType.ValueType,
) {
true
}
)
val jsonPath = JsonPath(jsonPathStatement, functionExtensionRetriever = testRetriever::get)
// select from a json element
jsonPath.query(buildJsonElement {})Invalid JSONPath expressions fail during JsonPath construction with a JsonPathCompilerException. The compiler's error listener only receives diagnostic details; it does not suppress or replace that exception.
On JVM/Android, iOS, macOS, tvOS, JS, and WasmJS, the default listener sends diagnostics to Napier (provided the application has registered an Antilog). On watchOS, Linux, Windows (mingw), and Android Native, the default listener is a no-op. Compilation failures still throw on these platforms, but logging requires a custom implementation of AntlrJsonPathCompilerErrorListener.
Pass a compiler with that listener to one JsonPath, or assign it as the global default:
val compiler = AntlrJsonPathCompiler(errorListener = myErrorListener)
val jsonPath = JsonPath(jsonPathExpression, compiler = compiler)
// Optional: use it for subsequent JsonPath instances that do not specify a compiler.
JsonPath.defaultCompiler = compilerUsing a single, shared default works because operations only use an ANTLR cache as global state and this cache is guarded to enable concurrent access and mutation.
External contributions are greatly appreciated! Just be sure to observe the contribution guidelines (see CONTRIBUTING.md).
The Apache License does not apply to the logos, (including the A-SIT logo) and the project/module name(s), as these are the sole property of A-SIT/A-SIT Plus GmbH and may not be used in derivative works without explicit permission!