Doug
Doug

Reputation: 15513

How to get path of JSON object in Jackson JsonMappingException

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

Answers (1)

Mihai Bojin
Mihai Bojin

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

Related Questions