Case insensitive JSON to POJO mapping without changing the POJO
JavaJsonData BindingCase InsensitiveFasterxmlJava Problem Overview
Does anyone know how com.fasterxml.jackson.databind.ObjectMapper is able to map JSON properties to POJO properties case insensitive?
JSON-String: >[{"FIRSTNAME":"John","LASTNAME":"Doe","DATEOFBIRTH":"1980-07-16T18:25:00.000Z"}]
POJO-Class:
public class Person {
private String firstName;
private String lastName;
private Date dateOfBirth;
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public Date getDateOfBirth() {
return dateOfBirth;
}
public void setDateOfBirth(Date dateOfBirth) {
this.dateOfBirth = dateOfBirth;
}
}
Test-Class:
@Test
public final void testDeserializingPersonJsonToPersonClass()
throws JsonParseException, JsonMappingException, IOException {
final String jsonAsString = "[{\"FIRSTNAME\":\"John\",\"LASTNAME\":\"Doe\",\"DATEOFBIRTH\":\"1980-07-16T18:25:00.000Z\"}]";
final ObjectMapper mapper = new ObjectMapper();
final Person person = mapper.readValue(jsonAsString, Person.class);
assertNotNull(person);
assertThat(person.getFirstName(), equalTo("John"));
}
This ends up in following error:
com.fasterxml.jackson.databind.JsonMappingException: Can not deserialize instance of ...
It's not possible to change neither JSON-String nor POJO-Class.
Java Solutions
Solution 1 - Java
This behaviour was introduced in Jackson 2.5.0. You can configure the mapper to be case insensitive using MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES.
For example :
ObjectMapper mapper = new ObjectMapper();
mapper.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true);
Solution 2 - Java
You can solve this problem by configuring the mapper, as described by the @Nicolas Riousset.
In addition, since version Jackson 2.9 you can do the same using annotation @JsonFormat(with = JsonFormat.Feature.ACCEPT_CASE_INSENSITIVE_PROPERTIES)
over a field or class, which is a more flexible option.
@JsonFormat(with = JsonFormat.Feature.ACCEPT_CASE_INSENSITIVE_PROPERTIES)
private String firstName;
Solution 3 - Java
I had the same problem and couldn't find a global way of solving this. However you can have 2 setters per property to achieve this:
@JsonSetter("FIRSTNAME")
public void setFirstNameCaps(String firstName) {
this.firstName = firstName;
}
@JsonSetter("firstName")
public void setFirstName(String firstName) {
this.firstName = firstName;
}
Not elegant but will work for both upper and lower case json fields. You can also try the solution mentioned here but this might have a performance overhead
Solution 4 - Java
package br.com.marcusvoltolim.util;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.log4j.Log4j;
@Log4j
public class JsonUtils {
private static final ObjectMapper OBJECT_MAPPER;
static {
OBJECT_MAPPER = new ObjectMapper();
OBJECT_MAPPER.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true);
OBJECT_MAPPER.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
}
public static <T> T fromJson(final String json, final Class<T> classe) {
try {
return OBJECT_MAPPER.readValue(json, classe);
} catch (Exception e) {
log.error(e);
try {
return classe.newInstance();
} catch (InstantiationException | IllegalAccessException ex) {
return null;
}
}
}
}
Solution 5 - Java
As of Jackson version 2.12, you can finally annotate on class:
@JsonFormat(with = JsonFormat.Feature.ACCEPT_CASE_INSENSITIVE_PROPERTIES)
public class Person {
private String firstName;
private String lastName;
private Date dateOfBirth;
// setters and getters ...
}
As noted in the Github issue, there is still no support for single-property case-insensitivity!
Solution 6 - Java
I was in the same kind of situation and had to convert to a map and then copy the values over manually.
import com.fasterxml.jackson.core.type.TypeReference;
Map<String, String> map =
mapper.readValue(jsonAsString, new TypeReference<Map<String, String>>(){});