[jackson] Handle LocalDate for json

Posted by {"name"=>"Palash Ray", "email"=>"paawak@gmail.com", "url"=>"https://www.linkedin.com/in/palash-ray/"} on January 23, 2016 · 4 mins read

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)

 

Solution

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]}

Sources

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