Pointer-Path is a Java API for building and transforming nested data types like JSON.
The API is generic, but currently only supports JSON operations, with the intent to provide POJO and XML providers.
Final Releases are available on Maven Central:
compile 'io.heretical:pointer-path-core:1.1.0'
compile 'io.heretical:pointer-path-json:1.1.0'
<dependency>
<groupId>io.heretical</groupId>
<artifactId>pointer-path-core</artifactId>
<version>1.1.0</version>
<type>pom</type>
</dependency>
<dependency>
<groupId>io.heretical</groupId>
<artifactId>pointer-path-json</artifactId>
<version>1.1.0</version>
<type>pom</type>
</dependency>
WIP release maven coordinates can be found at:
This library requires Java 8 and the JSON functionality is dependent on Jackson.
The pointer path syntax is an extended version of the JSON
Pointer syntax with the addition of a wildcard operator
(*
) and a descent operator(**
).
/person
references the person
element in an data structure.
/person/firstName
references the child firstName
beneath the person
element in an data structure.
/people/*/age
will reference the age
value a level below the people
array (e.g. /people/measures/age
).
/measures/**/value
will reference all the value
values at any level below the measures
root.
(e.g. /measures/value
and /measures/foo/bar/value
)
The Pointer
API, relying on the pointer path syntax, provides accessors and mutators for use against a nested data
structure.
The following code snippet will deep copy the tree that matches /person/measures/*/value
from the from
node into the
into
node.
JSONNestedPointerCompiler.COMPILER.nested( "/person/measures/*/value" ).copy( from, into );
This code will return an array type (specific to the provider) of all the values
values found in the tree.
JsonNode from = mapper.readTree( JSONData.nested );
JsonNode result = COMPILER.nested( "/**/measures/*/value" ).allAt( from );
Here we coerce all elements named value
to float
types, then retrieve all the values as a JSON array.
JsonNode from = mapper.readTree( JSONData.nested );
NestedPointer<JsonNode, ArrayNode> pointer = COMPILER.nested( "/person/**/value" );
pointer.apply( from, JSONPrimitiveTransforms.TO_FLOAT ); // accepts a Java 8 Function or lambda
JsonNode result = pointer.allAt( from );
The Builder
class allows for new nested objects to be created from a set of BuildSpec
declarations and a Map of
values. With the intent of declaring the transformation once, and repeatedly generating new objects on new sets of
argument values.
Below a new JSON object is being created from a Map of values.
Map<Comparable, Object> arguments = new HashMap<>();
arguments.put( "id", "123-45-6789" );
arguments.put( "age", 50 );
arguments.put( "first", "John" );
arguments.put( "last", "Doe" );
arguments.put( "child", "Jane" );
arguments.put( "child-age", 4 );
BuildSpec spec = new BuildSpec()
.putInto( "id", "/ssn" )
.putInto( "age", String.class, "/age" )
.putInto( "first", "/name/first" )
.putInto( "last", "/name/last" )
.addInto( "child", "/children" )
.addInto( "child-age", Integer.class, "/childAges" );
JSONBuilder builder = new JSONBuilder( spec );
ObjectNode value = JsonNodeFactory.instance.objectNode();
builder.build( ( key, type ) -> arguments.get( key ), value );
Note that the values in the arguments
Map can be any primitive type or supported child type to the parent node,
including another JSON Object or Array. Internally, all primitive values are wrapped by their corresponding node type,
if any.
Similarly the Copier
class allows for a new nested objects to be created from a set of CopySpec
declarations and a
single input nested data type. Again with the intent of declaring the transformation once, and repeatedly generating new
objects on new sets of argument values.
Below a new JSON object is being created by copying the original object, but excluding specific values.
JsonNode value = mapper.readTree( JSONData.nested );
ObjectNode result = JsonNodeFactory.instance.objectNode();
CopySpec spec = new CopySpec()
.fromExclude( "/person", "/ssn", "/children" ); // exclude ssn and children from the result
JSONCopier copier = new JSONCopier( spec );
copier.copy( value, result );
Its worth noting that the Copier and CopySpec declarations attempts to replicate the semantics of copying a directory tree from one location to another.
In the example below, we apply a transformation to all the value
elements in an array into float
values.
JsonNode value = mapper.readTree( JSONData.nested );
ObjectNode result = JsonNodeFactory.instance.objectNode();
CopySpec spec = new CopySpec()
.fromTransform( "/result", "/measures/*/value", JSONPrimitiveTransforms.TO_FLOAT );
JSONCopier copier = new JSONCopier( spec );
copier.copy( value, result );
We could also have used a descent operator to catch all the child value
elements in a complex object to help normalize
the data structure. In some object the value
could be an int
on one path and float
on another, which could cause
downstream headaches if a system was tyring to infer data types from observed values (looking at you Elasticsearch).