是否可以为一个特定的执行配置 ObjectMapper?

Is it possible to configure ObjectMapper for one specific execution?

我正在开发一个 Web 项目,它使用一个静态 ObjectMapper,它是通过 XML 文件配置的,并且应该在整个项目中生效。但是,我必须实现一个 API 来发送一个响应,无论设置如何,都不会忽略 null 属性 。我的老板告诉我他不希望创建另一个 ObjectMapper,并且创建我自己的 JSON writer 被认为是多余的,因此也被禁止。这导致我被困在这里。 我试过了。

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

临时切换设置,它有效。但是考虑到映射器是异步使用的,改变全局配置绝对不是一个好主意。我还考虑过在配置切回之前锁定映射器,但由于映射器是静态的,这可能是另一个坏主意。我想要一些简洁的方式,比如注释或参数神奇地影响单次执行。请问可以吗?

您当前拥有的是危险的,因为您正在临时更改全局映射器的配置。这也会影响同时使用同一个映射器实例进行序列化的其他线程。

但是,还有另一种方法可以实现您的需要。 ObjectMapper 实例有几种方法可以根据您的映射器创建 ObjectWriter-实例。

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

正如 @Stepan Stahlmann 在您的评论中所说,您还可以使用 ObjectMapper#copy() 方法基于全局实例创建一个临时的新 ObjectMapper 实例。想法是一样的:使用全局 ObjectMapper 作为配置目的的根并进行一些调整,以便它生成适合 API-Contract.

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

不同的方法...

我能想到另一种方法,而且我很确定还有更多方法。 您可以将 resultMap 包装在 class 中,并带有一些注释,这些注释应否决映射器的默认行为:

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