In this short article, we will cover in-depth the JsonMappingException: no String-argument constructor/factory method to deserialize from String value () exception.
First, we will explain the root cause behind this exception. Then, we will showcase how to reproduce and solve it using practical examples.
The Cause
Before diving deep into the exception, let’s understand what its stack trace means.
Typically, “no String-argument constructor/factory method to deserialize from String” tells us that Jackson fails to find a suitable constructor or factory method to deserialize a JSON string into an object.
In short, the exception occurs when something goes wrong while deserializing a JSON string.
The leading cause behind it is trying to perform deserialization using double quotes ”…” instead of bracelets {…}.
Jackson interprets the double quotes as a string. So it expects the target object to have a string argument constructor or a factory method. Hence the error no String-argument constructor/factory method.
Another reason would be accidentally using the convertValue() method instead of readValue().
Reproducing no String-argument constructor/factory method to deserialize from String
Now that we know what the stack trace means, let’s see how to reproduce it in practice.
For instance, let’s consider the Person class:
public class Person {
private String firstName;
private String lastName;
private Email email;
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public Email getEmail() {
return email;
}
public void setEmail(Email email) {
this.email = email;
}
}
Next, we are going to create the Email class:
public class Email {
private String account;
public String getAccount() {
return account;
}
public void setAccount(String account) {
this.account = account;
}
}
Now, let’s add a static method to deserialize a JSON string into a Person object:
public static void deserializeJsonString() {
ObjectMapper mapper = new ObjectMapper();
String personJson = "{" +
"\"firstName\": \"Azhrioun\"," +
"\"lastName\": \"Abderrahim\"," +
"\"email\": \"gaccount@gmail.com\"" +
"}";
try {
Person person = mapper.readValue(personJson, Person.class);
System.out.println("Email Account: " + person.getEmail().getAccount());
} catch (JsonProcessingException e) {
e.printStackTrace();
}
}
As we can see, we used a string value to denote the JSON property email.
Finally, let’s execute the method and take a close look at the stack trace:
com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of ...
no String-argument constructor/factory method to deserialize from String value ('gaccount@gmail.com')
As shown above, Jackson fails with the exception because it could not convert the specified JSON value gaccount@gmail.com, which is a string, into an object of the Email type.
This behavior makes sense. In order to perform deserialization, Jackson needs to call a constructor which takes String email as an argument.
Fixing the Exception
The easiest solution would be defining a string argument constructor in our class Person.
This constructor will be used by Jackson later to handle the JSON deserialization.
Here’s an example:
public Person(@JsonProperty("firstName") String firstName,
@JsonProperty("lastName") String lastName,
@JsonProperty("email") String email) {
this.firstName = firstName;
this.lastName = lastName;
Email emailObj = new Email();
emailObj.setAccount(email);
this.email = emailObj;
}
We used the @JsonProperty annotation to denote the property names in the given JSON.
As we can see, we created a new Email object. Then, we used the passed string argument to set the account field.
Alternatively, we can annotate a custom method that accepts a string parameter with @JsonProperty(“email”).
So, let’s see it in action:
@JsonProperty("email")
public void deserializeEmail(String email) {
Email emailObj = new Email();
emailObj.setAccount(email);
this.email = emailObj;
}
That way, we tell Jackson to call deserializeEmail() when deserializing the JSON property “email”: “gaccount@gmail.com”.
Another approach would be using a static factory method instead of the string argument constructor:
@JsonCreator
public static Person of(@JsonProperty("firstName") String firstName,
@JsonProperty("lastName") String lastName,
@JsonProperty("email") String email) {
Person person = new Person();
person.firstName = firstName;
person.lastName = lastName;
Email emailObj = new Email();
emailObj.setAccount(email);
person.email = emailObj;
return person;
}
Simply put, @JsonCreator marks the factory method defining how to deserialize the JSON. It’s a good practice when the specified JSON doesn’t match the target class.
No comments:
Post a Comment