Skip to content

ArrayStoreException for Builder-based type with Object Id when a forward reference in a typed array is resolved by a later element of the same array #6225

Description

@cowtowncoder

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);
}

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions