JDT WASM - JSON Document Transforms

Rust-based JSON Document Transforms (JDT) implementation with WebAssembly support

View the Project on GitHub simbo1905/jdt-wasm

JsonPath

This module provides a JSONPath-style query engine for JSON documents parsed with jdk.sandbox.java.util.json.

It is based on the original Stefan Goessner JSONPath article: https://goessner.net/articles/JsonPath/

Quick Start

import jdk.sandbox.java.util.json.*;
import json.java21.jsonpath.JsonPath;

JsonValue doc = Json.parse("""
  {"store": {"book": [{"title": "A", "price": 8.95}, {"title": "B", "price": 12.99}]}}
  """);

var titles = JsonPath.parse("$.store.book[*].title").query(doc);
var cheap = JsonPath.parse("$.store.book[?(@.price < 10)].title").query(doc);

Syntax At A Glance

Operator Example What it selects
root $ the whole document
property $.store.book a nested object property
bracket property $['store']['book'] same as dot notation, but allows escaping
wildcard $.store.* all direct children
recursive descent $..price any matching member anywhere under the document
array index $.store.book[0] / [-1] element by index (negative from end)
slice $.store.book[:2] / [0:4:2] / [::-1] slice by start:end:step
union $.store['book','bicycle'] / [0,1] select multiple names/indices
filter exists $.store.book[?(@.isbn)] elements where a member exists
filter compare $.store.book[?(@.price < 10)] elements matching a comparison
filter logic $.store.book[?(@.isbn && (@.price < 10 || @.price > 20))] compound boolean logic
script (limited) $.store.book[(@.length-1)] last element via length-1

Examples

Expression What it selects
$.store.book[*].title all book titles
$.store.book[?(@.price < 10)].title titles of books cheaper than 10
$.store.book[?(@.isbn && (@.price < 10 || @.price > 20))].title books with an ISBN and price outside the mid-range
$..price every price anywhere under the document
$.store.book[-1] the last book
$.store.book[0:4:2] every other book from the first four

Supported Syntax

This implementation follows Goessner-style JSONPath operators, including:

Stream-Based Functions (Aggregations)

Some JsonPath implementations include aggregation functions such as $.numbers.avg(). In this implementation we provide first class stream support so you can use standard JDK aggregation functions on JsonPath.query(...) results.

The query() method returns a standard List<JsonValue>. You can stream, filter, map, and reduce these results using standard Java APIs. To make this easier, we provide the JsonPathStreams utility class with predicate and conversion methods.

Strict vs. Lax Conversions

We follow a pattern of “Strict” (asX) vs “Lax” (asXOrNull) converters:

Examples

Summing Numbers (Lax - safe against bad data)

import json.java21.jsonpath.JsonPathStreams;
import java.util.Objects;

// Calculate sum of all 'price' fields, ignoring non-numbers
double total = path.query(doc).stream()
    .map(JsonPathStreams::asDoubleOrNull) // Convert to Double or null
    .filter(Objects::nonNull)             // Remove non-numbers
    .mapToDouble(Double::doubleValue)     // Unbox
    .sum();

Average (Strict - expects valid data)

import java.util.OptionalDouble;

// Calculate average, fails if any value is not a number
OptionalDouble avg = path.query(doc).stream()
    .map(JsonPathStreams::asDouble)       // Throws if not a number
    .mapToDouble(Double::doubleValue)
    .average();

Filtering by Type

import java.util.List;

// Get all strings
List<String> strings = path.query(doc).stream()
    .filter(JsonPathStreams::isString)
    .map(JsonPathStreams::asString)
    .toList();

Available Helpers (JsonPathStreams)

Predicates:

Converters (Strict):

Converters (Lax):

Testing

./mvnw test -pl json-java21-jsonpath -am -Djava.util.logging.ConsoleHandler.level=INFO
./mvnw test -pl json-java21-jsonpath -am -Dtest=JsonPathGoessnerTest -Djava.util.logging.ConsoleHandler.level=FINE