question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

`MapDeserializer` forcing `JsonMappingException` wrapping even if WRAP_EXCEPTIONS set to false

See original GitHub issue

Describe the bug I am upgrading a project from 1.9.13 to 2.12.1. The WRAP_EXCEPTION feature was added in since that as we have a custom exception now being wrapped.

The project adds a Deserializer and overrides deserialize where it throws a custom exception. We do not want this exception to be wrapped but it now gets wrapped as a JsonMappingException. Even setting WRAP_EXCEPTION to false it still wraps the exception.

The code calls wrapAndThrow and then wraps the exception ContainerDeserializerBase.

Then when handled as its already been wrapped by a JsonMappingException it is a instance of IOException so it will not unwrap the exception in the code BeanDeserializerBase.

Should we be able to get the underlying exception here or is this by design to force this wrapping?

Version information 2.12.1

To Reproduce

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.Version;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

import java.io.IOException;
import java.util.Collections;
import java.util.Map;

public class JacksonIssue {

    private static final ObjectMapper OBJECT_MAPPER = getObjectMapper();

    private static ObjectMapper getObjectMapper() {
        ObjectMapper mapper = new ObjectMapper();
        mapper.configure(DeserializationFeature.WRAP_EXCEPTIONS, false);
        mapper.configure(SerializationFeature.WRAP_EXCEPTIONS, false);

        SimpleModule module = new SimpleModule("SimpleModule", new Version(1, 0, 0, ""));

        module.addDeserializer(MyContainerModel.class, new JsonDeserializer<MyContainerModel>() {
            @Override
            public MyContainerModel deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
                throw new CustomException("This gets wrapped");
            }
        });

        mapper.addMixIn(MyJobModel.class, MyJsonJobModelMixIn.class);
        mapper.addMixIn(MyContainerModel.class, MyJsonContainerModelMixIn.class);

        mapper.registerModule(module);

        return mapper;
    }


    @Test
    void wrapExceptionIgnored() throws JsonProcessingException {
        ObjectNode jobModelJson = buildJobModelJson();
        ObjectNode containerModelJson = (ObjectNode) jobModelJson.get("containers").get("1");
        containerModelJson.remove("processor-id");

        String jsonString = new ObjectMapper().writeValueAsString(jobModelJson);

        Assertions.assertThrows(CustomException.class, () -> {
            OBJECT_MAPPER.readValue(jsonString, MyJobModel.class);
        });
    }


    private static ObjectNode buildJobModelJson() {
        ObjectMapper objectMapper = new ObjectMapper();

        ObjectNode model = objectMapper.createObjectNode();
        model.put("system", "foo");
        model.put("stream", "bar");
        model.put("partition", 1);

        ArrayNode containerModel1TaskTestSSPsJson = objectMapper.createArrayNode();
        containerModel1TaskTestSSPsJson.add(model);

        ObjectNode model2 = objectMapper.createObjectNode();
        model2.put("task-name", "test");
        model2.put("changelog-partition", 2);
        model2.put("task-mode", "Active");

        ObjectNode containerModel1TasksJson = objectMapper.createObjectNode();
        containerModel1TasksJson.put("test", model);

        ObjectNode containerModel1Json = objectMapper.createObjectNode();
        containerModel1Json.put("processor-id", "1");
        containerModel1Json.put("tasks", containerModel1TasksJson);

        ObjectNode containersJson = objectMapper.createObjectNode();
        containersJson.put("1", containerModel1Json);

        ObjectNode jobModelJson = objectMapper.createObjectNode();
        jobModelJson.put("containers", containersJson);

        return jobModelJson;
    }


    public abstract class MyJsonContainerModelMixIn {
        static final String PROCESSOR_ID_KEY = "processor-id";
        static final String CONTAINER_ID_KEY = "container-id";
        static final String TASKS_KEY = "tasks";

        public MyJsonContainerModelMixIn() {
        }

        @JsonProperty("processor-id")
        abstract String getId();

    }

    @JsonIgnoreProperties(
            ignoreUnknown = true
    )
    public abstract class MyJsonJobModelMixIn {
        @JsonCreator
        public MyJsonJobModelMixIn(@JsonProperty("containers") Map<String, MyContainerModel> containers) {
        }

        @JsonProperty("containers")
        abstract Map<String, MyContainerModel> getContainers();
    }


    public static class MyContainerModel {
        private final String id;

        public MyContainerModel(String id) {
            this.id = id;
        }

        public String getId() {
            return this.id;
        }


    }

    public static class MyJobModel {
        private final Map<String, MyContainerModel> containers;
        public int maxChangeLogStreamPartitions;

        @JsonCreator
        public MyJobModel(Map<String, MyContainerModel> containers) {
            this.containers = Collections.unmodifiableMap(containers);
            this.maxChangeLogStreamPartitions = 0;

        }


        public Map<String, MyContainerModel> getContainers() {
            return this.containers;
        }


    }


}
public class CustomException extends RuntimeException {
    private static final long serialVersionUID = 1L;

    public CustomException() {
        super();
    }

    public CustomException(String s, Throwable t) {
        super(s, t);
    }

    public CustomException(String s) {
        super(s);
    }

    public CustomException(Throwable t) {
        super(t);
    }
}

Expected behavior <jacksonissue.CustomException> but was: <com.fasterxml.jackson.databind.JsonMappingException>

Additional context This is due to trying to upgrade Apache Samza from a very old Jackson version where they throw an exception like the case above referenced here

Issue Analytics

  • State:closed
  • Created 3 years ago
  • Comments:5 (3 by maintainers)

github_iconTop GitHub Comments

1reaction
cowtowncodercommented, Feb 26, 2021

Was straight-forward to fix and I think likelihood of regression is low (affects regular Map and EnumMap deserializers), so including it in 2.12 branch for upcoming 2.12.2 patch.

1reaction
perksscommented, Feb 25, 2021

I have tried to create a toy example which hits WrapsAndThrows in ContainerDeserializerBase.

Read more comments on GitHub >

github_iconTop Results From Across the Web

How to make Jackson throw exception as is when ...
Inside of Jackson's StdValueInstantiator this method gets hit when an exception is thrown during deserialization:
Read more >
Jackson Exceptions - Problems and Solutions - Baeldung
When we try to deserialize a JSON String to Zoo instance, it throws the JsonMappingException: Can Not Construct Instance Of:
Read more >
DeserializationFeature (jackson-databind 2.6.0 API) - FasterXML
If not, a JsonMappingException is thrown; otherwise value of the wrapped property will be deserialized as if it was the root value. Feature...
Read more >
Security update for jackson-databind, jackson-dataformats ...
... + 'MapDeserializer' forcing 'JsonMappingException' wrapping even if WRAP_EXCEPTIONS set to false + Auto-detection of constructor-based ...
Read more >
Jsonprovider Unusable - ADocLib
Jackson Release 2.12.2 FasterXML/jackson Wiki GitHub MapDeserializer forcing JsonMappingException wrapping even if WRAPEXCEPTIONS set to false.
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found