Version
3.2.x (not applicable to 3.1, which does not allow Object Id with Builder-based deserialization)
Description
Follow-up to #5946. If a typed array (T[]) has a forward Object Id reference, and a later element of the same array resolves it, deserialization fails with ArrayStoreException. This only happens when T is built with a Builder.
The cause is that ObjectArrayReferringAccumulator.replaceResolvedItem() returns early when _array == null, which is the case while the array is still being read. The reference has already been resolved to the Builder, and that Builder is stored in the accumulator. When the Builder is later replaced by the built value, the accumulator is never updated. buildArray() then tries to store the Builder into a T[].
If the reference is resolved after the array has been built, it works correctly. The same structure with List or Set also works.
Reproduction
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
@JsonDeserialize(builder = EB.class)
static class E {
final int id; final E[] array;
E(EB b) { id = b.id; array = b.array; }
public int getId() { return id; }
}
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
@JsonPOJOBuilder(withPrefix = "")
static class EB {
int id; E[] array;
public EB id(int v) { id = v; return this; }
public EB array(E[] v) { array = v; return this; }
public E build() { return new E(this); }
}
E e = mapper.readValue("""
{"id":0,"array":[1,{"id":1}]}
""", E.class);
// expected: e.array[0] == e.array[1]
fails with:
tools.jackson.databind.DatabindException: ...
Caused by: java.lang.ArrayStoreException: ...$EB
at ...ObjectArrayDeserializer$ObjectArrayReferringAccumulator.buildArray(ObjectArrayDeserializer.java)
at ...ObjectArrayDeserializer._deserializeWithObjectId(ObjectArrayDeserializer.java)
Suggested fix
When _array == null, update the accumulator slot anyway instead of returning:
final int index = forRef._index;
final Object slot = _accumulator.get(index);
if (slot == forRef || slot == oldItem) {
if (_array != null) {
_array[index] = newItem;
}
_accumulator.set(index, newItem);
}
Version
3.2.x (not applicable to 3.1, which does not allow Object Id with Builder-based deserialization)
Description
Follow-up to #5946. If a typed array (
T[]) has a forward Object Id reference, and a later element of the same array resolves it, deserialization fails withArrayStoreException. This only happens whenTis built with a Builder.The cause is that
ObjectArrayReferringAccumulator.replaceResolvedItem()returns early when_array == null, which is the case while the array is still being read. The reference has already been resolved to the Builder, and that Builder is stored in the accumulator. When the Builder is later replaced by the built value, the accumulator is never updated.buildArray()then tries to store the Builder into aT[].If the reference is resolved after the array has been built, it works correctly. The same structure with
ListorSetalso works.Reproduction
fails with:
Suggested fix
When
_array == null, update the accumulator slot anyway instead of returning: