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