jackson-databind icon indicating copy to clipboard operation
jackson-databind copied to clipboard

Reading into existing instance uses creator property setup instead of mutator (setter, field)

Open odrotbohm opened this issue 3 months ago • 3 comments

Search before asking

  • [x] I searched in the issues and found nothing similar.

Describe the bug

When reading into an existing object, the JsonMapper uses the deserializer of the creator property instead of the parameter of the accessor.

package example.three;

import static org.assertj.core.api.Assertions.*;

import tools.jackson.databind.json.JsonMapper;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.stream.Stream;

import org.junit.jupiter.api.DynamicTest;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestFactory;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonUnwrapped;

class Jackson3Test {

  @Test
  void readsIntoCreator() throws Exception {

    var mapper = JsonMapper.builder()
        .build();

    mapper.readerForUpdating(new ArrayListHolder("A")).readValue("""
        {
          "values" : [ "A", "B" ]
        }
        """);
  }

  public static class ArrayListHolder {
    Collection<String> values;

    ArrayListHolder(String... values) {
      this.values = new ArrayList<>(Arrays.asList(values));
    }

    public void setValues(Collection<String> values) {
      this.values = values;
    }
  }
}

Stack trace:

tools.jackson.databind.DatabindException: Cannot cast [Ljava.lang.String; to java.util.Collection
 at [Source: REDACTED (`StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION` disabled); byte offset: #UNKNOWN] (through reference chain: example.three.Jackson3Test$ArrayListHolder["values"])
	at tools.jackson.databind.DatabindException.wrapWithPath(DatabindException.java:111)
	at tools.jackson.databind.deser.bean.BeanDeserializerBase.wrapAndThrow(BeanDeserializerBase.java:1850)
	at tools.jackson.databind.deser.bean.BeanDeserializer.deserialize(BeanDeserializer.java:288)
	at tools.jackson.databind.deser.DeserializationContextExt.readRootValue(DeserializationContextExt.java:267)
	at tools.jackson.databind.ObjectReader._bindAndClose(ObjectReader.java:1702)
	at tools.jackson.databind.ObjectReader.readValue(ObjectReader.java:1220)
	at example.three.Jackson3Test.readsIntoCreator(Jackson3Test.java:102)
	at java.base/java.lang.reflect.Method.invoke(Method.java:580)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
Caused by: java.lang.ClassCastException: Cannot cast [Ljava.lang.String; to java.util.Collection
	at java.base/java.lang.Class.cast(Class.java:4067)
	at tools.jackson.databind.deser.impl.MethodProperty.set(MethodProperty.java:179)
	at tools.jackson.databind.deser.CreatorProperty.deserializeAndSet(CreatorProperty.java:219)
	at tools.jackson.databind.deser.bean.BeanDeserializer.deserialize(BeanDeserializer.java:286)
	... 7 more

Works on Jackson 2.

Version Information

3.0 RC8

Reproduction

No response

Expected behavior

No response

Additional context

No response

odrotbohm avatar Aug 26 '25 18:08 odrotbohm

Thank you for reporting this.

I would expect this to fail for 2.x too, but will mark as 3.x as per report.

cowtowncoder avatar Aug 26 '25 23:08 cowtowncoder

Wrote #5286 to verify indeed it did not work since 2.18. Few questions tho, @cowtowncoder

  1. this should work without annotating Collection<String> values; with @JsonMerge?
  2. Also, when working, should it be MergingSettableBeanProperty that takes care of handling?

Then I can go take a look deeper

JooHyukKim avatar Aug 29 '25 13:08 JooHyukKim

Ahhh. Reading exception more carefully, realized the problem is with type being used taken as String[] (from String ... for Creator property) while calling (correctly) Setter method (which takes Collection<String>).

This may be tricky to tackle.

In the meantime I would suggest a work-around of either:

  1. Changing one of signatures so they'd match OR
  2. Adding @JsonIgnore on Constructor to avoid it being detected

cowtowncoder avatar Sep 30 '25 23:09 cowtowncoder