1

I wonder why spring boot can deserialize the class which has no default constructor by Jackson's objectMapper,but when i mannually using objectMapper in unit test,it can not deserialize(com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of xxx (no Creators, like default constructor, exist): cannot deserialize from Object value (no delegate- or property-based Creator) ).

Here is my controller:

  @PostMapping("/precise")
  public Resp<List<MatchedOutLineResponseDTO>> getPreciseMatchedOutLine(
  @RequestBody List<MatchedOutLineRequestDTO> request) 

Here is my pojo:

@Getter
public class MatchedOutLineRequestDTO {

   private String uuid;
   private JSONObject outLineGeometry;

   public MatchedOutLineRequestDTO(String uuid, JSONObject outLineGeometry) {
     this.uuid = uuid;
     this.outLineGeometry = outLineGeometry;
   }
 }

can someone tell me the reason?

5
  • are you trying to serialize (convert from object to JSON) or are you trying to de-serialize (convert from JSON to object) Commented Dec 14, 2020 at 15:08
  • This isn't very clear. Please provide a minimal reproducible example. And to @SunitChatterjee's point, why do are you using two JSON libraries? Commented Dec 14, 2020 at 15:11
  • The JSON request and the problematic unit test would be useful. Commented Dec 14, 2020 at 15:13
  • @SotiriosDelimanolis I think his application is using the Object Mapper library provided automatically with Spring Boot. However when he is writing unit tests, the Spring Boot library is not available. So he might be creating and using ObjectMapper directly in unit tests Commented Dec 14, 2020 at 15:32
  • @SunitChatterjee yes you are right. I use objectMapper directly in unit tests. Commented Dec 15, 2020 at 7:02

1 Answer 1

4
  1. Json Serialization (Object to JSON)
    • does not requires a no-argument constructor
    • only requires accessor methods (get...) for properties you want to expose
  2. Json Deserialization (JSON to Object)
    • requires a no-argument constructor and setters because the object mapper first creates the class using the no-args constructor and then uses the setters to set the field values.

Consider the code example below

public class Test {
    @Getter
    static class Dummy {
        private String uuid;
        private Date date;
        private JSONObject jsonObject;

        public Dummy(String uuid, Date date, JSONObject jsonObject) {
            this.uuid = uuid;
            this.date = date;
            this.jsonObject = jsonObject;
        }
    }

    public static void main(String[] args) throws JsonProcessingException {
        ObjectMapper mapper = new ObjectMapper();

        JSONObject jsonObject = new JSONObject();
        jsonObject.put("Name", "John Doe");
        jsonObject.put("Age", "Unknown");
        Dummy dummyObj = new Dummy(UUID.randomUUID().toString(), new Date(), jsonObject);

        String jsonStr = mapper.writeValueAsString(dummyObj);
        System.out.println("Serialized JSON = " + jsonStr);
    }
}

If you run this code, the only serialization error you will get is No serializer found for class org.json.JSONObject

The problem is the serialization of the JSONObject class.

The default configuration of ObjectMapper instance is to only access properties that are public fields or have public getters.

Hence this is problem in serialization of org.json.JSONObject.

There can be few workarounds

  1. Remove JsonObject and use a Map instead.

  2. Configure your object mapper to access Private fields by adding this:

    mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
    

You will see the output like this

{
    "uuid": "15f37c75-9a82-476b-a725-90f3742d3de1",
    "date": 1607961986243,
    "jsonObject": {
        "map": {
            "Age": "Unknown",
            "Name": "John Doe"
        }
    }
}
  1. Last option is to ignore failing fields (like JSONObject) and serialize remaining fields. You can do this by configuring the object mapper like this:

    mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
    

If you do this you will see output like this below:

{
    "uuid": "82808d07-47eb-4f56-85ff-7a788158bbb5",
    "date": 1607962486862,
    "jsonObject": {}
}
Sign up to request clarification or add additional context in comments.

3 Comments

What I wonder is when I use spring boot, once a json request come in, spring boot can deserialize the json request atomatically. But as showed above the request class has no default constructor, spring boot can also deserialize the the json request.
I haven't yet tried deserializing a class without constructor in Spring Boot. However what i know is by default the Object mapper in Spring Boot comes with lot of default configuration, and registered modules. The one that we use in Unit Tests are plain Object Mappers without any such configuration.
One similar issue that we faced was around serialization and deserialization of java.time.LocalDate. If we provided LocalDate as "2020-01-01" in json string, Spring Boot was able to deserialize it into LocalDate. However the Object Mapper in Unit Test was not able to do so. We had to add this module in Object Mapper to make it working in unit tests - mapper.registerModule(new JavaTimeModule()); So maybe you can debug your Spring Boot application, get the object mapper and inspect what all modules and configurations are registered in it by default

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.