jackson-module-model-versioning icon indicating copy to clipboard operation
jackson-module-model-versioning copied to clipboard

Migration from JsonSubTypes with empty super type fails

Open frawa opened this issue 4 years ago • 1 comments

Given the types MySuperDto, MySubDto, MyEmptySubDto, see below. For version 1 the super type added a the field superField, but was empty at version 0.

Reading this Json, at version 0, with a reader for the super type MySuperDto

{
   "type" : "MyEmptySubDto"
}

will fail throwing

com.fasterxml.jackson.databind.JsonMappingException: value must be a JSON object
 at [Source: (String)"{
  "type" : "MyEmptySubDto"}"; line: 2, column: 27]

	at com.fasterxml.jackson.databind.JsonMappingException.from(JsonMappingException.java:274)
	at com.fasterxml.jackson.databind.DeserializationContext.mappingException(DeserializationContext.java:1844)
	at com.github.jonpeterson.jackson.module.versioning.VersionedModelDeserializer.deserialize(VersionedModelDeserializer.java:75)
	at com.fasterxml.jackson.databind.jsontype.impl.AsPropertyTypeDeserializer._deserializeTypedForId(AsPropertyTypeDeserializer.java:130)
	at com.fasterxml.jackson.databind.jsontype.impl.AsPropertyTypeDeserializer.deserializeTypedFromObject(AsPropertyTypeDeserializer.java:97)
	at com.fasterxml.jackson.databind.deser.AbstractDeserializer.deserializeWithType(AbstractDeserializer.java:254)
	at com.fasterxml.jackson.databind.deser.impl.TypeWrappedDeserializer.deserialize(TypeWrappedDeserializer.java:68)
	at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4218)
	at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3214)
	at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3182)

Details on sub types

    @JsonTypeInfo(
            use = JsonTypeInfo.Id.NAME,
            property = "type"
    )
    @JsonSubTypes({
            @JsonSubTypes.Type(value = MySubDto.class, name = "MySubDto"),
            @JsonSubTypes.Type(value = MyEmptySubDto.class, name = "MyEmptySubDto")
    })
    @JsonVersionedModel(
            currentVersion = "1",
            defaultDeserializeToVersion = "0",
            toCurrentConverterClass = ToCurrentMySuperDto.class)
    static abstract class MySuperDto {

        @JsonProperty(required = true)
        public String superField;

        @JsonCreator
        public MySuperDto(
                @JsonProperty(required = true, value = "superField") String superField
        ) {
            this.superField = superField;
        }
    }

    static class MySubDto extends MySuperDto {
        @JsonProperty
        String subField;

        @JsonCreator
        public MySubDto(
                @JsonProperty(required = true, value = "superField") String superField,
                @JsonProperty(required = true, value = "subField") String subField
        ) {
            super(superField);
            this.subField = subField;
        }
    }

    static class MyEmptySubDto extends MySuperDto {
        @JsonCreator
        public MyEmptySubDto(
                @JsonProperty(required = true, value = "superField") String superField
        ) {
            super(superField);
        }
    }

frawa avatar May 14 '20 12:05 frawa

A fix might be to add something like

if (jsonNode.isNull()) {
                jsonNode = (JsonNode) parser.getCodec().createObjectNode();
}

before the deserializer throws.

@jonpeterson If you think that's a feasible approach, I can give it a try.

frawa avatar May 14 '20 12:05 frawa