Search before asking
- [x] I searched in the issues and found nothing similar.
Describe the bug
If both JsonCreator
and DefaultCreator
are reported, the JsonCreator
should take precedence.
On the other hand, in certain cases, the DefaultCreator
may take precedence.
This issue is a Javaized version of the following https://github.com/FasterXML/jackson-module-kotlin/issues/932 https://github.com/FasterXML/jackson-databind/issues/5040
Version Information
- 2.19.0-SNAPSHOT
- Probably the same for
2.18.3
.
Reproduction
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.cfg.MapperConfig;
import com.fasterxml.jackson.databind.introspect.AnnotatedClass;
import com.fasterxml.jackson.databind.introspect.NopAnnotationIntrospector;
import com.fasterxml.jackson.databind.introspect.PotentialCreator;
import com.fasterxml.jackson.databind.json.JsonMapper;
import org.junit.jupiter.api.Test;
import java.util.List;
public class Kotlin932Java {
static class User {
private final int age;
public User(int age) { this.age = age; }
@JsonCreator
public User() { this(0); }
public int getAge() { return age; }
}
static class AI extends NopAnnotationIntrospector {
@Override
public PotentialCreator findDefaultCreator(MapperConfig<?> config,
AnnotatedClass valueClass,
List<PotentialCreator> declaredConstructors,
List<PotentialCreator> declaredFactories) {
if (valueClass.getRawType() != User.class) return null;
return declaredConstructors.stream()
.filter(it -> it.paramCount() != 0)
.findFirst()
.orElse(null);
}
}
@Test
public void kotlin932() throws JsonProcessingException {
ObjectMapper mapper = JsonMapper.builder().annotationIntrospector(new AI()).build();
String json =
"{\n" +
" \"age\": 25\n" +
"}";
User user = mapper.readValue(json, User.class);
System.out.println(user);
}
}
Expected behavior
Deserialization should succeed, but in fact has no property name ...
is reported.
Additional context
The full stack trace when run on the 2.19
branch of kotlin-module
.
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Invalid type definition for type `com.fasterxml.jackson.module.kotlin.test.github.Kotlin932Java$User`: Argument #0 of Creator [constructor for `com.fasterxml.jackson.module.kotlin.test.github.Kotlin932Java$User` (1 arg), annotations: [null] has no property name (and is not Injectable): can not use as property-based Creator
at [Source: REDACTED (`StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION` disabled); line: 1, column: 1]
at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:62)
at com.fasterxml.jackson.databind.DeserializationContext.reportBadTypeDefinition(DeserializationContext.java:1893)
at com.fasterxml.jackson.databind.deser.BasicDeserializerFactory._addSelectedPropertiesBasedCreator(BasicDeserializerFactory.java:535)
at com.fasterxml.jackson.databind.deser.BasicDeserializerFactory._constructDefaultValueInstantiator(BasicDeserializerFactory.java:259)
at com.fasterxml.jackson.databind.deser.BasicDeserializerFactory.findValueInstantiator(BasicDeserializerFactory.java:209)
at com.fasterxml.jackson.databind.deser.BeanDeserializerFactory.buildBeanDeserializer(BeanDeserializerFactory.java:263)
at com.fasterxml.jackson.databind.deser.BeanDeserializerFactory.createBeanDeserializer(BeanDeserializerFactory.java:152)
at com.fasterxml.jackson.databind.deser.DeserializerCache._createDeserializer2(DeserializerCache.java:472)
at com.fasterxml.jackson.databind.deser.DeserializerCache._createDeserializer(DeserializerCache.java:416)
at com.fasterxml.jackson.databind.deser.DeserializerCache._createAndCache2(DeserializerCache.java:318)
at com.fasterxml.jackson.databind.deser.DeserializerCache._createAndCacheValueDeserializer(DeserializerCache.java:285)
at com.fasterxml.jackson.databind.deser.DeserializerCache.findValueDeserializer(DeserializerCache.java:175)
at com.fasterxml.jackson.databind.DeserializationContext.findRootValueDeserializer(DeserializationContext.java:669)
at com.fasterxml.jackson.databind.ObjectMapper._findRootDeserializer(ObjectMapper.java:5102)
at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4972)
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3887)
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3855)
at com.fasterxml.jackson.module.kotlin.test.github.Kotlin932Java.kotlin932(Kotlin932Java.java:50)
at java.base/java.lang.reflect.Method.invoke(Method.java:569)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
Comment From: cowtowncoder
Ok. Looks like possible bug, although usage is bit odd -- if 0-args Constructor is expected to be used (I think?), how should age
be passed?
Also, instead of
System.out.println(user);
it'd be better to assert (I know, there's now exception instead, but eventually this should work).
I think the problem probably has to do with special handling of "default" (0-args) construct (note: "findDefaultCreator()" is bad name, renamed in 3.0 as "findPreferredCreator". It should be recognized, I suppose, as Properties-based one. But unfortunately there is some specific code for use of default constructors.
ONe quick thing: does addition of mode = Mode.PROPERTIES
on @JsonCreator
help?
Comment From: k163377
if 0-args Constructor is expected to be used
This is the state of affairs for minimum reproduction. Originally, additional properties were registered from the setter after initialization with a 0-args constructor.
Also, instead of
Oops, I forgot to correct that.
mode = Mode.PROPERTIES on @JsonCreator help?
I tried all valid modes, but unfortunately they did not seem to work.
Comment From: cowtowncoder
Trying to figure this out. One big conceptual/modeling problem is that 0-param "default" constructors (alas, overloaded term) are NOT included in regular declared constructors List, but as separate things. Changing that seems risky, unfortunately. But maybe can get this particular case to work at least... somehow.
Comment From: cowtowncoder
Ok: was able to fix. Note tho that when using 0-param Constructor, "age" has to be passed some other way (or ignored) -- I modified test to use empty Object.
Will probably also file a follow-up ticket for actually passing 0-param constructor to AnnotationIntrospector
-- but that will be 3.0-only being API change.