Jackson¶
dev.zazr:zazr-jackson is a Jackson 3 module. Once it is registered, Jackson reads and writes the Zazr collections,
Option and the tuples, and so the records and classes that hold them.
Add the dependency¶
zazr-jackson ships from Zazr 0.3.0. It depends on zazr-core and on Jackson 3
(tools.jackson.core:jackson-databind), which your project already has if it uses Jackson 3.
If your project uses Java modules, add requires dev.zazr.jackson; to its module-info.java.
Register the module¶
Add ZazrModule when you build the mapper:
findAndAddModules() on the builder finds it too, as it finds every Jackson module on the class path or module path.
A record that holds Zazr types then reads and writes like any other:
record Line(String sku, int quantity) {}
record Order(Vector<Line> lines, Option<String> note) {}
var order = new Order(Vector.of(new Line("A-1", 2)), Option.none());
var json = mapper.writeValueAsString(order);
var back = mapper.readValue(json, Order.class); // Order
// json is {"lines":[{"sku":"A-1","quantity":2}],"note":null}, back equals order
The JSON format¶
| Zazr type | JSON | Example |
|---|---|---|
Vector, List, Queue, LazyList |
array | [1,2,3] |
HashSet, LinkedHashSet, TreeSet |
array | ["a","b"] |
NonEmptyVector, NonEmptySet, NonEmptySortedSet |
array of at least one element | [1] |
HashMap, LinkedHashMap, TreeMap |
object | {"a":1} |
NonEmptyMap, NonEmptySortedMap |
object of at least one entry | {"a":1} |
Option |
the value, or null for None |
"x", null |
Tuple1 to Tuple8 |
array of 1 to 8 elements | [1,"a"] |
Map keys go through Jackson's key serializers and deserializers, so a key can be a String, a number, an enum, a
UUID, a java.time value, or any type Jackson has a key deserializer for.
Reading¶
Jackson reads each element, key and value with the type the declaration gives it, at any depth:
var byDate = new TypeReference<HashMap<LocalDate, Vector<String>>>() {};
var dates = mapper.readValue("{\"2026-10-03\":[\"a\"]}", byDate);
var counts = mapper.readValue("[1,null,3]", new TypeReference<Vector<Option<Integer>>>() {});
// dates is HashMap((2026-10-03, Vector(a))), counts is Vector(Some(1), None, Some(3))
A property declared as one of the interfaces reads into a concrete type:
| Declared as | Reads as |
|---|---|
Set |
HashSet |
SortedSet |
TreeSet |
Map |
HashMap |
SortedMap |
TreeMap |
To read a property declared as Traversable, name the type with @JsonDeserialize(as = Vector.class).
Order¶
TreeSet,TreeMap,NonEmptySortedSetandNonEmptySortedMapuse the natural order: their element or key type must implementComparable. If it does not, reading fails with a message that names the type.LinkedHashSetandLinkedHashMapkeep the order of the JSON.- When a JSON object has the same key twice, the later value wins.
HashSetandHashMaphave no order; written to JSON, they follow their iteration order.
Option¶
Noneis written asnull, andnullreads asNone.- A record component or a constructor parameter that is absent from the JSON reads as
None. With Jackson 3.1'sDeserializationFeature.USE_NULL_FOR_MISSING_REFERENCE_VALUESenabled, it reads asnullinstead. - A field or a setter property that is absent keeps its initial value: initialise it with
Option.none(). @JsonInclude(JsonInclude.Include.NON_ABSENT)on a property leaves it out of the JSON when it isNone.
What fails¶
Reading fails, with a message that says what and where, on:
- a
nullelement, key or value of a collection, unless its type readsnullas a value, asOptiondoes; - an empty array or object for
NonEmptyVector,NonEmptySet,NonEmptySortedSet,NonEmptyMapandNonEmptySortedMap; - an array whose length differs from the size of the tuple it is read into.
var numbers = new TypeReference<Vector<Integer>>() {};
var failure = Try.of(() -> mapper.readValue("[1,null]", numbers)).getCause(); // Throwable
// failure.getMessage() starts with "Element 1 of the Vector is null: Zazr collections hold no null."
A tuple holds null components, as Tuple.of(1, null) does: [1,null] reads as that tuple.
A collection property that is null in the JSON reads as null, as a java.util collection does. With
@JsonSetter(nulls = Nulls.AS_EMPTY) on the property, it reads as the empty collection.
Writing¶
- A value declared as
Objector as an interface is written by its runtime type. - A
LazyListis written by iterating it, so it must be finite. - Jackson 3 writes and reads an enum with its
toString()by default (EnumFeature.WRITE_ENUMS_USING_TO_STRING), as an element and as a map key; an enum that overridestoString()appears in the JSON as that string.
Type ids¶
With @JsonTypeInfo or default typing, the type id of a Zazr collection names its public type: List and LazyList
for every list, whatever class holds it. With use = JsonTypeInfo.Id.NAME, it is the name that @JsonSubTypes
gives List or LazyList.
The elements of a collection, the values of a map and the components of a tuple get the type ids that their declared
types take, at any depth. That includes the elements of a collection property and the values of a map property
annotated with @JsonTypeInfo.
Such JSON reads back, with these exceptions:
- An
Optionis written as its value, with the type id of the value, as Jackson does forOptional. - A
TreeSet,TreeMap,NonEmptySortedSetorNonEmptySortedMapin a property declared asObjectdoes not read back: its element or key type is thenObject, which has no natural order. - A property declared as
Traversablereads only with@JsonDeserialize(as = ...). - With
@JsonTypeInfo(use = NAME)on anOptionof aListor aLazyList, the id read is the name ofListorLazyList: name them in@JsonSubTypes. - A generic tuple written as the root value with
writerFor(...)underDefaultTyping.NON_FINAL_AND_RECORDSdoes not read back, as for any generic record in Jackson: hold it in a property of a record, or use anotherDefaultTyping. - A tuple whose component has another type than its declared one, after an unchecked cast, fails when it is written.
Not covered¶
Either,Try,Validation,LazyandTuple0have no JSON format in this module. Map them to a covered type first, for example withtoOption()on anEither, aTryor aValidation, orget()on aLazy.- The module works with Jackson 3 (
tools.jackson), the version Spring Boot 4 uses. Jackson 2 (com.fasterxml.jackson) is out of its scope.