Search code examples
javajsonspringjacksonobjectmapper

Can I ignore MismatchedInputException within ObjectMapper?


I'm using the Jackson ObjectMapper class like so:

objectMapper.treeToValue(jsonNode, MyClass.class)

where jsonNode is an instance of JsonNode.

When I call treeToValue(), I get a MismatchedInputException with the message

Cannot deserialize instance of com.example.MyField` out of START_OBJECT token

because MyField is defined as a String inside of MyClass, but it is a JSON object inside of the jsonNode variable. I am perfectly OK with jsonNode having a non-matching type for one of its fields, but I'd rather ObjectMapper just not try and serialize this field and ignore it instead of throwing the MismatchedInputException.

I've tried using

objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

but this just ignores missing fields, it does not do anything to prevent the MismatchedInputException for an existing field.


Solution

  • You can recursively remove offending fields and try again. Here is what I came up with (can be simplified based on your logging and exception handling needs).

    public class YourClass {
    
        private static final Logger LOGGER = Logger
                .getLogger(YourClass.class.getName());
    
        public final static ObjectMapper mapper = new ObjectMapper().configure(
                DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
    
        public static <T> T toTypedObject(Class<T> type, JsonNode tree) {
            return toTypedObject(type, tree, true);
        }
    
        private static <T> T toTypedObject(Class<T> type, JsonNode tree,
                boolean topLevel) {
    
            T object;
    
            try {
                object = mapper.treeToValue(tree, type);
            } catch (MismatchedInputException e) {
                String originalTree = tree.toString();
                object = toTypedObject(type, tree, originalTree, e);
                if (topLevel) {
                    LOGGER.log(Level.WARNING, "Failed to convert node tree to a "
                            + type.getSimpleName()
                            + " object without modifications: " + originalTree, e);
                    LOGGER.log(Level.INFO,
                            "Modified node tree was successfully converted to a "
                                    + type.getSimpleName() + " object: " + tree);
                }
            } catch (JsonProcessingException e) {
                throw new YourException("Failed to convert node tree to a "
                        + type.getSimpleName() + " object: " + tree, e);
            }
    
            return object;
        }
    
        private static <T> T toTypedObject(Class<T> type, JsonNode tree,
                String originalTree,
                MismatchedInputException mismatchedInputException) {
    
            T object;
    
            List<Reference> path = mismatchedInputException.getPath();
            if (path != null && !path.isEmpty()) {
    
                try {
    
                    ObjectNode subNode = (ObjectNode) tree;
                    for (int i = 0; i < path.size(); i++) {
                        String fieldName = path.get(i).getFieldName();
                        if (i + 1 < path.size()) {
                            subNode = (ObjectNode) tree.get(fieldName);
                        } else {
                            subNode.remove(fieldName);
                        }
                    }
                    object = toTypedObject(type, tree, false);
    
                } catch (Exception e) {
                    throw new YourException("Failed to convert node tree to a "
                            + type.getSimpleName() + " object: " + originalTree,
                            mismatchedInputException);
                }
    
            } else {
                throw new YourException(
                        "Failed to convert node tree to a " + type.getSimpleName()
                                + " object: " + originalTree,
                        mismatchedInputException);
            }
    
            return object;
        }
    
    }
    

    Call with:

    YourObject yourObject = YourClass.toTypedObject(YourObject.class, tree);