Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Jackson JSON deserialization with multiple parameters constructor

I've been using FasterXML/Jackson-Databind in my project for a while now, and all was working great, until I've discovered this post and started to use this approach to desserialize objects without the @JsonProperty annotations.

The problem is that when I have a constructor which take multiple parameters and decorate this constructor with the @JsonCreator annotation Jackson throw the following error:

Exception in thread "main" com.fasterxml.jackson.databind.JsonMappingException:  Argument #0 of constructor [constructor for com.eliti.model.Cruiser, annotations: {interface com.fasterxml.jackson.annotation.JsonCreator=@com.fasterxml.jackson.annotation.JsonCreator(mode=DEFAULT)}] has no property name annotation; must have name when multiple-parameter constructor annotated as Creator  at [Source: {   "class" : "com.eliti.model.Cruiser",   "inventor" : "afoaisf",   "type" : "MeansTransport",   "capacity" : 123,   "maxSpeed" : 100 }; line: 1, column: 1] 

I've created a little project to illustrate the problem, the class I'm trying to desserialize is this one:

public class Cruise extends WaterVehicle {   private Integer maxSpeed;    @JsonCreator   public Cruise(String name, Integer maxSpeed) {     super(name);     System.out.println("Cruise.Cruise");     this.maxSpeed = maxSpeed;   }    public Integer getMaxSpeed() {     return maxSpeed;   }    public void setMaxSpeed(Integer maxSpeed) {     this.maxSpeed = maxSpeed;   }  } 

And the code to desserialize is like this:

public class Test {   public static void main(String[] args) throws IOException {     Cruise cruise = new Cruise("asd", 100);     cruise.setMaxSpeed(100);     cruise.setCapacity(123);     cruise.setInventor("afoaisf");      ObjectMapper mapper = new ObjectMapper().enable(SerializationFeature.INDENT_OUTPUT);     mapper.registerModule(new ParameterNamesModule(JsonCreator.Mode.PROPERTIES));      String cruiseJson = mapper.writeValueAsString(cruise);      System.out.println(cruiseJson);      System.out.println(mapper.readValue(cruiseJson, Cruise.class));  } 

I already tried to remove the @JsonCreator, but if I do so, the throws the following exception:

Exception in thread "main" com.fasterxml.jackson.databind.JsonMappingException: Can not construct instance of com.eliti.model.Cruise: no suitable constructor found, can not deserialize from Object value (missing default constructor or creator, or perhaps need to add/enable type information?)  at [Source: {   "class" : "com.eliti.model.Cruise",   "inventor" : "afoaisf",   "type" : "MeansTransport",   "capacity" : 123,   "maxSpeed" : 100 }; line: 3, column: 3] 

I have tried to issue a "mvn clean install", but the problem persists.

Just to include some extra information, I've researched thoroughly about this problem (GitHub issues, Blog posts, StackOverflow Q&A). Here are some debbuging/investigation that I have been doing on my end:

Investigation 1

javap -v on the generated bytecode give me this:

 MethodParameters:       Name                           Flags       name       maxSpeed 

When talking about the constructor, so I guess that the -parameters flag is really being set for javac compiler.

Investigation 2

If I create a constructor with a single parameter the object gets initialized, but I want/need to use the multiple parameter constructor.

Investigation 3

If I use the annotation @JsonProperty on each field it works as well, but for my original project it is too much overhead since I have a lot of fields in the constructor (and also it gets very hard to refactor code with annotations).

The question that remain is: How can I make Jackson work with multiple parameter constructor without annotations?

like image 659
pedrostanaka Avatar asked Aug 24 '16 12:08

pedrostanaka


People also ask

Does Jackson need all args constructor?

Jackson won't use a constructor with arguments by default, you'd need to tell it to do so with the @JsonCreator annotation. By default it tries to use the no-args constructor which isn't present in your class.

Does Jackson use constructor or setters?

Deserialization Without No-arg Constructors. By default, Jackson recreates data objects by using no-arg constructors.

Is JsonProperty annotation necessary?

You definitely don't need all those @jsonProperty . Jackson mapper can be initialized to sereliazie/deserialize according to getters or private members, you of course need only the one you are using.

What is JsonProperty annotation?

@JsonProperty is used to mark non-standard getter/setter method to be used with respect to json property.


2 Answers

You need to add the annotation @JsonProperty specifying the name of the json property that needs to be passed to the constructor when creating the object.

public class Cruise extends WaterVehicle {   private Integer maxSpeed;    @JsonCreator   public Cruise(@JsonProperty("name") String name, @JsonProperty("maxSpeed")Integer maxSpeed) {     super(name);     System.out.println("Cruise.Cruise");     this.maxSpeed = maxSpeed;   }    public Integer getMaxSpeed() {     return maxSpeed;   }    public void setMaxSpeed(Integer maxSpeed) {     this.maxSpeed = maxSpeed;   }  } 

EDIT

I just tested using the below code and it works for me

import java.io.IOException;  import com.fasterxml.jackson.annotation.JsonCreator.Mode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.module.paramnames.ParameterNamesModule;  class WaterVehicle {      private String name;     private int capacity;     private String inventor;     public WaterVehicle(String name) {         this.name=name;     }     public String getName() {         return name;     }     public void setName(String name) {         this.name = name;     }     public int getCapacity() {         return capacity;     }     public void setCapacity(int capacity) {         this.capacity = capacity;     }     public String getInventor() {         return inventor;     }     public void setInventor(String inventor) {         this.inventor = inventor;     }   }   class Cruise  extends WaterVehicle{          private Integer maxSpeed;          public Cruise(String name, Integer maxSpeed) {             super(name);             this.maxSpeed = maxSpeed;         }          public Integer getMaxSpeed() {             return maxSpeed;         }          public void setMaxSpeed(Integer maxSpeed) {             this.maxSpeed = maxSpeed;         }       }  public class Test {       public static void main(String[] args) throws IOException {         Cruise cruise = new Cruise("asd", 100);         cruise.setMaxSpeed(100);         cruise.setCapacity(123);         cruise.setInventor("afoaisf");          ObjectMapper mapper = new ObjectMapper().enable(SerializationFeature.INDENT_OUTPUT);         mapper.registerModule(new ParameterNamesModule(Mode.PROPERTIES));          String jsonString = mapper.writeValueAsString( cruise);         System.out.println(jsonString);          Cruise anotherCruise = mapper.readValue(jsonString, Cruise.class);          System.out.println(anotherCruise );          jsonString = mapper.writeValueAsString( anotherCruise );          System.out.println(jsonString);      }  } 

It produces the following output

{   "name" : "asd",   "capacity" : 123,   "inventor" : "afoaisf",   "maxSpeed" : 100 } Cruise@56f4468b {   "name" : "asd",   "capacity" : 123,   "inventor" : "afoaisf",   "maxSpeed" : 100 } 

Make sure you have the compilerArgs in the pom file.

<compilerArgs>      <arg>-parameters</arg> </compilerArgs> 
like image 78
Nithish Thomas Avatar answered Sep 22 '22 17:09

Nithish Thomas


Short answer: use Java 8, javac -parameters, and jackson-module-parameter-names

Long answer: Why when a constructor is annotated with @JsonCreator, its arguments must be annotated with @JsonProperty?

like image 25
Jon Peterson Avatar answered Sep 20 '22 17:09

Jon Peterson