5

I have a situation where POJOs extend an abstract super class, which defines methods like getId() and setId() using java.io.Serializable type (code shown below). Whenever I am deserializing a JSON string to my concrete POJOs, I get following exception:

Exception in thread "main" com.fasterxml.jackson.databind.JsonMappingException: Can not construct instance of java.io.Serializable, problem: abstract types either need to be mapped to concrete types, have custom deserializer, or be instantiated with additional type information
 at [Source: java.io.StringReader@6fd90825; line: 1, column: 2] (through reference chain: com.demo.jackson.AClass["id"])
    at com.fasterxml.jackson.databind.JsonMappingException.from(JsonMappingException.java:164)
    at com.fasterxml.jackson.databind.DeserializationContext.instantiationException(DeserializationContext.java:716)
    at com.fasterxml.jackson.databind.deser.AbstractDeserializer.deserialize(AbstractDeserializer.java:140)
    at com.fasterxml.jackson.databind.deser.SettableBeanProperty.deserialize(SettableBeanProperty.java:525)
    at com.fasterxml.jackson.databind.deser.impl.MethodProperty.deserializeAndSet(MethodProperty.java:99)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:242)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:118)
    at com.fasterxml.jackson.databind.ObjectReader._bindAndClose(ObjectReader.java:1270)
    at com.fasterxml.jackson.databind.ObjectReader.readValue(ObjectReader.java:897)

The Java codes:

Abstract Super Class

public abstract class AbstractClass {
  protected abstract void setId(final Serializable id);
  protected abstract Serializable getId();
}

Implementation Class: AClass

public class AClass extends AbstractClass {
  private Long id;
  private String name;

  public Long getId() {
    return id;
  }
  public void setId(Serializable id) {
    this.id = (Long) id;
  }
  public String getName() {
    return name;
  }
  public void setName(String name) {
    this.name = name;
  }
}

Implementation Class: BClass

public class BClass extends AbstractClass {
  private String id;
  private String name;

  public String getId() {
    return id;
  }
    public void setId(Serializable id) {
    this.id = (String) id;
  }
  public String getName() {
    return name;
  }
  public void setName(String name) {
    this.name = name;
  }
}

The testing class

public class JsonSerializerTest {
  public static void main(String[] args) throws Exception {
    final ObjectMapper objectMapper = new ObjectMapper();
    serialize(objectMapper);
  }

  private static void serialize(final ObjectMapper objectMapper) throws Exception {
    final String jsonString = "{\"id\":123,\"name\":\"AClass\"}";
    final ObjectReader objectReader = objectMapper.reader(AClass.class);
    final AClass a = objectReader.readValue(jsonString);
    System.out.println(a);
  }
}

Could someone provide some pointers?

~ NN

Niranjan
  • 2,601
  • 8
  • 43
  • 54
  • i dont think serializable is a type, its an interface. also your abstract getid is of type serializable but your concrete method is of type long – chiliNUT Oct 10 '14 at 19:11
  • Yes, I am aware of this and this is something by design. Our framework team provides such interfaces and abstract classes whereas the implementations are provided by the app development team. – Niranjan Oct 10 '14 at 20:07

4 Answers4

5

We had same situation, we have to use java.io.Serializable as ID in entities. With Serialization we have no problem, but in Deserialization we have the issue. In our application we use String in JOSN objects, so Stdandard StringDeserializer and it is working - in theory you can use it with any implementation of a Serializable. (Apache CXF and Jackson 2.4.1 is used):

Initialize Jackson provider:

    // Jackson JSON Provider
    ObjectMapper mapper = new ObjectMapper();
    mapper.registerModule(new IdentifiableDeserializableModule());

    JacksonJaxbJsonProvider jp = new JacksonJaxbJsonProvider(mapper, JacksonJaxbJsonProvider.DEFAULT_ANNOTATIONS);
    jp.configure(SerializationFeature.INDENT_OUTPUT, indentJson);

And the module:

import com.fasterxml.jackson.databind.deser.std.StringDeserializer;
import com.fasterxml.jackson.databind.module.SimpleModule;

import java.io.Serializable;

/**
 * Jackson module to deserialize java.io.Serializable class.
 */
public class IdentifiableDeserializableModule extends SimpleModule {
    public IdentifiableDeserializableModule() {
        addDeserializer(Serializable.class, new StringDeserializer());
    }
}
2

The solution that worked for us is shown below.

Abstract Class: Use Object instead of Serializable. I know Serializable suits better for IDs, but this issue was a kind of blocker for our app and we opted this solution.

public abstract class AbstractClass {
  protected abstract void setId(final Object id);
  protected abstract Object getId();
}

Implementation: AClass

public class AClass extends AbstractClass {
  private Long id;
  private String name;

  public Long getId() {
    return id;
  }
  public void setId(Object id) {
    // We need this inverted way to get a Long from a String, but we didn't have any other option!
    this.id = Long.valueOf(Objects.toString(id));
  }
  public String getName() {
    return name;
  }
  public void setName(String name) {
    this.name = name;
  }
}

Implementation: BClass

public class BClass extends AbstractClass {
  private String id;
  private String name;

  public String getId() {
    return id;
  }
    public void setId(Object id) {
    this.id = Objects.toString(id);
  }
  public String getName() {
    return name;
  }
  public void setName(String name) {
    this.name = name;
  }
}

I do not know if this is an elegant solution or not (compromising(?) Serializable for Objects), but please feel free to post a better one.

Thanks.

~ NN

Niranjan
  • 2,601
  • 8
  • 43
  • 54
  • This is not compromising at all. The implementation is very good. My only concern is related if we are are not free to change the json structure. For example in the Serializable object, we can have String or Long and not only nested objects. If there is such a restriction, this cannot be implemented with this way, I suppose. – dimeros Dec 21 '22 at 05:02
1

I recently ran into this situation. The solution I went with approaches the problem similar to the Answer posted above by Niranjan in using Object in lieu of Serializable. However, instead of using get/set methods with Object or attempting to create a separate custom de-serializer I leveraged the constructor to keep the field type as Serializable while allowing Jackson to provide an instance of Object.

I used the @JsonCreator annotation on the constructor and then individual @JsonProperty annotations on the parameters (this isn't required in Java 8 using the Parameter Names Module making this an even cleaner solution).

This answer also contains extensive information regarding @JsonCreator.

For the purpose of brevity in the answer the class defined below is immutable (all fields final and no setters).

public class PersonLocation {
    private final Serializable id;
    private final String name;
    private final Location location;

    /**
     * Constructor leveraged by Jackson to de-serialize an incoming 
     * PersonLocation instance represented in JSON.
     *
     * @param id The instance identity.
     * @param name The name of the person.
     * @param location The location of the person.
     **/
    @JsonCreator
    public PersonLocation (
        @JsonProperty("id") Object id,
        @JsonProperty("name") String name,
        @JsonProperty("location") Location location)
    {
        //This safety check and cast should be a static utility method.
        if (!id instanceof Serializable) {
            throw new IllegalArgumentException("Id must be a serializable type.");
        }
        this.id = (Serializable)id;
        this.name = name;
        this.location = location;
    }

    public Serializable getId () {
        return id;
    }

    public String getName () {
        return name;
    }

    public Location getLocation () {
        return location;
    }
}
justin.hughey
  • 1,246
  • 15
  • 16
0

I don't know what options you have, but what about overloading the setId methods in the subclasses then make Jackson to ignore those which accept Serializable?

So, ClassA would look like this:

public class AClass extends AbstractClass {
    private Long id;
    private String name;

    public Long getId() {
        return id;
    }
    @JsonIgnore
    public void setId(Serializable id) {
        this.id = (Long) id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}

This solution is not too elegant but it may be acceptable for you. If not then I suggest to check Jackson's custom serialization and/or polymorphic serialization support.

Katona
  • 4,816
  • 23
  • 27
  • Thanks for this, but I am afraid this will not work because `@JsonIgnore` is to ignore the property. So, with this approach my ID value won't be populated during deserialization. Let me glance at the links you provided. I had seen these custom and polymorphic serializations, but somehow I didn't find a good example similar to my problem. – Niranjan Oct 10 '14 at 20:09
  • Ok, but have a look on this question regarding the overloaded methods with JsonIgnore: http://stackoverflow.com/questions/6346018/deserializing-json-into-object-with-overloaded-methods-using-jackson – Katona Oct 10 '14 at 20:54