Jackson, deserialize class with private fields and arg-constructor without annotations - java

It is possible to deserialize to a class with private fields and a custom argument constructor without using annotations and without modifying the class, using Jackson?
I know it's possible in Jackson when using this combination: 1) Java 8, 2) compile with "-parameters" option, and 3) the parameters names match JSON. But it's also possible in GSON by default without all these restrictions.
For example:
public class Person {
private final String firstName;
private final String lastName;
private final int age;
public Person(String firstName, String lastName, int age) {
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
}
public static void main(String[] args) throws IOException {
String json = "{firstName: \"Foo\", lastName: \"Bar\", age: 30}";
System.out.println("GSON: " + deserializeGson(json)); // works fine
System.out.println("Jackson: " + deserializeJackson(json)); // error
}
public static Person deserializeJackson(String json) throws IOException {
ObjectMapper mapper = new ObjectMapper();
mapper.enable(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES);
mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
return mapper.readValue(json, Person.class);
}
public static Person deserializeGson(String json) {
Gson gson = new GsonBuilder().create();
return gson.fromJson(json, Person.class);
}
}
Which works fine for GSON, but Jackson throws:
Exception in thread "main" com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `jacksonParametersTest.Person` (no Creators, like default construct, exist): cannot deserialize from Object value (no delegate- or property-based Creator)
at [Source: (String)"{firstName: "Foo", lastName: "Bar", age: 30}"; line: 1, column: 2]
at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:67)
It's possible in GSON, so I would expect that there must be some way in Jackson without modifying the Person class, without Java 8, and without an explicit custom deserializer. Does anybody know a solution?
Update, additional info
Gson seems to skip the argument constructor, so it must be creating a no-argument constructor behind the scenes using reflections.
Also, there exists a Kotlin Jackson module which is able to do this for Kotlin data classes, even without the "-parameters" compiler flag.
So it is strange that such a solution doesn't seem to exist for Java Jackson.
This is the (nice and clean) solution available in Kotlin Jackson (which IMO should also become available in Java Jackson via a custom module):
val mapper = ObjectMapper()
.enable(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES)
.registerModule(KotlinModule())
val person: Person = mapper.readValue(json, Person::class.java)

Solution with mix-in annotations
You could use mix-in annotations. It's a great alternative when modifying the classes is not an option. You can think of it as kind of aspect-oriented way of adding more annotations during runtime, to augment the statically defined ones.
Assuming that your Person class is defined as follows:
public class Person {
private final String firstName;
private final String lastName;
private final int age;
public Person(String firstName, String lastName, int age) {
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
}
// Getters omitted
}
First define a mix-in annotation abstract class:
public abstract class PersonMixIn {
PersonMixIn(#JsonProperty("firstName") String firstName,
#JsonProperty("lastName") String lastName,
#JsonProperty("age") int age) {
}
}
Then configure ObjectMapper to use the defined class as a mix-in for your POJO:
ObjectMapper mapper = new ObjectMapper();
mapper.enable(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES);
mapper.addMixIn(Person.class, PersonMixIn.class);
And deserialize the JSON:
String json = "{firstName: \"Foo\", lastName: \"Bar\", age: 30}";
Person person = mapper.readValue(json, Person.class);

Since there is no default constructor, jackson or gson want create instance by there own. you should tell to the API how to create such instance by providing
custom deserialize.
here an snippet code
public class PersonDeserializer extends StdDeserializer<Person> {
public PersonDeserializer() {
super(Person.class);
}
#Override
public Person deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
try {
final JsonNode node = jp.getCodec().readTree(jp);
final ObjectMapper mapper = new ObjectMapper();
final Person person = (Person) mapper.readValue(node.toString(),
Person.class);
return person;
} catch (final Exception e) {
throw new IOException(e);
}
}
}
Then register simple module as to handle your type
final ObjectMapper mapper = jacksonBuilder().build();
SimpleModule module = new SimpleModule();
module.addDeserializer(Person.class, new PersonDeserializer());

Jackson provides the module jackson-modules-java8 for solve your problem.
You must built your ObjectMapper as following:
ObjectMapper mapper = new ObjectMapper()
.enable(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES)
.registerModule(new ParameterNamesModule(JsonCreator.Mode.PROPERTIES));
You must add -parameters as compiler argument.
Example for maven:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.5.1</version>
<configuration>
<!--somecode-->
<compilerArgument>-parameters</compilerArgument>
</configuration>
</plugin>
For gradle:
compileJava {
options.compilerArgs << "-parameters"
}

Only if you can change your class implementation, below solution works
A simple solution is just to create a default constructor
Person() in Person class
public class Person {
private final String firstName;
private final String lastName;
private final int age;
public Person() {
}
public Person(String firstName, String lastName, int age) {
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
}
...
}

Related

Cannot construct instance of `com.domain.User` (no Creators, like default constructor, exist): cannot deserialize from Object value

I have a controller that accepts ObjectNode as #RequestBody.
That ObjectNode represents json with some user data
{
"given_name":"ana",
"family_name": "fabry",
"email": "fabry#gmail.com",
"password": "mypass",
"gender": "FEMALE"
}
Controller.java
#PostMapping(produces = MediaType.APPLICATION_JSON_VALUE)
public JsonNode createUser(#RequestBody ObjectNode user){
return userService.addUser(user);
}
I want to get user as ObjectNode convert it to Java POJO save it to database and again return it as JsonNode.
UserServiceImpl.java
private final UserRepository userRepository;
private final UserMapper userMapper;
#Override
public JsonNode addUser(#RequestBody ObjectNode user) {
try {
return userMapper.fromJson(user)
.map(r -> {
final User created = userRepository.save(r);
return created;
})
.map(userMapper::toJson)
.orElseThrow(() -> new ResourceNotFoundException("Unable to find user"));
} catch (RuntimeException re) {
throw re;
}
}
To convert ObjectNode to POJO
I did this in my UserMapper class:
public Optional<User> fromJson(ObjectNode jsonUser) {
User user = objectMapper.treeToValue(jsonUser, User.class);
}
Also, to write object to JsonNode I did this:
public JsonNode toJson(User user) {
ObjectNode node = objectMapper.createObjectNode();
node.put("email", user.email);
node.put("password", user.password);
node.put("firstName", user.firstName);
node.put("lastName", user.firstName);
node.put("gender", user.gender.value);
node.put("registrationTime", user.registrationTime.toString());
return node;
}
User.java
#Document(collection = "user")
#Builder
#AllArgsConstructor
public class User {
#Indexed(unique = true)
public final String email;
#JsonProperty("password")
public final String password;
#JsonProperty("firstName")
public final String firstName;
#JsonProperty("lastName")
public final String lastName;
#JsonProperty("gender")
public final Gender gender;
#JsonProperty("registrationTime")
public final Instant registrationTime;
public static User createUser(
String email,
String password,
String firstName,
String lastName,
Gender gender,
Instant registrationTime){
return new User(email, password, firstName, lastName, gender, registrationTime);
}
}
When I run my application, this is the error I am receiving:
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `com.domain.User` (no Creators, like default constructor, exist): cannot deserialize from Object value (no delegate- or property-based Creator)
I have read about the error, and it seems this error occurs because Jackson library doesn't know how to create a model which doesn't have an empty constructor and the model contains a constructor with parameters which I annotated its parameters with #JsonProperty("fieldName"). But even after applying #JsonProperty("fieldName") I am still getting the same error.
I have defined ObjecatMapper as Bean
#Bean
ObjectMapper getObjectMapper(){
return new ObjectMapper();
}
What am I missing here?
I could reproduce the exception. Then I added an all-args constructor with each parameter annotated with the right #JsonProperty.
#JsonCreator
public User(
#JsonProperty("email") String email,
#JsonProperty("password") String password,
#JsonProperty("firstName") String firstName,
#JsonProperty("lastName") String lastName,
#JsonProperty("gender") String gender,
#JsonProperty("registrationTime") Instant registrationTime){
super();
this.email = email;
this.password = password;
this.firstName = firstName;
this.lastName = lastName;
this.gender = gender;
this.registrationTime = registrationTime;
}
Now, it creates the instance, but I get other mapping errors (Unrecognized field "given_name") which you should be able to resolve.
Register Jackson ParameterNamesModule, which will automatically map JSON attributes to the corresponding constructor attributes and therefore will allow you to use immutable classes.
The error happened in scala due to missing the name of keys in the constructor.
I missed specifying the names (example currency) in the snippet below - #JsonProperty("currency")
For converting json to case-class using scala and jackson, we can do it in the following way -
Add the jackson dependency in maven/sbt. If you are using sbt, then add the below entry in build.sbt
libraryDependencies += "com.fasterxml.jackson.core" % "jackson-core" % "2.12.5"
Define the case class -
import com.fasterxml.jackson.annotation.{JsonCreator, JsonProperty}
import java.util.Currency
#JsonCreator
case class TopUp (
#JsonProperty("cardId") cardId : Long,
#JsonProperty("amount") amount : BigDecimal,
#JsonProperty("currency") currency: Currency
)
object TopUp {
}
In the main method -
import com.fasterxml.jackson.databind.ObjectMapper
import models.oyster.mappers.request.TopUp
object App {
def main(args : Array[String]): Unit ={
val tmp =
"""
|{
| "cardId": 718908976540,
| "amount": 30.00,
| "currency": "GBP"
|
|}
|""".stripMargin
val objectMapper = new ObjectMapper()
val inputParsed = objectMapper.readValue(tmp, classOf[TopUp])
println("parsed input :: " + inputParsed)
}
}
create the default constructor for this User class

Jackson ConstructorProperties ignores properties names

I am really confused how jackson (2.9.6 version) ObjectMapper works with #ConstructorProperties annotation.
It seems that mapper ignores property names which are present in a #ConstructorPropertiesannotation value method.
What's even more interesting - mapper works correctly regardless of properties names.
What I am talking about?
Let's consider custom XmlMapper:
private static final ObjectMapper XML_MAPPER = new XmlMapper()
.setAnnotationIntrospector(
AnnotationIntrospector.pair(
new JaxbAnnotationIntrospector(),
new JacksonAnnotationIntrospector()
)
)
.registerModule(new JavaTimeModule())
.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false)
.setPropertyNamingStrategy(PropertyNamingStrategy.KEBAB_CASE);
and simple Data Transfer Object (DTO):
#XmlRootElement(name = "person")
#XmlAccessorType(XmlAccessType.NONE)
static class Person {
#XmlAttribute
final int age;
#XmlAttribute
final String name;
#XmlAttribute
final LocalDate dateOfBirth;
#ConstructorProperties({"age","name","dateOfBirth"})
public Person(int age, String name, LocalDate dateOfBirth) {
this.age = age;
this.name = name;
this.dateOfBirth = dateOfBirth;
}
#Override
public String toString() {
return "Person{" +
"age=" + age +
", name='" + name + '\'' +
", dateOfBirth=" + dateOfBirth +
'}';
}
}
I created test to reproduce the issue:
#Test
#DisplayName("Check xml deseralization for Person class")
void deserialization() throws IOException {
String xml = "<person age=\"26\" name=\"Fred\" date-of-birth=\"1991-11-07\"/>";
Person person = XML_MAPPER.readValue(xml, Person.class);
Assertions.assertEquals("Person{age=26, name='Fred', dateOfBirth=1991-11-07}", person.toString());
}
It's strange for me why test is passed regardless of #ConstructorProperties annotation. The test passed with annotation
#ConstructorProperties({"a","b","c"})
public Person(int age, String name, LocalDate dateOfBirth) {
this.age = age;
this.name = name;
this.dateOfBirth = dateOfBirth;
}
Is it a magic? How jackson processes this annotation? What is an equivalent in jackson annotations to ConstructorProperties?
It's passing because the JaxbAnnotationIntrospector can determine the property names from the #XmlAttribute annotations.
The doc on AnnotationIntrospectorPair says:
Helper class that allows using 2 introspectors such that one
introspector acts as the primary one to use; and second one
as a fallback used if the primary does not provide conclusive
or useful result for a method.
The JacksonAnnotationIntrospector (which understands the #ConstructorProperties annotation) isn't being used at all.
If you remove all the JAXB annotations your test will only pass when the correct names are specified in #ConstructorProperties.
If you want to do it "the jackson way", then remove the JAXB annotations and the JaxbAnnotationIntrospector completely (just drop the call to setAnnotationIntrospector, the mapper will default to using the JacksonAnnotationIntrospector).
Deserialisation will work, but you'll have to add some jackson native annotations if you want to achieve the same serialised form:
#JacksonXmlRootElement(localName = "person")
static class Person {
#JacksonXmlProperty(isAttribute = true)
final int age;
#JacksonXmlProperty(isAttribute = true)
final String name;
#JacksonXmlProperty(isAttribute = true)
final LocalDate dateOfBirth;
#ConstructorProperties({"age", "name", "dateOfBirth"})
public Person(int age, String name, LocalDate dateOfBirth) {
this.age = age;
this.name = name;
this.dateOfBirth = dateOfBirth;
}
//...

Jackson throws NPE during deserialization on missing property

I'm still getting NPE while trying to deserialize an JSON. I wish to put a default value (0 / null) if property is missing. This is my Spring Boot configuration bean:
#Bean
public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() {
MappingJackson2HttpMessageConverter jsonConverter = new MappingJackson2HttpMessageConverter();
jsonConverter.setObjectMapper(objectMapper());
return jsonConverter;
}
ObjectMapper objectMapper() {
SimpleModule simpleModule = new SimpleModule();
simpleModule.addDeserializer(ProductBasicDto.class, new ProductDeserializer());
ObjectMapper mapper = new ObjectMapper()
.configure(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES, false)
.configure(DeserializationFeature.FAIL_ON_MISSING_CREATOR_PROPERTIES, false)
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.configure(DeserializationFeature.FAIL_ON_NULL_CREATOR_PROPERTIES, false)
.configure(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES, false);
mapper.registerModule(simpleModule);
return mapper;
}
My custom deserializer:
#Override
public ProductBasicDtoWrapper deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
JsonNode node = jsonParser.getCodec().readTree(jsonParser);
System.out.println(node.get("id").asLong()); // here it throws NPE
return null; // ignore that, just for testing
}
json:
{
"name": "js",
"category": "CoNiFer"
}
and execption:
java.lang.NullPointerException: null
at api.product.infrastructure.ProductDeserializer.deserialize(ProductDeserializer.java:19)
at api.product.infrastructure.ProductDeserializer.deserialize(ProductDeserializer.java:14)
at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4001)
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3072)
at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.readJavaType(AbstractJackson2HttpMessageConverter.java:235)
at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.read(AbstractJackson2HttpMessageConverter.java:223)
at org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodArgumentResolver.readWithMessageConverters(AbstractMessageConverterMethodArgumentResolver.java:206)
at org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.readWithMessageConverters(RequestResponseBodyMethodProcessor.java:157)
at org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.resolveArgument(RequestResponseBodyMethodProcessor.java:130)
at org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:124)
at org.springframework.web.method.support.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:161)
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:131)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:102)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:870)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:776)
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:991)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:925)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:978)
at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:881)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:661)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:855)
How can I avoid this NPE?
Is it possible to put 0 / null values without explict checks in deserializer if property(like id in this example) is missing?
EDIT: Added some code examples
Let's say this is my DTO class:
class MyDto implements Serializable {
private final String firstName;
private final String lastName;
}
Now I'm creating my custom mapper:
#Override
public ProductBasicDtoWrapper deserialize(JsonParser jsonParser,
DeserializationContext deserializationContext) throws IOException,
JsonProcessingException {
objectMapper.convertValue(jsonNode, MyDto.class);
...
Now I decided to add additional Integer to MyDto:
class MyDto implements Serializable {
private final String firstName;
private final String lastName;
private final Integer age;
}
It's great it doesn't needs changing ANYTHING else (in my mapper class). But assume I got this kind of json
{
"firstName": "name"
}
Now it throws NPE. So the idea is to check if values are null in mapper. Let's do it:
#Override
public ProductBasicDtoWrapper deserialize(JsonParser jsonParser,
DeserializationContext deserializationContext) throws IOException,
JsonProcessingException {
JsonNode node = jsonParser.getCodec().readTree(jsonParser);
node.get("firstName") == null ?
node.get("lastName") == null ?
node.get("age") == null ?
}
Okay it works, now let's say I added one more attribute to my DTO class
class MyDto implements Serializable {
private final String firstName;
private final String lastName;
private final Integer age;
private final String card;
}
The problem is, in this case I have to change ALSO my mapper class because 4th parameter isn't handled.
This line returns null
node.get("id") // returns null when property is not defined
Because the property is obviouly not defined in the JSON. You can resolve your problem in many ways, but you will always have to check null when using get(name) and one of the methods asLong, asString, etc, or just check if the property is defined with has(name).
You can also use a helper function
public static Long getLong(JsonNode node, String name) {
if (node.get(name) != null && node.get(name).isLong()) {
return node.get(name).asLong();
} else {
return null;
}
}
You can return null, or throw an exception, if you return null you should be carefull and handle it later.
And then use it to print the variable or null when not defined.
System.out.println(getLong(node, "id"));
EDIT (Acording to the edited question):
When you configure the object mapper you can specify how strict it should be with the configure method, you could use the DeserializationFeature enum to indicate when it should fail and when it shouldn't.
Here you can see each feature and what is for:
https://fasterxml.github.io/jackson-databind/javadoc/2.9/com/fasterxml/jackson/databind/DeserializationFeature.html
Now if the name of the properties in your class and json matches you can convert a json to a dto object just as follows:
ObjectMapper mapper = new ObjectMapper() // object mapper with wanted properties
.configure(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES, false)
.configure(DeserializationFeature.FAIL_ON_MISSING_CREATOR_PROPERTIES, false)
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.configure(DeserializationFeature.FAIL_ON_NULL_CREATOR_PROPERTIES, false)
.configure(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES,
mapper.readValue(json, MyDto.class);
Here you just need to create the mapper (no deserializer is needed) and then transform it to the destination class.
In case you have different names in you class and json you have to use the annotation #JsonProperty and specify the name.
Possible problems:
Now, for what I can see in your DTO your using final attributes, when you use them you have to create a constructor with arguments, and cannot create an empty contructor (at least without specified it's values beforehand), this empty constructor is necessary for a java POJO and is used internally by the object mapper.
So your DTO should be something like:
class MyDto implements Serializable {
private String firstName;
private String lastName;
private Integer age;
// empty constructor (only necessary when another constructor is specified)
// getters and setters
}
If you still need to use Immutable objects and not POJOs you can create a class like the following:
class MyDto implements Serializable {
private final String firstName;
private final String lastName;
private final Integer age;
#JsonCreator
MyDto(#JsonProperty("firstName") String firstName,
#JsonProperty("lastName") String lastName,
#JsonProperty("age") Integer age) {
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
}
// getters
}
Using the mapper
With the POJO class and object mapper from above you can do the following:
MyDto myDto1 = mapper.readValue("{\"firstName\": \"name\"}", MyDto.class); // object with firstName defined, lastName and age are null
MyDto myDto2 = mapper.readValue("{\"firstName\": \"name\",\"lastName\": \"last\"}", MyDto.class); // object with firstName and lastName defined, age is null
MyDto myDto3 = mapper.readValue("{\"firstName\": \"name\",\"lastName\": \"last\",\"age\": 1}", MyDto.class); // object with firstName, lastName and age defined
And you can even use an empty object or have unknown properties.
MyDto myDto4 = mapper.readValue("{}", MyDto.class); // object with all properties null
MyDto myDto5 = mapper.readValue("{\"blablah\": \"name\"}", MyDto.class); // object with all properties null

JAVA object mapper serialization with mixin and changed getter

I'm mapping object (code to which I have no access) into json using Jackson Object Mapper and MixIn class. So, for example i have this object:
public class Person {
private String name;
private String surname;
public Person (String name, String surname) {
this.name = name;
this.surname = surname;
}
public String getName() {
return name;
}
public String getSurname() {
return surname;
}
}
and MixIn class like this:
abstract class MixIn {
#JsonIgnore
abstract String getSurname(); // we don't need this in json output!
}
This working well. But what if i need to change output of "getName()" getter to return like return person.name + " " + person.surname.
Is this even possible to do by specific Object Mapper configuration? (when i have no access to Person class code)
You could write a custom serializer:
class PersonSerializer extends JsonSerializer<Person> {
#Override
public void serialize(Person person, JsonGenerator generator, SerializerProvider provider) throws IOException {
generator.writeStartObject();
generator.writeStringField("name", person.getName() + " " + person.getSurname());
generator.writeEndObject();
}
}
Then, there are multiple ways to register it, for example using a module:
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addSerializer(Person.class, new PersonSerializer());
mapper.registerModule(module);
System.out.println(mapper.writeValueAsString(new Person("user", "1827257")));
You may want to serialize the object differently in different cases, so using mixins is preferred:
objectMapper.getSerializationConfig().addMixInAnnotations(Person.class, MixIn.class);
String json = objectMapper.writeValueAsString(new Person("user", "1827257"));

Converting camelCase to underscore when deserializing an object using ObjectMapper

I have a fixture file person.json,
{
"fistName": "John"
"lastName": "Smith"
}
I have a class called Person
public class Person {
private String firstName;
private String lastName;
//.. getters and setters
}
I deserialize Person using ObjectMapper like below
ObjectMapper mapper = new ObjectMapper();
Person person = mapper.readValue(new FileInputStream(new File("person.json")),Person.class);
I get this error,
java.lang.RuntimeException: com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException:
Unrecognized field "firstName" (class com.foo.Person), not marked as ignorable (2 known properties: , "first_name", "last_name"])
I get the same error when I use
mapper.setPropertyNamingStrategy(PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES);.
My question is Why do I get this error and how to resolve it?
Because you ask Jackson to use a naming strategy that translates camel case, i.e. firstName to lower case with underscore i.e. first_name.
public class App {
public static void main(String[] args) throws Exception {
ObjectMapper mapper = new ObjectMapper();
Person person = mapper.readValue(new FileInputStream(new File("/path/to/person.json")),Person.class);
System.out.println(person);
}
}
#Data // lombok #Data
public class Person {
private String firstName;
private String lastName;
}
and person.json (fixed):
{
"firstName": "John",
"lastName": "Smith"
}
Output:
Person(firstName=John, lastName=Smith)

Categories

Resources