Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 91 additions & 0 deletions HIGHLIGHTS_1.9.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@

# v1.9 `json-tree` Highlights

A presentation about what happened in version 1.9

----

## Features πŸŽ‰

* `.to(Class)` API (JSON to Java)
* `.query(JsonSelector)` API (finding matching nodes RFC-9535-ish)
* `InputExpression`s (RegEx-ish pattern matching but "safe" and "cheap")
* `JsonPointer` support via `JsonPath` (RFC-6901)
* `Json5` input support (JSON5-ish)
* `Jurl` = JSON in the URL (a JSON notation that is "URL-safe")
* `JsonNode` + `JsonValue` with small footprint `Serializable` support
* `JsonString`-`JsonNumber` + `JsonString`-`JsonBoolean` duality

----

## Specified Edge-cases

* a full JSON node validation will occur at the latest when `JsonNode#endIndex` is called
* duplicate `keys()` in objects are observed
* duplicate key `entries()` are observed with the value of the **first** occurrence
when nodes `Index` cache is effective, otherwise they are observed as defined in JSON

----

## Validation



----

## Performance πŸƒ

`Text` goodness

* `Text`-views (a rich `CharSequence` API instead of `String`s)
* essential APIs all are `Text`-based (avoids lots of array copying)
* JSON string as lazy `Text`-views (if no escaping was used)
* O(1) cached numeric `char[]` for array indexes => `Text`
* O(1) "is numeric" for cache backed numeric `Text`
* O(1) lookup cached short name `Text`s (for loops with string properties)
* O(1) hashing for `Text` (main component in `JsonNode` nodes map)

Slim `JsonNode`s

* more compact `JsonNode` implementations (2 refs + 2 ints per node)
* `JsonNode` + `JsonValue` as `Map.Entry`s (no need for `Map.entry`)

Avoid or defer work (fewer indirections, more control)

* `Streamable` provides rich API with minimal overhead
* Nodes `Index` control for "looping"
* `JsonPath` as chain (no alloc parent access, O(1) append)
* once resolved a `JsonNode` is remembered in `JsonValue`
* JSON numbers as lazy `TextualNumber`-views


----

## That escalated quickly...

The origins of `Text`...

https://github.com/dhis2/json-tree/blob/ce68c5acafe49ad0262967404415adca839e0f88/src/main/java/org/hisp/dhis/jsontree/JsonTree.java#L589

_A few moments later..._

https://github.com/dhis2/json-tree/pull/86

----

## Performance - `Text`-views

from
```
{"key":"value", "key2":42, ...}
|
v
new String("key") ---> new byte[] { ... }
```
to
```
{"key":"value", "key2":42, ...}
^ ^
| |
new Text.Slice(s, e)
```
14 changes: 9 additions & 5 deletions src/main/java/org/hisp/dhis/jsontree/InputExpression.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

import static java.lang.Character.toLowerCase;
import static java.lang.Integer.MAX_VALUE;
import static java.util.Arrays.asList;

import java.util.List;
import java.util.stream.Stream;

/**
* Input patterns are specifically designed to match short sequences where the relevant features to
Expand All @@ -28,7 +28,10 @@
public record InputExpression(List<Pattern> patterns) {

public static InputExpression of(String... patterns) {
return new InputExpression(Stream.of(patterns).map(Pattern::of).toList());
if (patterns.length == 1) return new InputExpression(List.of(Pattern.of(0, patterns[0])));
Pattern[] arr = new Pattern[patterns.length];
for (int i = 0; i < patterns.length; i++) arr[i] = Pattern.of(i, patterns[i]);
return new InputExpression(asList(arr));
}

/**
Expand Down Expand Up @@ -399,17 +402,18 @@ private static int skipUnit(Text pattern, int offset) {
/**
* A pattern is one alternative within a multi-pattern-{@link InputExpression}.
*
* @param ordinal the ID or serial number within an {@link InputExpression#patterns()} list
* @param pattern the pattern to match
* @param minLength the minimum input length required to match the pattern, -1 if not fixed
* @param maxLength the maximum input length possible to match the pattern, -1 if not fixed
* @implNote Using length bounds is purely a performance optimisation that makes use of the
* fact the Input Expressions often have a clear length bounds.
*/
public record Pattern(Text pattern, int minLength, int maxLength) {
public record Pattern(int ordinal, Text pattern, int minLength, int maxLength) {

public static Pattern of(CharSequence pattern) {
public static Pattern of(int ordinal, CharSequence pattern) {
Text p = Text.of(pattern);
return new Pattern(p, length(p, 0, p.length(), false), length(p, 0, p.length(), true));
return new Pattern(ordinal, p, length(p, 0, p.length(), false), length(p, 0, p.length(), true));
}

public boolean matches(CharSequence input) {
Expand Down
9 changes: 4 additions & 5 deletions src/main/java/org/hisp/dhis/jsontree/JsonAbstractObject.java
Original file line number Diff line number Diff line change
Expand Up @@ -115,11 +115,9 @@ default boolean exists(CharSequence name) {
}

/**
* Note that keys may differ from the member names as defined in the JSON document in case that
* their literal interpretation would have clashed with key syntax. In that case the object member
* name is "escaped" so that using the returned key with {@link #get(CharSequence)} will return
* the value. Use {@link #names()} to receive the literal object member names as defined in the
* document.
* Stream of the raw JSON object member names in order of declaration.
* If keys re-occur in the JSON (duplicates) they also re-occur in the stream.
* Use {@link Stream#distinct()} on the result to de-duplicate when needed.
*
* @return The keys of this map.
* @throws JsonTreeException in case this node does exist but is not an object node
Expand Down Expand Up @@ -155,6 +153,7 @@ default Stream<E> entries() {

/**
* Lists raw JSON object member names in order of declaration.
* If keys re-occur in the JSON (duplicates) they also re-occur in the list.
*
* @return The list of object member names in the order they were defined.
* @throws JsonTreeException in case this node does exist but is not an object node
Expand Down
5 changes: 5 additions & 0 deletions src/main/java/org/hisp/dhis/jsontree/JsonObject.java
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,11 @@ default Stream<JsonMixed> entries() {
}

/**
* Whether duplicate keys are observed as the first definition every time or as declared in JSON
* depends on the {@link Index} strategy used. If the index is effective (either from prior
* indexing or because {@link Index#ADD} is used) the first declaration is observed every time the
* key re-occurs, otherwise the value is observed as declared in the JSON source.
*
* @implNote This utilizes {@link JsonNode#members(Index)} avoiding map lookups for each element.
* On {@link JsonAbstractArray} level this cannot be done as the node cannot be {@link
* JsonNode#lift(JsonAccessors)} ed to the unknown generic target type.
Expand Down
Loading
Loading