Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to get path of JSON object in Jackson JsonMappingException

Tags:

java

json

jackson

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?

like image 442
Doug Avatar asked Oct 15 '25 04:10

Doug


1 Answers

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
like image 50
Mihai Bojin Avatar answered Oct 17 '25 21:10

Mihai Bojin



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!