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

Deserialization with Builder, External type id, @JsonCreator not yet implemented #4742

Open
1 task done
bernd opened this issue Oct 9, 2024 · 3 comments
Open
1 task done
Labels
2.18 has-failing-test Indicates that there exists a test case (under `failing/`) to reproduce the issue to-evaluate Issue that has been received but not yet evaluated

Comments

@bernd
Copy link

bernd commented Oct 9, 2024

Search before asking

  • I searched in the issues and found nothing similar.

Describe the bug

Starting with version 2.18, we get the following exception when deserializing our auto-value-based objects.

Exception in thread "main" com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Deserialization (of [simple type, class com.github.bernd.javatests.jackson.JacksonBuilderCreatorSubtype$Animal]) with Builder, External type id, @JsonCreator not yet implemented
 at [Source: REDACTED (`StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION` disabled); line: 3, column: 6] (through reference chain: com.github.bernd.javatests.jackson.JacksonBuilderCreatorSubtype$Animals["animals"]->java.util.ArrayList[0])
	at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:67)
	at com.fasterxml.jackson.databind.DeserializationContext.reportBadDefinition(DeserializationContext.java:1888)
	at com.fasterxml.jackson.databind.deser.BuilderBasedDeserializer.deserializeUsingPropertyBasedWithExternalTypeId(BuilderBasedDeserializer.java:828)
	at com.fasterxml.jackson.databind.deser.BuilderBasedDeserializer.deserializeWithExternalTypeId(BuilderBasedDeserializer.java:764)
	at com.fasterxml.jackson.databind.deser.BuilderBasedDeserializer.deserializeFromObject(BuilderBasedDeserializer.java:318)
	at com.fasterxml.jackson.databind.deser.BuilderBasedDeserializer.deserialize(BuilderBasedDeserializer.java:220)
	at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer._deserializeFromArray(CollectionDeserializer.java:361)
	at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:246)
	at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:30)
	at com.fasterxml.jackson.databind.deser.impl.FieldProperty.deserializeAndSet(FieldProperty.java:138)
	at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:310)
	at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:177)
	at com.fasterxml.jackson.databind.deser.DefaultDeserializationContext.readRootValue(DefaultDeserializationContext.java:342)
	at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4917)
	at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3860)
	at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3828)
	at com.github.bernd.javatests.jackson.JacksonBuilderCreatorSubtype.main(JacksonBuilderCreatorSubtype.java:101)

Version Information

2.18.0

Reproduction

This example triggers the bug. Our code uses Google's auto-value, but I wrote the example without auto-value to make it easier to debug. The code works with 2.17 but fails with 2.18.

package com.github.bernd.javatests.jackson;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.json.JsonMapper;

import java.util.List;

public class JacksonBuilderCreatorSubtype {
    public static class Animals {
        @JsonProperty("animals")
        public List<Animal> animals;
    }

    @JsonDeserialize(builder = Animal.Builder.class)
    public static class Animal {
        @JsonProperty("kind")
        public String kind;

        @JsonProperty("properties")
        public AnimalProperties properties;

        @Override
        public String toString() {
            return "Animal{kind='" + kind + '\'' + ", properties=" + properties + '}';
        }

        public static abstract class Builder {
            @JsonProperty("kind")
            public abstract Builder kind(String kind);

            @JsonProperty("properties")
            @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXTERNAL_PROPERTY, property = "kind")
            @JsonSubTypes({
                    @JsonSubTypes.Type(name = "bird", value = BirdProperties.class),
                    @JsonSubTypes.Type(name = "mammal", value = MammalProperties.class)
            })
            public abstract Builder properties(AnimalProperties properties);

            @JsonCreator
            public static BuilderImpl create() {
                return new BuilderImpl();
            }

            public abstract Animal build();
        }

        public static class BuilderImpl extends Builder {
            private String kind;
            private AnimalProperties properties;

            public BuilderImpl kind(String kind) {
                this.kind = kind;
                return this;
            }

            public BuilderImpl properties(AnimalProperties properties) {
                this.properties = properties;
                return this;
            }

            @Override
            public Animal build() {
                final var animal = new Animal();
                animal.kind = kind;
                animal.properties = properties;
                return animal;
            }
        }
    }

    public interface AnimalProperties {
    }

    public static class MammalProperties implements AnimalProperties {
        @JsonProperty("num_teeth")
        public int teeth;

        @Override
        public String toString() {
            return "MammalProperties{teeth=" + teeth + '}';
        }
    }

    public static class BirdProperties implements AnimalProperties {
        @JsonProperty("color")
        public String color;

        @Override
        public String toString() {
            return "BirdProperties{color='" + color + '\'' + '}';
        }
    }

    public static void main(String[] args) throws Exception {
        final var jsonMapper = JsonMapper.builder().build();

        final var animals = jsonMapper.readValue("""
                {
                  "animals": [
                    {"kind": "bird", "properties": {"color": "yellow"}},
                    {"kind": "mammal", "properties": {"num_teeth": 2}}
                  ]
                }
                """, Animals.class);

        animals.animals.forEach(System.out::println);
    }
}

Expected behavior

No response

Additional context

No response

@bernd bernd added the to-evaluate Issue that has been received but not yet evaluated label Oct 9, 2024
@cowtowncoder cowtowncoder added has-failing-test Indicates that there exists a test case (under `failing/`) to reproduce the issue 2.18 labels Oct 10, 2024
@JooHyukKim
Copy link
Member

JooHyukKim commented Oct 20, 2024

Apologies for late response 🙏🏼 First of all, thank you for reporting this!
Seems like this is regression.
I started looking into this.

@JooHyukKim
Copy link
Member

Seems like code path changed by #4515.

So before 2.18 in BuilderBasedDeserializer, used to call deserializeWithExternalTypeId.
But starting 2.18 version is now calling deserializeUsingPropertyBasedWithExternalTypeId.

    protected Object deserializeWithExternalTypeId(JsonParser p, DeserializationContext ctxt)
        throws IOException
    {
        if (_propertyBasedCreator != null) {
            return deserializeUsingPropertyBasedWithExternalTypeId(p, ctxt);
        }
        return deserializeWithExternalTypeId(p, ctxt, _valueInstantiator.createUsingDefault(ctxt));
    }

The _propertyBasedCreator is just empty one.

@JooHyukKim
Copy link
Member

Wrote #4757 to start talking with @cowtowncoder possible direction to fix.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
2.18 has-failing-test Indicates that there exists a test case (under `failing/`) to reproduce the issue to-evaluate Issue that has been received but not yet evaluated
Projects
None yet
Development

No branches or pull requests

3 participants