Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

If there is a no-argument constructor marked as JsonCreator and a constructor reported as DefaultCreator, the DefaultCreator is incorrectly used. #5045

Open
1 task done
k163377 opened this issue Mar 22, 2025 · 2 comments
Labels
to-evaluate Issue that has been received but not yet evaluated

Comments

@k163377
Copy link
Contributor

k163377 commented Mar 22, 2025

Search before asking

  • 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
FasterXML/jackson-module-kotlin#932
#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)
@k163377 k163377 added the to-evaluate Issue that has been received but not yet evaluated label Mar 22, 2025
@cowtowncoder
Copy link
Member

cowtowncoder commented Mar 22, 2025

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?

@k163377
Copy link
Contributor Author

k163377 commented Mar 23, 2025

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.

JooHyukKim added a commit to JooHyukKim/jackson-databind that referenced this issue Mar 28, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
to-evaluate Issue that has been received but not yet evaluated
Projects
None yet
Development

No branches or pull requests

2 participants