Note. You can run all the snippets on jdoodle. Make sure you include the Jackson dependency in the maven-lib
folder. Press ...
→ Manage libs
, then paste com.fasterxml.jackson.core:jackson-databind:2.15.2
and finally press Add library
. Notice, the Person
class includes a package declaration. It's important, don't forget to add it too
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.core.JsonProcessingException;
import lib.Person;
public class MyClass {
public static void main(String args[]) throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
String JSON = "{\"id\": 1, \"name\": \"Sergey\"}";
Person me = mapper.readValue(JSON, Person.class);
System.out.println(me);
}
}
-
Do I need to include setters?
Yes, you do (assuming we discuss the default scenario as defined in your question). Watch out! The exception you're going to get in case you don't include setters is not going to be very informative
package lib;
public class Person {
private int id;
private String name;
public Person() {
System.out.println("Person's public no-args is called...");
}
@Override
public String toString() {
return String.format("Person[id=%d, name=%s]", id, name);
}
}
Person's public no-args is called...
Exception in thread "main" com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "id" (class lib.Person), not marked as ignorable (0 known properties: ])
at [Source: (String)"{"id": 1, "name": "Sergey"}"; line: 1, column: 9] (through reference chain: lib.Person["id"])
...
-
Does a no-args have to be public?
No, it doesn't. You can make the setters private
too
package lib;
public class Person {
private int id;
private String name;
private Person() {
System.out.println("Person's private no-args is called...");
}
public void setId(int id) {
System.out.println("setId() is called...");
this.id = id;
}
public void setName(String name) {
System.out.println("setName() is called...");
this.name = name;
}
@Override
public String toString() {
return String.format("Person[id=%d, name=%s]", id, name);
}
}
Person's private no-args is called...
setId() is called...
setName() is called...
Person[id=1, name=Sergey]
-
Does a no-args have to be the only constructor available? What if there are an explicit no-args and an all-args, for example? What happens then?
Jackson wouldn't care about your other constructor. You can even make your no-args private
thus nudging Jackson to call your public
canonical – it couldn't care less
package lib;
public class Person {
private int id;
private String name;
private Person() {
System.out.println("Person's private no-args is called...");
}
public Person(int id, String name) {
System.out.println("Person's public canonical is called...");
this.id = id;
this.name = name;
}
public void setId(int id) {
System.out.println("setId() is called...");
this.id = id;
}
public void setName(String name) {
System.out.println("setName() is called...");
this.name = name;
}
@Override
public String toString() {
return String.format("Person[id=%d, name=%s]", id, name);
}
}
Person's private no-args is called...
setId() is called...
setName() is called...
Person[id=1, name=Sergey]
-
Can Jackson figure out what to do if there's no no-args? Will it take an all-args?
No, it won't
package lib;
public class Person {
private int id;
private String name;
public Person(int id, String name) {
System.out.println("Person's public canonical is called...");
this.id = id;
this.name = name;
}
public void setId(int id) {
System.out.println("setId() is called...");
this.id = id;
}
public void setName(String name) {
System.out.println("setName() is called...");
this.name = name;
}
@Override
public String toString() {
return String.format("Person[id=%d, name=%s]", id, name);
}
}
Exception in thread "main" com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `lib.Person` (no Creators, like default constructor, exist): cannot deserialize from Object value (no delegate- or property-based Creator)
at [Source: (String)"{"id": 1, "name": "Sergey"}"; line: 1, column: 2]
I'm going off on a tangent a little bit since the question is explicitly about Jackson's default behavior, but if you annotate your canonical constructor as @JsonCreator
and manually map your constructor arguments with @JsonProperty
, Jackson will finally condescend to use it
package lib;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
public class Person {
private int id;
private String name;
@JsonCreator
public Person(@JsonProperty("id") int id, @JsonProperty("name") String name) {
System.out.println("Person's public canonical is called...");
this.id = id;
this.name = name;
}
public void setId(int id) {
System.out.println("setId() is called...");
this.id = id;
}
public void setName(String name) {
System.out.println("setName() is called...");
this.name = name;
}
@Override
public String toString() {
return String.format("Person[id=%d, name=%s]", id, name);
}
}
Person's public canonical is called...
Person[id=1, name=Sergey]
If you omit @JsonProperty
annotations assuming Jackson will map the properties automatically (after all, the names and types match), you're going to have problems. Again, the error message is not going to help you at all
package lib;
import com.fasterxml.jackson.annotation.JsonCreator;
public class Person {
private int id;
private String name;
@JsonCreator
public Person(int id, String name) {
System.out.println("Person's public canonical is called...");
this.id = id;
this.name = name;
}
public void setId(int id) {
System.out.println("setId() is called...");
this.id = id;
}
public void setName(String name) {
System.out.println("setName() is called...");
this.name = name;
}
@Override
public String toString() {
return String.format("Person[id=%d, name=%s]", id, name);
}
}
Exception in thread "main" com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Invalid type definition for type `lib.Person`: Argument #0 of constructor [constructor for `lib.Person` (2 args), annotations: {interface com.fasterxml.jackson.annotation.JsonCreator=@com.fasterxml.jackson.annotation.JsonCreator(mode=DEFAULT)} has no property name (and is not Injectable): can not use as property-based Creator
at [Source: (String)"{"id": 1, "name": "Sergey"}"; line: 1, column: 1]
As a way to avoid manual mapping with @Json
ou can register the ParameterNamesModule
. It will allow Jackson to perform the mapping automatically if JSON properties' names match those of class fields. Jackson 3.x supports ParameterNamesModule
by default
ObjectMapper mapper = new ObjectMapper()
.registerModule(new ParameterNamesModule());
-
Would Jackson assemble an instance if some properties are not set by a constructor (assuming it would take a "some-args" constructor)? Would Jackson invoke available setters, or would it resort to reflexion?
As stated above, in a default scenario, Jackson is going to be happy with a no-args only. However, if you use the annotations mentioned in the previous item, Jackson will do exactly that: invoke a "some-args" constructor and then call setters to initialize the remaining fields
package lib;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
public class Person {
private int id;
private String name;
@JsonCreator
public Person(@JsonProperty("name") String name) {
System.out.println("Person's public \"some-args\" is called...");
this.name = name;
}
public void setId(int id) {
System.out.println("setId() is called...");
this.id = id;
}
public void setName(String name) {
System.out.println("setName() is called...");
this.name = name;
}
@Override
public String toString() {
return String.format("Person[id=%d, name=%s]", id, name);
}
}
Person's public "some-args" is called...
setId() is called...
Person[id=1, name=Sergey]