javajsonjacksondeserializationjson-deserialization

Jackson deserialization interface on multiple types


I'm experimenting some troubles with Jackson deserialization in Java. I've made 2 solutions, and I can't resolve the problem. Problem? I got my result with the property duplicated, a field it's duplicated after jackson deserialization. (My problem is exact the same as this question: Avoid duplicate field generated by JsonTypeInfo in Jackson and no one could give you an answer at the time)

First at all, I have the following class:

@JsonIgnoreProperties(ignoreUnknown = true)
public class Instance {

    @JsonProperty("id")
    private String id;
 
    @JsonProperty("name")
    private String name;

    @JsonProperty("type")
    private InstanceType type;
 }

What I'm triying to do, is just instantiate an object of type 'Instance', save it and read it. And with solution 2, the object is saved with the type duplicated (type appear as array that contain 'name', 'firs_type', for example or 'second_type) depends on what I create. With solution 1, I can save the object ok, but when I try to read it, I fall on a jackson exception casting.

Solution 1:

@JsonDeserialize(using = InstanceTypeDeserializer.class)
public interface InstanceType {
    String value();
}

@JsonDeserialize(as = HardInstanceType.class)
public enum HardInstanceType implements InstanceType {
    FIRST_TYPE("first_type"),
    SECOND_TYPE("second_type")
    private String value;

    HardInstanceType(String value) {
        this.value = value;
    }

    @JsonValue
    public String value() {
       return value;
    }
}

@JsonDeserialize(as = SoftInstanceType.class)
public enum SoftInstanceType implements InstanceType {
    //.. types implementaion similar as HardInstanceType
}

public class InstanceTypeDeserializer extends JsonDeserializer<InstanceType> {
    @Override
    public InstanceType deserialize(JsonParser jp,  DeserializationContext ctxt) throws IOException, JsonProcessingException {
        ObjectMapper mapper = (ObjectMapper) jp.getCodec();
        ObjectNode root = (ObjectNode) mapper.readTree(jp);
        
        if(root.get("name").asText().equals("hard")) {
            return mapper.readValue(root.toString(), HardInstanceType.class);
        } else { 
            return mapper.readValue(root.toString(), SoftInstanceType.class);
        }
    }
}

The problem with this solution, is that when I try to get the data stored, and map to the class, I get the following error:

exception parsing json: com.fasterxml.jackson.databind.JsonMappingException: class com.fasterxml.jackson.databind.node.TextNode cannot be cast to class com.fasterxml.jackson.databind.node.ObjectNode (com.fasterxml.jackson.databind.node.TextNode and com.fasterxml.jackson.databind.node.ObjectNode are in unnamed module of loader org.springframework.boot.loader.LaunchedURLClassLoader @1a3e8e24) (through reference chain: java.util.ArrayList[0]->com.project.package.xxxx.Instance["type"])

Solution 2

@JsonTypeInfo(
    use = JsonTypeInfo.Id.NAME,
    include = JsonTypeInfo.As.PROPERTY,
    property = "name")
@JsonSubTypes({
    @JsonSubTypes.Type(value = HardInstanceType.class, name = "hard") })
public interface InstanceType {
   String value();
}

The problem with this solution, is that when I save the data, when I create an Instance object, and store it, in the data Stored, I get the following:

      "id": "1",
      "name": "hard",
      "type": [
        "hard",
        "first_type"
      ]

what is not correct, in type should be store just "first_type" (what is stored with solution 1, but I can't read it haha).

Of course, Instace class is more complex and with more fields, I reduce it here, just for the example.

I need help with this, thank you very much in advance.


Solution

  • Finally, I could solve the problem. I post this just in case someone else need it.

    1. Add a property to my HardInstanceType class.

      public enum HardInstanceType implements InstanceType {
      
          FIRST_TYPE("first_type"),
          SECOND_TYPE("second_type");
          private String value;
      
          public String hardTypeIdentifierSer = "hardTypeIdentifierSer";
      
          HardInstanceType(String value) {
              this.value = value;
          }
      
          @JsonValue
          public String value() {
             return value;
          }
      }
      
    2. Then, in the deserializer:

      public class InstanceTypeDeserializer extends JsonDeserializer<InstanceType> {
      
          @Override
          public InstanceType deserialize(JsonParser jp,  DeserializationContext ctxt) throws IOException, JsonProcessingException {
              TreeNode node = jp.readValueAsTree();
      
              if (node.get("hardTypeIdentifierSer") != null) {
                  return jp.getCodec().treeToValue(node, HardInstanceType.class);
              }
          }
      }