The Document API
The "Document API" is the "full" intermediate structured data tree. This is a sealed hierarchy of types that looks like this:
DocumentElement
NonValueElement
FormattingElement
CommentElement
ValueElement
ObjectElement
ArrayElement
PrimitiveElement
BooleanElement
DoubleElement
LongElement
StringElement
NullElement
KeyValuePairElement
Note that all of the above types are public API, and that DoubleElement and LongElement are distinct types, which can be hit with a pattern-matching switch. Additionally, KeyValuePairs inside an ObjectElement are exposed, which can be useful for setting or inspecting prologue comments.
Non-Value Elements
There are two kinds of things which are "not a value" in an object tree, and these things are always attached to (or "attributed to") ValueElements.
Formatting is generally some kind of whitespace in the document. Currently the only usage recommended is FormattingElement.NEWLINE, but you are currently free to emit any additional whitespace you want.
Comments are supported in many types - although a destination format may not support them, in which case the comment may be transformed into a different type:
- LINE_END (
// comment) - OCTOTHORPE (
# comment) - MULTILINE (
/* comment */) - DOC (
/** comment */)
JSON5 supports all these types, so you can expect the default JsonWriter to emit these comments unharmed.
CommentElement is mutable, so you can setValue("") to change the comment text. Currently you cannot set the comment type in an existing document, you would replace the comment with a new one of the desired type instead.
Key-Value Pairs
These are a special category of DocumentElement that are neither value nor non-value elements. As such, they can't be in places where Value is expected, like "as a list element" or "the root element of a document". KeyValuePairElement implements Map.Entry<String, ValueElement> and consists of the key combined with the ValueElement as you might expect. You'll see these primarily in the Iterator that ObjectElement provides.
Value Elements
Anything that is vald as a root element in a JSON file is a ValueElement. See above for hierarchy.
Values are Optionals
Because JSON null is a JSON value, and all values are nullable in JSON, the Document API treats all values as if they were Optionals.
The following methods unconditionally return nonnull ValueElements. However, those elements may be equivalent to an Optional.empty().
PrimitiveElement ObjectElement::getPrimitive(String key);
ArrayElement ObjectElement::getArray(String key);
ObjectElement ObjectElement::getObject(String key);
PrimitiveElement ArrayElement::getPrimitive(int index);
ArrayElement ArrayElement::getArray(int index);
ObjectElement ArrayElement::getObject(int index);
This makes it possible to dive several levels deep for a given piece of data, and only have to deal with the optional-unwrap once:
ObjectElement root = Jankson.readJson(in);
PrimitiveElement connectionTimeout = root
.getArray("servers")
.getObject(0)
.getObject("connections")
.getPrimitive("timeout");
// ConnectionTimeout may be PrimitiveElement.ofNull() at this point,
// but it will never be *Java* null.
If you'd prefer to handle the information about presence or absence of keys yourself, you can use the "try" versions (such as ObjectElement::tryGetPrimitive(String key)) to get an Optional containing your element. If the Optional is empty, there was no mapping for that key or index. If it is present, and the object was PrimitiveElement.ofNull(), then a null was explicitly recorded there.
PrimitiveElements can be projected into Optionals of an expected type
Optional<String> PrimitiveElement::asString();
Optional<Boolean> PrimitiveElement::asBoolean();
OptionalDouble PrimitiveElement::asDouble();
OptionalLong PrimitiveElement::asLong();
OptionalInt PrimitiveElement::asInt();
// These expect a json string!
Optional<BigInteger> PrimitiveElement::asBigInteger();
Optional<BigDecimal> PrimitiveElement::asBigDecimal();
They can be unwrapped with a default value just like Optional::orElse
String PrimitiveElement::orElse(String value);
boolean PrimitiveElement::orElse(boolean value);
double PrimitiveElement::orElse(double value);
long PrimitiveElement::orElse(long value);
They can be mapped just like Optional::map
Optional<T> PrimitiveElement::mapAsString(Function<String, T> mapper);
Optional<T> PrimitiveElement::mapAsBoolean(Function<Boolean, T> mapper);
Optional<T> PrimitiveElement::mapAsDouble(DoubleFunction<T> mapper);
Optional<T> PrimitiveElement::mapAsLong(LongFunction<T> mapper);
Optional<T> PrimitiveElement::mapAsInt(IntFunction<T> mapper);
They cannot be directly filtered, flat-mapped, or otherwise transformed without first projecting into a typed Optional.
Objects and Arrays are Collections
ObjectElement, even the "empty" object, implements Map<String, ValueElement>. Unlike Map, it can also be directly iterated over, without first calling entrySet(). ArrayElement, even the "empty" array, implements List<ValueElement>. It supports all the new operations, including streaming.