Post Java 8, LocalDate has become mainstream. Of course, Joda Time, from which LocalDate derives, was quite popular as well. I was trying to convert LocalDate to and from json.
Consider the below simple Person POJO:
public class Person {
private final long id;
private final String firstName;
private final String lastName;
private final LocalDate dateOfBirth;
public Person(long id, String firstName, String lastName, LocalDate dateOfBirth) {
this.id = id;
this.firstName = firstName;
this.lastName = lastName;
this.dateOfBirth = dateOfBirth;
}
/** The default constructor is needed for Json to Java conversion */
public Person() {
this(0, null, null, null);
}
public long getId() {
return id;
}
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
public LocalDate getDateOfBirth() {
return dateOfBirth;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((dateOfBirth == null) ? 0 : dateOfBirth.hashCode());
result = prime * result + ((firstName == null) ? 0 : firstName.hashCode());
result = prime * result + (int) (id ^ (id >>> 32));
result = prime * result + ((lastName == null) ? 0 : lastName.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Person other = (Person) obj;
if (dateOfBirth == null) {
if (other.dateOfBirth != null)
return false;
} else if (!dateOfBirth.equals(other.dateOfBirth))
return false;
if (firstName == null) {
if (other.firstName != null)
return false;
} else if (!firstName.equals(other.firstName))
return false;
if (id != other.id)
return false;
if (lastName == null) {
if (other.lastName != null)
return false;
} else if (!lastName.equals(other.lastName))
return false;
return true;
}
}
The below code converts a Person to Json:
Person person = new Person(100, "Subodh", "Ghosh", LocalDate.of(1909, 9, 14)); ObjectMapper mapper = new ObjectMapper(); String personJson = mapper.writeValueAsString(person);
The generated json is as below:
{"id":100,"firstName":"Subodh","lastName":"Ghosh","dateOfBirth":{"year":1909,"month":"SEPTEMBER","dayOfMonth":14,"dayOfWeek":"TUESDAY","era":"CE","dayOfYear":257,"leapYear":false,"monthValue":9,"chronology":{"id":"ISO","calendarType":"iso8601"}}}
Notice that the dateOfBirth field has lots of attributes. The next logical thing to do would be to take this string and try to un-marshall it to Java Object:
String personJson =
"{"id":100,"firstName":"Subodh","lastName":"Ghosh","dateOfBirth":{"year":1909,"month":"SEPTEMBER","dayOfMonth":14,"dayOfWeek":"TUESDAY","era":"CE","dayOfYear":257,"leapYear":false,"monthValue":9,"chronology":{"id":"ISO","calendarType":"iso8601"}}}";
ObjectMapper mapper = new ObjectMapper();
Person person = mapper.readValue(personJson, Person.class);
However, this results in the below exception:
com.fasterxml.jackson.databind.JsonMappingException: No suitable constructor found for type [simple type, class java.time.LocalDate]: can not instantiate from JSON object (missing default constructor or creator, or perhaps need to add/enable type information?)
at [Source: {"id":100,"firstName":"Subodh","lastName":"Ghosh","dateOfBirth":{"year":1909,"month":"SEPTEMBER","dayOfMonth":14,"dayOfWeek":"TUESDAY","era":"CE","dayOfYear":257,"leapYear":false,"monthValue":9,"chronology":{"id":"ISO","calendarType":"iso8601"}}}; line: 1, column: 66] (through reference chain: com.swayam.demo.json.simple.Person["dateOfBirth"])
at com.fasterxml.jackson.databind.JsonMappingException.from(JsonMappingException.java:148)
at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeFromObjectUsingNonDefault(BeanDeserializerBase.java:1106)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:294)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:131)
at com.fasterxml.jackson.databind.deser.SettableBeanProperty.deserialize(SettableBeanProperty.java:520)
at com.fasterxml.jackson.databind.deser.impl.FieldProperty.deserializeAndSet(FieldProperty.java:101)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:256)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:125)
at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:3702)
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:2714)
Its very easy to solve it. We would need to add the LocalDate type module in pom.xml:
com.fasterxml.jackson.datatype jackson-datatype-jsr310 2.6.1
To enable it:
ObjectMapper mapper = new ObjectMapper(); // register all data type modules // this may have performance implications: // mapper.findAndRegisterModules(); mapper.registerModule(new JavaTimeModule());
Now we can just go our usual way of using the ObectMapper. Note that now the json string now becomes more concise:
{"id":100,"firstName":"Subodh","lastName":"Ghosh","dateOfBirth":[1909,9,14]}
The entire project is here:
https://github.com/paawak/blog/tree/master/code/json-inheritance-demo
The full test case is here:
https://github.com/paawak/blog/blob/master/code/json-inheritance-demo/src/test/java/com/swayam/demo/json/simple/LocalDateJsonTest.java