Search code examples
javajsonobjectmapperfasterxml

Is it possible to configure ObjectMapper for one specific execution?


I am working on a web project, that uses one static ObjectMapper, which is configured through an XML file and is supposed to take effect across the whole project. However, I have to implement an API to send a response with the null property not being ignored no matter the setting. My boss told me that he doesn't want another ObjectMapper being created, and creating my own JSON writer is considered redundant so it is forbidden as well. Which caused me stuck here. I tried this.

        Map<String, Object>resultMap = getResult();
        try {
            mapper.setSerializationInclusion(Include.ALWAYS);
            response = mapper.writeValueAsString(resultMap);
        } catch (JsonProcessingException e) {
            throw new RuntimeException(e);
        } finally {
            if (ServiceConfig.isWriteNull()) {
                mapper.setSerializationInclusion(Include.ALWAYS);
            } else {
                mapper.setSerializationInclusion(Include.NON_NULL);
            }
        }

To temporally switch the settings, it works. but considering the mapper being used asynchronously, it is definitely a bad idea to change the global configuration. I also thought about putting a lock on the mapper until the configuration switchback, but since the mapper is static, it may be another bad idea. I would like to have some neat way like annotation or a parameter magically affect an single execution. I wonder if it is possible?


Solution

  • What you currently have is dangerous, since you're temporarly changing the configuration of the global mapper. This will also affect other Threads doing serialization using the same mapper instance at the same time.

    However, there's another way to achieve what you need. The ObjectMapper instance has several methods to create a ObjectWriter-instance based on your mapper.

    Map<String, Object> resultMap = getResult();
    try {
        response = mapper
            .writer(SerializationFeature.WRITE_NULL_MAP_VALUES)
            .writeValueAsString(resultMap);
    } catch (JsonProcessingException e) {
        throw new RuntimeException(e);
    }
    

    As @Stepan Stahlmann said in your comments, you could also create a temporary new ObjectMapper instance based on the global instance using the ObjectMapper#copy() method. The idea is the same: Use the global ObjectMapper as the root for configuration purposes and make some tweaks so it generates JSON that fits the API-Contract.

    Map<String, Object> resultMap = getResult();
    try {
        response = mapper
            .copy()
            .setSerializationInclusion(Include.ALWAYS)
            .writeValueAsString(resultMap);
    } catch (JsonProcessingException e) {
        throw new RuntimeException(e);
    }
    

    A different approach ...

    There's another way I can think of and I'm pretty sure there are even more. You could wrap your resultMap in a class with some annotations present that should overrule the default behaviour of the mapper:

    package example;
    
    import com.fasterxml.jackson.annotation.JsonInclude;
    import com.fasterxml.jackson.annotation.JsonValue;
    
    import java.util.Map;
    
    // Your inclusion rule
    @JsonInclude(JsonInclude.Include.ALWAYS)
    public class ResponseWrapper {
    
        private final Map<String, Object> response;
    
        public ResponseWrapper(Map<String, Object> response) {
            this.response = response;
        }
    
        // tells Jackson to use the this a the actual value (so you don't see this wrapper in the json)
        @JsonValue
        public Map<String, Object> getResponse() {
            return this.response;
        }
    }
    
    
    Map<String, Object> resultMap = getResult();
    try {
        response = mapper.writeValueAsString(new ResponseWrapper(resultMap));
    } catch (JsonProcessingException e) {
        throw new RuntimeException(e);
    }