jackson-module-kotlin icon indicating copy to clipboard operation
jackson-module-kotlin copied to clipboard

Unable to initialise property "type"

Open Simran-Jit-Singh opened this issue 3 years ago • 6 comments

Describe the bug My class

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
@JsonSubTypes(
    JsonSubTypes.Type(value = ClassA::class, name = "A"),
    JsonSubTypes.Type(value = ClassB::class, name = "B"),
    JsonSubTypes.Type(value = ClassC::class, name = "C") ...
)

abstract class A() {
    lateinit var first: <something>
    lateinit var second: <somthing>
    lateinit var id: String
    lateinit var type: String
    lateinit var start: Time ...
}

and data class

data classA(val name: String?, val gender: String) : A() { } ...
data classB() : A() { } ...

To Reproduce When I deserialise the json to data class, the propert type is not initialised and throws run time exception lateinit property type has not been initialized

However I am able to initialize every other property in class A() like id, first, second and many more

Versions Kotlin: 1.6.21 Jackson-module-kotlin: 2.13.3 Jackson-databind: 2.13.3

Additional context Am I missing any annotation?

Simran-Jit-Singh avatar Jun 02 '22 10:06 Simran-Jit-Singh

Not sure if it'd help but there is separate value As.EXISTING_PROPERTY meant to be used when Type Id is not "virtual" property (that is, matches an actual non-metadata Object property). Otherwise assumption is that Type Id is just metadata used for type handling and does not map to/from actual Java/Kotlin side property.

cowtowncoder avatar Jun 02 '22 18:06 cowtowncoder

Thanks for looking into. I get same error with As.EXISTING_PROPERTY and As.EXTERNAL_PROPERTY

sjeet-mobi avatar Jun 03 '22 07:06 sjeet-mobi

Right, you would not want to use EXTERNAL_PROPERTY, that has different semantics (it expects property within "parent" (enclosing) JSON Object).

But unfortunate that EXISTING_PROPERTY makes no difference.

cowtowncoder avatar Jun 03 '22 18:06 cowtowncoder

Ok, whats the solution here?

Simran-Jit-Singh avatar Jun 06 '22 06:06 Simran-Jit-Singh

I don't know, was just trying to suggest things to try. I do not know Kotlin module (or Kotlin) well enough so I hope someone else can comment on specific issue encountered and perhaps offer a work-around.

cowtowncoder avatar Jun 06 '22 15:06 cowtowncoder

Found the solution here: https://fasterxml.github.io/jackson-annotations/javadoc/2.4/com/fasterxml/jackson/annotation/JsonTypeInfo.html Adding visible = true, the property will be passed as-is to deserializers.

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type", visible = true)

Simran-Jit-Singh avatar Jun 07 '22 09:06 Simran-Jit-Singh

This is not a kotlin-module problem in the least, as I can reproduce it with the following Java code. Therefore, this issue is closed. If you have any further discussion on this issue, please submit the issue to databind.

(BTW, the workaround that was shared worked with this code as well)

import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

public class GitHub569 {
    @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
    @JsonSubTypes(@JsonSubTypes.Type(value = ClassA.class, name = "A"))
    static class A {
        private String id;
        private String type;

        public String getId() { return id; }
        public void setId(String id) { this.id = id; }

        // Because of another problem, getter is not provided.
        public String _getType() { return type; }
        public void setType(String type) { this.type = type; }
    }

    static class ClassA extends A {
    }

    public static void main(String[] args) throws JsonProcessingException {
        ClassA a = new ClassA();
        a.setId("id");

        ObjectMapper mapper = new ObjectMapper();
        String serialized = mapper.writeValueAsString(a);
        System.out.println(serialized);
        ClassA deserialized = mapper.readValue(serialized, ClassA.class);
        System.out.println(deserialized._getType());
    }
}

k163377 avatar Feb 28 '23 10:02 k163377

Yes, this is due to Type Erasure: it is STRONGLY RECOMMENDED that polymorphic values are not serialized/deserialized as root-level values but only as nested properties. There are workarounds for the problem but fundamentally Java (and Kotlin to a degree) makes it impossible to avoid the problem: during serialization detected Base Type is different than when deserializing, causing mismatch between expected Type Id on read vs write.

cowtowncoder avatar Feb 28 '23 18:02 cowtowncoder