8

I am trying to deserialize an object using JSON ObjectMapper. I see the below error when trying to deserialize

com.fasterxml.jackson.databind.JsonMappingException: Can not construct instance of com.phoenix.types.OrderItem: abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information at [Source: java.io.StringReader@4bb33f74; line: 112, column: 7] (through reference chain: com.phoenix.types.GenerateRequest["order"]->com.phoenix.types.Order["orderItems"]->Object[][0]) at com.fasterxml.jackson.databind.JsonMappingException.from(JsonMappingException.java:261) at com.fasterxml.jackson.databind.DeserializationContext.instantiationException(DeserializationContext.java:1456) at com.fasterxml.jackson.databind.DeserializationContext.handleMissingInstantiator(DeserializationContext.java:1012) at com.fasterxml.jackson.databind.deser.AbstractDeserializer.deserialize(AbstractDeserializer.java:149) at com.fasterxml.jackson.databind.deser.std.ObjectArrayDeserializer.deserialize(ObjectArrayDeserializer.java:196) at com.fasterxml.jackson.databind.deser.std.ObjectArrayDeserializer.deserialize(ObjectArrayDeserializer.java:20) at com.fasterxml.jackson.databind.deser.SettableBeanProperty.deserialize(SettableBeanProperty.java:499) at com.fasterxml.jackson.databind.deser.impl.MethodProperty.deserializeAndSet(MethodProperty.java:101) at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:276) at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:140) at com.fasterxml.jackson.databind.deser.SettableBeanProperty.deserialize(SettableBeanProperty.java:499) at com.fasterxml.jackson.databind.deser.impl.MethodProperty.deserializeAndSet(MethodProperty.java:101) at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:276) at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:140) at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:3789) at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:2833)

I came across this post for performing polymorphic deserialization. This basically provides the solution for resolving the error listed above. The classes (In this instance OrderItem etc) that I am using for deserialization are part of a jar file. However is there a way to define JsonDeserialize as part of objectmapper when trying to deserialize instead of adding annotation to the class itself as I don't have access to it.

Punter Vicky
  • 14,140
  • 41
  • 164
  • 279

4 Answers4

15

Yes, you can write your own Custom Deserializer for the abstract class. This deserializer will have to determine which concrete class the JSON represents, and instantiate an instance of it.

There is likely a more idiomatic way of doing this, but here is a quick-and-dirty example:

public class Test {
    public static void main(String... args) throws IOException {
        final ObjectMapper mapper = new ObjectMapper();
        final SimpleModule module = new SimpleModule();
        module.addDeserializer(Animal.class, new AnimalDeserializer());
        mapper.registerModule(module);

        final String json = "{\"aGoodBoy\": true}";
        final Animal animal = mapper.readValue(json, Animal.class);
        System.out.println(animal.talk());
    }

    public static abstract class Animal {
        public abstract String talk();
    }

    public static class Fish extends Animal {
        @Override
        public String talk() {
            return "blub blub I'm a dumb fish";
        }
    }

    public static class Dog extends Animal {
        public boolean aGoodBoy;

        @Override
        public String talk() {
            return "I am a " + (aGoodBoy ? "good" : "bad") + " dog";
        }
    }

    public static class AnimalDeserializer extends StdDeserializer<Animal> {
        protected AnimalDeserializer() {
            this(null);
        }

        protected AnimalDeserializer(final Class<?> vc) {
            super(vc);
        }

        @Override
        public Animal deserialize(final JsonParser parser, final DeserializationContext context)
        throws IOException, JsonProcessingException {
            final JsonNode node = parser.getCodec().readTree(parser);
            final ObjectMapper mapper = (ObjectMapper)parser.getCodec();
            if (node.has("aGoodBoy")) {
                return mapper.treeToValue(node, Dog.class);
            } else {
                return mapper.treeToValue(node, Fish.class);
            }
        }
    }
}
Andrew Rueckert
  • 4,433
  • 1
  • 30
  • 40
  • Thanks Andrew. Is there an example to specify concrete classes for abstract classes using custom deserializer? – Punter Vicky Jun 05 '17 at 18:58
  • Updated with a code example. I'm sure there's a better way of getting the underlying `ObjectMapper` than casting the ObjectCodec, but I'm having trouble finding it off the top of my head. You can also just manually construct the concrete instances, but I'd kind of like to avoid doing that work. – Andrew Rueckert Jun 05 '17 at 23:41
  • This saved my day. – आनंद Jun 08 '21 at 12:58
1
Abhinav Atul
  • 515
  • 5
  • 14
0

MyClass.java:

class MyClass<T extends Serializable> implements Serializable{

    @JsonProperty(value = "allowedValues", required = false)
    private List<T> allowedValues;
}

In my cases , the type is List. Due to this , I'm getting this exception

Error while de-serializing json to MyClass.java: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of java.io.Serializable (no Creators, like default construct, exist): abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information at [Source: (String)"{"allowedValues":["a","b","c"]} (through reference chain: com.babu.model.MyClass->["allowedValues"]->java.util.ArrayList[0])

How do i resolve this?

Babu Pasungily
  • 215
  • 3
  • 13
0

Jackson try to create an instance of your abstract class.

Write @JsonIgnore annotation for this field in your abstract class and create the getter for this field. Then write another annotation on this getter @JsonProperty("{{nameOfYourField}}")

@JsonIgnore
@JoinColumn(name = "column_name")
private OrderItem orderItem;

@JsonProperty("orderItem")
public OrderItem getOrderItem () {
    return orderItem;
}