While serialization using Jackson JsonCreator.Mode.PROPERTIES, in the serialized string, by default, Jackson assumes class attribute names as JSON object keys.
BTW, Jackson is high-performance JSON processor library in Java.
And, In Jackson, @JsonCreator.Mode.PROPERTIES is a constant used with the @JsonCreator annotation on constructors to specify how arguments for deserialization should be bound from incoming JSON data.
Let’s assume an Employee class like below:
public class Employee {
@JsonProperty
private String name;
@JsonProperty
private int age;
@JsonProperty
private LocalDate dateOfJoining;
@JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
public Employee(@JsonProperty String name, @JsonProperty int age, @JsonProperty LocalDate dateOfJoining) {
this.name = name;
this.age = age;
this.dateOfJoining = dateOfJoining;
}
@Override
public String toString() {
return "Employee{" +
"name='" + name + '\'' +
", age=" + age +
", dateOfJoining=" + dateOfJoining +
'}';
}
}
And a runner class like this, in which an Employee object is serialized and printed to the console.
public class JSONSerializationDemo {
public static void main(String[] args) throws JsonProcessingException, ParseException {
ObjectMapper objectMapper = new ObjectMapper()
.registerModule(new JavaTimeModule())
.enable(SerializationFeature.INDENT_OUTPUT);
Employee emp = new Employee("David", 24, LocalDate.now());
String json = objectMapper.writeValueAsString(emp);
System.out.println(emp);
}
}
Output:
{
"age" : 24,
"dateOfJoining" : [ 2023, 12, 20 ],
"name" : "David"
}
Here it automatically figured out from the Employee class attributes that age, dateOfJoining and name are the keys to be used in the serialized json.
But while deserilizing the same object, Jackson throws InvalidDefinitionException.
public class JSONSerializationDemo {
public static void main(String[] args) throws JsonProcessingException, ParseException {
ObjectMapper objectMapper = new ObjectMapper()
.registerModule(new JavaTimeModule())
.enable(SerializationFeature.INDENT_OUTPUT);
Employee emp = new Employee("David", 24, LocalDate.now());
String json = objectMapper.writeValueAsString(emp);
Employee emp2 = objectMapper.readValue(json, Employee.class);
System.out.println(emp2);
}
}
Exception in thread "main" com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Invalid type definition for type `org.example.entity.Employee`: Argument #0 of constructor [constructor for `org.example.entity.Employee` (3 args), annotations: {interface com.fasterxml.jackson.annotation.JsonCreator=@com.fasterxml.jackson.annotation.JsonCreator(mode=PROPERTIES)} has no property name (and is not Injectable): can not use as property-based Creator
at [Source: (String)"{
"age" : 24,
"dateOfJoining" : [ 2023, 12, 20 ],
"name" : "David"
}"; line: 1, column: 1]
at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:62)
at com.fasterxml.jackson.databind.DeserializationContext.reportBadTypeDefinition(DeserializationContext.java:1882)
at com.fasterxml.jackson.databind.deser.BasicDeserializerFactory._validateNamedPropertyParameter(BasicDeserializerFactory.java:1144)
at com.fasterxml.jackson.databind.deser.BasicDeserializerFactory._addExplicitPropertyCreator(BasicDeserializerFactory.java:884)
at com.fasterxml.jackson.databind.deser.BasicDeserializerFactory._addExplicitConstructorCreators(BasicDeserializerFactory.java:470)
at com.fasterxml.jackson.databind.deser.BasicDeserializerFactory._constructDefaultValueInstantiator(BasicDeserializerFactory.java:304)
at com.fasterxml.jackson.databind.deser.BasicDeserializerFactory.findValueInstantiator(BasicDeserializerFactory.java:223)
at com.fasterxml.jackson.databind.deser.BeanDeserializerFactory.buildBeanDeserializer(BeanDeserializerFactory.java:261)
at com.fasterxml.jackson.databind.deser.BeanDeserializerFactory.createBeanDeserializer(BeanDeserializerFactory.java:150)
at com.fasterxml.jackson.databind.deser.DeserializerCache._createDeserializer2(DeserializerCache.java:415)
at com.fasterxml.jackson.databind.deser.DeserializerCache._createDeserializer(DeserializerCache.java:350)
at com.fasterxml.jackson.databind.deser.DeserializerCache._createAndCache2(DeserializerCache.java:264)
at com.fasterxml.jackson.databind.deser.DeserializerCache._createAndCacheValueDeserializer(DeserializerCache.java:244)
at com.fasterxml.jackson.databind.deser.DeserializerCache.findValueDeserializer(DeserializerCache.java:142)
at com.fasterxml.jackson.databind.DeserializationContext.findRootValueDeserializer(DeserializationContext.java:642)
at com.fasterxml.jackson.databind.ObjectMapper._findRootDeserializer(ObjectMapper.java:4805)
at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4675)
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3629)
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3597)
at org.example.serialization.text.JSONSerializationDemo.main(JSONSerializationDemo.java:20)
This error goes away on providing property names in @JsonProperty annotation.
public class Employee {
@JsonProperty
private String name;
@JsonProperty
private int age;
@JsonProperty
private LocalDate dateOfJoining;
@JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
public Employee(@JsonProperty("name") String name, @JsonProperty("age") int age, @JsonProperty("dateOfJoining") LocalDate dateOfJoining) {
this.name = name;
this.age = age;
this.dateOfJoining = dateOfJoining;
}
@Override
public String toString() {
return "Employee{" +
"name='" + name + '\'' +
", age=" + age +
", dateOfJoining=" + dateOfJoining +
'}';
}
}
Output:
Employee{name='David', age=24, dateOfJoining=2023-12-20}
Why does Jackson @JsonProperty behave differently while Serialization and Deserialization? Is there any specific design consideration behind this behavior?
Please share your thoughts.