156

I want to serialize and deserialize an immutable object using com.fasterxml.jackson.databind.ObjectMapper.

The immutable class looks like this (just 3 internal attributes, getters and constructors):

public final class ImportResultItemImpl implements ImportResultItem {

    private final ImportResultItemType resultType;

    private final String message;

    private final String name;

    public ImportResultItemImpl(String name, ImportResultItemType resultType, String message) {
        super();
        this.resultType = resultType;
        this.message = message;
        this.name = name;
    }

    public ImportResultItemImpl(String name, ImportResultItemType resultType) {
        super();
        this.resultType = resultType;
        this.name = name;
        this.message = null;
    }

    @Override
    public ImportResultItemType getResultType() {
        return this.resultType;
    }

    @Override
    public String getMessage() {
        return this.message;
    }

    @Override
    public String getName() {
        return this.name;
    }
}

However when I run this unit test:

@Test
public void testObjectMapper() throws Exception {
    ImportResultItemImpl originalItem = new ImportResultItemImpl("Name1", ImportResultItemType.SUCCESS);
    String serialized = new ObjectMapper().writeValueAsString((ImportResultItemImpl) originalItem);
    System.out.println("serialized: " + serialized);

    //this line will throw exception
    ImportResultItemImpl deserialized = new ObjectMapper().readValue(serialized, ImportResultItemImpl.class);
}

I get this exception:

com.fasterxml.jackson.databind.JsonMappingException: No suitable constructor found for type [simple type, class eu.ibacz.pdkd.core.service.importcommon.ImportResultItemImpl]: can not instantiate from JSON object (missing default constructor or creator, or perhaps need to add/enable type information?)
 at [Source: {"resultType":"SUCCESS","message":null,"name":"Name1"}; line: 1, column: 2]
    at 
... nothing interesting here

This exception asks me to create a default constructor, but this is an immutable object, so I don't want to have it. How would it set the internal attributes? It would totally confuse the user of the API.

So my question is: Can I somehow de/serialize immutable objects without default constructor?

2
  • While desrializing, the desrializer doesn't know about any of your constructor, so it hits default constructor. Due to this you have to create a default constructor, that won't change the immutability. Also I see the class is not final, why so? anybody can override the functionality isn't it? Commented Jun 1, 2015 at 8:04
  • Class is not final, because I forgot to write it there, thank you for noticing :) Commented Jun 1, 2015 at 8:15

5 Answers 5

197

To let Jackson know how to create an object for deserialization, use the @JsonCreator and @JsonProperty annotations for your constructors, like this:

@JsonCreator
public ImportResultItemImpl(@JsonProperty("name") String name, 
        @JsonProperty("resultType") ImportResultItemType resultType, 
        @JsonProperty("message") String message) {
    super();
    this.resultType = resultType;
    this.message = message;
    this.name = name;
}
Sign up to request clarification or add additional context in comments.

8 Comments

+1 thanks Sergey. It worked.. however I was having problem even after using JsonCreator annotation.. but after some review I realized that I forgot to use RequestBody annotation in my resource. Now it works.. Thanx once again.
What if you can't add a default constructor, or the @JsonCreator and @JsonProperty annotations because you don't have access to the POJO to change it? Is there still a way to deserialize the object?
@JackStraw 1) subclass from the object and override all getters and setters. 2) write your own serializer/deserializer for the class and use it (search for "custom serializer" 3) wrap the object and write a serializer/deserializer just for the one property (this may let you do less work than writing a serializer for the class itself, but maybe not).
How can we do this if we are using @Builder Annotation from Lombok?
@manikantanvsr you can add a constructor and move the @Builder annotation to this constructor, instead of the class.
|
53

You can use a private default constructor, Jackson will then fill the fields via reflection even if they are private final.

EDIT: And use a protected/package-protected default constructor for parent classes if you have inheritance.

1 Comment

Just to build on this answer, if the class you want to deserialise extends another class then you can make the default constructor of the super class (or both classes) protected.
32

The first answer of Sergei Petunin is right. However, we could simplify code with removing redundant @JsonProperty annotations on each parameter of constructor.

It can be done with adding com.fasterxml.jackson.module.paramnames.ParameterNamesModule into ObjectMapper:

new ObjectMapper()
        .registerModule(new ParameterNamesModule(JsonCreator.Mode.PROPERTIES))

(Btw: this module is registered by default in SpringBoot. If you use ObjectMapper bean from JacksonObjectMapperConfiguration or if you create your own ObjectMapper with bean Jackson2ObjectMapperBuilder then you can skip manual registration of the module)

For example:

public class FieldValidationError {
  private final String field;
  private final String error;

  @JsonCreator
  public FieldValidationError(String field,
                              String error) {
    this.field = field;
    this.error = error;
  }

  public String getField() {
    return field;
  }

  public String getError() {
    return error;
  }
}

and ObjectMapper deserializes this json without any errors:

{
  "field": "email",
  "error": "some text"
}

5 Comments

Excellent Answer, Works well
How do you use this, if you dont manually initialize ObjectMapper?
I inject it with Spring Context. Spring boot provides a bean of ObjectMapper
Is there a way to have this configuration on a per pojo basis? annotate the pojo somehow to tell object mapper what strategy to use to deserializee?
I do not believe so... maybe you can use a separate object mappers for separate pojos
5

It's 2021, I had the same issue. Unfortunately, the previous answers in this thread weren't helpful in my case, because:

  1. I need to deserialize java.net.HttpCookie class object. I can't modify it.
  2. Due to some reasons, I use jackson-databind-2.11.0 version. ParameterNamesModule has been added in 3+ version.

So, here's the solution I've found for my case. You can just use DeserializationProblemHandler:

    objectMapper.addHandler(new DeserializationProblemHandler() {


        @Override
        public Object handleMissingInstantiator(DeserializationContext ctxt, Class<?> instClass, ValueInstantiator valueInsta, JsonParser p, String msg) throws IOException {
            return super.handleMissingInstantiator(ctxt, instClass, valueInsta, p, msg);
        }

    });

Just return the object which you're expecting

1 Comment

Excellent Answer, Works well
0

Since Java 14 and Jackson 2.12.0 you can use a record class like this:

public record ImportResultItemImpl(String name, ImportResultItemType resultType, String message) implements ImportResultItem {
    
    public ImportResultItemImpl(String name, ImportResultItemType resultType) {
        // calling the default constructor
        this(name, resultType, null);
    }
}

Also, you will have to refactor your interface and usage because record's getters do not start with get or is:

public interface ImportResultItem {

    String name();

    ImportResultItemType resultType();

    String message();
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.