Skip to content

a-sit-plus/jsonpath

Repository files navigation

JsonPath

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 with Kotlin Multiplatform and Multiplatform Mobile in mind. Its primary targets are JVM, Android and iOS.

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. Use the JsonPath constructor for compiling JSONPath query expressions.
  2. Invoke the method JsonPath.query to select nodes satisfying the JsonPath query expression from a JsonElement.
  3. A nodeList containing both the selected values and their normalized paths is returned.
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                                                            
    },
)