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?
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);
}
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);
}