35

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?

pedrostanaka
  • 711
  • 1
  • 9
  • 16

6 Answers6

45

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>
Gama11
  • 31,714
  • 9
  • 78
  • 100
Nithish Thomas
  • 1,515
  • 14
  • 20
  • 2
    That's the thing, I don't want to use annotations in each constructor parameter, because in my original project there are a lot of parameters in classes. I wanted to use something like this: https://manosnikolaidis.wordpress.com/. – pedrostanaka Aug 24 '16 at 12:33
  • My issue got fixed when I added a single-argument string constructor. @JsonProperty doesn't work though. Not sure what is the reason. – Srini Karthikeyan Jul 20 '21 at 16:47
14

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?

Community
  • 1
  • 1
Jon Peterson
  • 2,966
  • 24
  • 32
  • 2
    Thanks for the input, but this solution did not work as well. It only seems to work in classes where there is only one constructor. When you have multiple constructors you have to hint Jackson which one to use to construct your objects. – pedrostanaka Aug 24 '16 at 15:52
11

@JsonCreator is not required after having @JsonProperty("xxx") in the parameter

sendon1982
  • 9,982
  • 61
  • 44
0

If you have nested inner class make it static

public class A{
    private int property1;
    public static class B{
        private String property2;
    }
}
Asif
  • 465
  • 5
  • 6
0

Registering the below modules helped:

JavaTimeModule was required for my use case

ObjectMapper mapper = new ObjectMapper();
    mapper.registerModule(new JavaTimeModule());
    mapper.registerModule(new GuavaModule());
    mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.OBJECT_AND_NON_CONCRETE, JsonTypeInfo.As.PROPERTY);
    mapper.configure(MapperFeature.USE_GETTERS_AS_SETTERS, false);
    mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
0

Try to register ParameterNamesModule in ObjectMapper.

It works for me:

objectMapper.registerModule(new ParameterNamesModule());
Kalkin
  • 1
  • 1