Skip to content

Deserialization with Builder, External type id, @JsonCreator failing #4742

@bernd

Description

@bernd

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    2.18Issues planned at 2.18 or laterhas-failing-testIndicates that there exists a test case (under `failing/`) to reproduce the issue

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions