Reputation: 15513
I am using Jackson to deserialize JSON of this form:
{
"foo" : { "bar" : "baz" }
}
The jackson code might look like:
@JsonCreator
public class MyProxy {
@JsonProperty("foo") final FooProxy foo;
}
public class FooProxy {
@JsonProperty("bar") final String bar;
}
Imagine a consumer of this API creates invalid JSON like this:
{
"foo" : { "bar" : 1 }
}
However, in this case, I receive a MismatchedInputException
and the error looks like this:
Cannot construct instance of
MyProxy
(although at least one Creator exists): no int/Int-argument constructor/factory method to deserialize from Number value (1)
When I inspect the MismatchedInputException, and I call ex.getPathReference()
I get:
FooProxy["bar"]->java.lang.Object[0]
I would like to be able to return the path to the broken value to the user without any reference to the underlying Java classes.
"foo.bar must be an Object."
How can I return an error message with the JSON path, and remove any reference to the Java implementation?
Upvotes: 4
Views: 2664
Reputation: 106
Something like this should do it:
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.exc.MismatchedInputException;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.stream.Collectors;
import org.junit.jupiter.api.Test;
public class DeserializationErrorTest {
@Test
void testDeserializeWrongInput() throws IOException {
final ObjectMapper mapper = new ObjectMapper();
try {
mapper.readValue("{\"foo\" : { \"bar\" : \"not-int\" }}", MyProxy.class);
} catch (MismatchedInputException e) {
throw remapMismatchedInputException(e, RuntimeException.class);
}
}
private <T extends Exception> T remapMismatchedInputException(final MismatchedInputException e, Class<T> exClass) {
try {
final String fieldName =
e.getPath().stream().map(JsonMappingException.Reference::getFieldName).collect(Collectors.joining("."));
return exClass.getConstructor(String.class).newInstance(fieldName + " must be of type " + e.getTargetType().getSimpleName());
} catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException pE) {
throw new IllegalArgumentException("Cannot instantiate exception class " + exClass.getSimpleName());
}
}
static class MyProxy {
@JsonProperty("foo") final FooProxy foo;
@JsonCreator
public MyProxy(@JsonProperty("foo") final FooProxy pFoo) {foo = pFoo;}
}
static class FooProxy {
@JsonProperty("bar") final Integer bar;
@JsonCreator
public FooProxy(@JsonProperty("bar") final Integer pBar) {bar = pBar;}
}
}
And will result in:
java.lang.RuntimeException: foo.bar must be of type Integer
Upvotes: 5