Skip to content

a-sit-plus/jsonpath4k

Repository files navigation

JsonPath4K

A-SIT Plus Official GitHub license Kotlin Kotlin Java Maven Central

This is a Kotlin Multiplatform Library for using Json Paths as specified in RFC9535.

Architecture

This library was built for Kotlin Multiplatform targeting JVM/Android and iOS. Other targets might work, but are not tested or even built.

Notable features for multiplatform are:

  • Use of Napier as the logging framework for the default compiler instance
  • Use of Kotest for unit tests
  • Use of kotlinx-serialization for serialization from/to JSON and to have JsonElement as evaluation target for JsonPathQuery

Using the Library

  1. Add JsonPath4K as a dependency in your project (at.situplus:jsonpath4k:$version)
  2. Use the JsonPath constructor for compiling JSONPath query expressions.
  3. Invoke the method JsonPath.query to select nodes satisfying the JsonPath query expression from a JsonElement.
  4. A 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].normalizedJsonPath

Function extensions

This library supports the function extensions specified in RFC9535 by default.

Custom function extensions

Custom function extensions can be added using JsonPathDependencyManager.functionExtensionRepository.addExtension:

// adding a logical type function extension with 1 parameter of type NodesType
JsonPathDependencyManager.functionExtensionRepository.addExtension("foo") {
    JsonPathFunctionExtension.LogicalTypeFunctionExtension(
        JsonPathFilterExpressionType.NodesType
    ) {
        true
    }
}

// adding a value type function extension returning a JsonValue with 2 parameters of type ValueType
JsonPathDependencyManager.functionExtensionRepository.addExtension("foo") {
    JsonPathFunctionExtension.ValueTypeFunctionExtension(
        JsonPathFilterExpressionType.ValueType,
        JsonPathFilterExpressionType.ValueType,
    ) {
        JsonPrimitive("")
    }
}

// adding a value type function extension returning the JsonValue with 2 parameters of type ValueType
JsonPathDependencyManager.functionExtensionRepository.addExtension("foo") {
    JsonPathFunctionExtension.ValueTypeFunctionExtension(
        JsonPathFilterExpressionType.ValueType,
        JsonPathFilterExpressionType.ValueType,
    ) {
        JsonNull
    }
}

// adding a value type function extension returning the special value `Nothing` with 2 parameters of type LogicalType
JsonPathDependencyManager.functionExtensionRepository.addExtension("foo") {
    JsonPathFunctionExtension.ValueTypeFunctionExtension(
        JsonPathFilterExpressionType.LogicalType,
        JsonPathFilterExpressionType.LogicalType,
    ) {
        null
    }
}

// adding a nodes type function extension with 2 parameters of type ValueType
JsonPathDependencyManager.functionExtensionRepository.addExtension("foo") {
    JsonPathFunctionExtension.NodesTypeFunctionExtension(
        JsonPathFilterExpressionType.ValueType,
        JsonPathFilterExpressionType.ValueType,
    ) {
        listOf()
    }
}

// adding a nodes type function extension with 2 parameters of type ValueType
JsonPathDependencyManager.functionExtensionRepository.addExtension("foo") {
    JsonPathFunctionExtension.NodesTypeFunctionExtension(
        JsonPathFilterExpressionType.ValueType,
        JsonPathFilterExpressionType.ValueType,
    ) {
        listOf()
    }
}

Removing Function extensions

Function extensions can be removed from the default repository by setting the value of JsonPathDependencyManager.functionExtensionRepository to a new repository.

Existing functions can be preserved by exporting them using JsonPathDependencyManager.functionExtensionRepository.export() and selectively importing them into the new repository.

Testing custom function extensions

In order to test custom function extensions without polluting the default function extension repository, it is advised to make an export and use the resulting map to build a function extension retriever.

val testRetriever = JsonPathDependencyManager.functionExtensionRepository.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 {})

Error handeling

The default compiler uses Napier for reporting errors. It is possible to implement a custom error listener by extending AntlrJsonPathCompilerErrorListener and setting a new default compiler:

JsonPathDependencyManager.compiler = AntlrJsonPathCompiler(
    errorListener = object : AntlrJsonPathCompilerErrorListener {
        //TODO: IMPLEMENT MEMBERS                                                            
    },
)

Contributing

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!