jackson-dataformats-binary icon indicating copy to clipboard operation
jackson-dataformats-binary copied to clipboard

Support @JsonSubTypes in schema generation and serialization

Open cowtowncoder opened this issue 8 years ago • 6 comments

(moved from https://github.com/FasterXML/jackson-dataformat-avro/issues/28 authored by @osi)

I'd like to get the JsonSubTypes annotation working for schema generation and serialization.

For schema generation, I think it should be a union of all the possible sub-types.

For serialization, if the configuration is such that the type name would be included as a property, ignore that, since it will be included as the name of the record type.

I have written some failing tests, but it is unclear to me how to proceed, since the TypeIdResolver doesn't provided a way to interrogate all possible types.

package com.fasterxml.jackson.dataformat.avro;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import org.apache.avro.Schema;
import org.junit.Test;

import java.util.Arrays;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;

public class JsonTypeInfoTest {

    @Test
    public void testGenerateSchemaForSubTypes() throws Exception {
        AvroMapper mapper = new AvroMapper();
        AvroSchema schema = mapper.schemaFor(Thing.class);

        Schema stringOrNull = Schema.createUnion(Arrays.asList(Schema.create(Schema.Type.NULL), Schema.create(Schema.Type.STRING)));
        Schema thingOne = Schema.createRecord(
                ThingOne.class.getSimpleName(),
                "Schema for " + ThingOne.class.getName(),
                ThingOne.class.getPackage().getName(),
                false);
        thingOne.setFields(Arrays.asList(
                new Schema.Field("favoriteColor", stringOrNull, null, null),
                new Schema.Field("name", stringOrNull, null, null)
        ));

        Schema thingTwo = Schema.createRecord(
                ThingTwo.class.getSimpleName(),
                "Schema for " + ThingTwo.class.getName(),
                ThingTwo.class.getPackage().getName(),
                false);
        thingTwo.setFields(Arrays.asList(
                new Schema.Field("favoriteFood", stringOrNull, null, null),
                new Schema.Field("name", stringOrNull, null, null)
        ));

        Schema expected = Schema.createUnion(Arrays.asList(thingOne, thingTwo));
        assertEquals(expected, schema.getAvroSchema());
    }

    @Test
    public void testSerializeSubType() throws Exception {
        AvroMapper mapper = new AvroMapper();
        AvroSchema schema = mapper.schemaFor(Thing.class);

        assertNotNull(mapper.writer(schema).writeValueAsBytes(new ThingOne("hello", "blue")));
    }

    @JsonTypeInfo(use = JsonTypeInfo.Id.NAME,
            include = JsonTypeInfo.As.PROPERTY,
            property = "@type")
    @JsonSubTypes({
            @JsonSubTypes.Type(value = ThingOne.class, name = "one"),
            @JsonSubTypes.Type(value = ThingTwo.class, name = "two")
    })
    public interface Thing {
        @JsonProperty
        String name();
    }

    public static class ThingOne implements Thing {
        public final String favoriteColor;
        private final String name;

        public ThingOne(String name, String favoriteColor) {
            this.name = name;
            this.favoriteColor = favoriteColor;
        }

        @Override
        public String name() {
            return name;
        }
    }

    public static class ThingTwo implements Thing {
        public final String favoriteFood;
        private final String name;

        public ThingTwo(String name, String favoriteFood) {
            this.name = name;
            this.favoriteFood = favoriteFood;
        }

        @Override
        public String name() {
            return name;
        }
    }
}

cowtowncoder avatar May 06 '16 02:05 cowtowncoder

Sort of related: #60.

cowtowncoder avatar Mar 27 '17 21:03 cowtowncoder

If you update the findSubtypes in AvroAnnotationIntrospector to look for @JsonSubTypes in addition to @Union, I think this should just work.

baharclerode avatar Apr 17 '17 03:04 baharclerode

@baharclerode I can do that too if there are no cases where lone @Union would be expected to work? I was assuming that value classes for @Union might include types as alias for @JsonSubTypes to avoid need for separate Jackson annotation, but if that is not correct I can make the change, just let me know.

cowtowncoder avatar Apr 17 '17 22:04 cowtowncoder

@cowtowncoder I meant either/or. So a lone @Union would work, or a lone @JsonSubTypes, or if both are present, union them (Which I think is the behavior you get if you install JacksonAnnotationIntrospector as a secondary in an AnnotationIntrospectorPair, but not sure).

baharclerode avatar Apr 17 '17 23:04 baharclerode

Any updates?

pasolid avatar Dec 26 '22 15:12 pasolid

@pasolid We typically add textual notes on issues if there are updates.

So to my knowledge, no updates.

cowtowncoder avatar Jan 07 '23 02:01 cowtowncoder