使用 ObjectMapper + JavaTimeModule 将 JacksonJsonProvider 注册到 Jersey 2 客户端

Posted

技术标签:

【中文标题】使用 ObjectMapper + JavaTimeModule 将 JacksonJsonProvider 注册到 Jersey 2 客户端【英文标题】:Registering JacksonJsonProvider with ObjectMapper + JavaTimeModule to Jersey 2 Client 【发布时间】:2017-07-17 00:38:06 【问题描述】:

我正在尝试编组包含这样的 ISO 格式时间戳的响应:


...
    "time" : "2014-07-02T04:00:00.000000Z"
...

进入我的域模型对象中的ZonedDateTime 字段。如果我使用以下 sn-p 中评论的解决方案,最终它会起作用。关于 SO 有很多类似的问题,但我想得到一个具体的答案 将JacksonJsonProviderObjectMapper + JavaTimeModule 一起使用的另一种方法有什么问题?

        ObjectMapper mapper = new ObjectMapper();
        mapper.registerModule(new JavaTimeModule());
        JacksonJsonProvider provider = new JacksonJsonProvider(mapper);
        Client client = ClientBuilder.newBuilder()
//                .register(new ObjectMapperContextResolver()
//                    @Override
//                    public ObjectMapper getContext(Class<?> type) 
//                        ObjectMapper mapper = new ObjectMapper();
//                        mapper.registerModule(new JavaTimeModule());
//                        return mapper;
//                    
//                )
                .register(provider)
                .register(JacksonFeature.class)
                .build();

我得到的错误:

javax.ws.rs.client.ResponseProcessingException: com.fasterxml.jackson.databind.JsonMappingException: Can not construct instance of java.time.ZonedDateTime: no String-argument constructor/factory method to deserialize from String value ('2017-02-24T20:46:05.000000Z')
 at [Source: org.glassfish.jersey.message.internal.ReaderInterceptorExecutor$UnCloseableInputStream@53941c2f

项目依赖有:

compile 'com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.8.7'
compile 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.8.7'
compile 'com.fasterxml.jackson.core:jackson-core:2.8.7'
compile 'com.fasterxml.jackson.core:jackson-databind:2.8.7'
compile 'com.fasterxml.jackson.core:jackson-annotations:2.8.7'
compile 'org.glassfish.jersey.core:jersey-client:2.25.1'

编辑

反序列化发生在这里:

CandlesResponse<BidAskCandle> candlesResponse = webTarget.request()
                .header(HttpHeaders.AUTHORIZATION,"Bearer "+token)
                .accept(MediaType.APPLICATION_JSON)
                .get(new GenericType<CandlesResponse<BidAskCandle>>());

【问题讨论】:

【参考方案1】:

如果我使用以下 sn-p 中注释的解决方案,最终它会起作用。

首先,您的列表中缺少一个依赖项,您也有,这就是问题所在。

jersey-media-json-jackson

此模块依赖于具有 JacksonJsonProvider 的本机 Jackson 模块。当您注册JacksonFeaturejersey-media-json-jackson 附带)时,它会注册自己的JacksonJaxbJsonProvider,这似乎优先于您提供的任何内容。

当您使用ContextResolver 时,JacksonJsonProvider 实际上会查找ContextResolver 并使用它来解析ObjectMapper。这就是它起作用的原因。无论您是使用JacksonFeature 还是注册自己的JacksonJsonProvider(无需为其配置ObjectMapperContextResovler 都可以使用。

关于jersey-media-json-jackson 模块的另一件事是,它参与了Jersey 的auto-discoverable 机制,它注册了JacksonFeature。因此,即使您没有明确注册它,它仍然会被注册。避免它被注册的唯一方法是:

    禁用自动发现(如previous link 中所述)1

    不要使用jersey-media-json-jackson。只需使用 Jackson 原生模块 jackson-jaxrs-json-provider。但问题是,jersey-media-json-jackson 在本机模块之上添加了一些功能,所以你会失去这些。

    尚未测试,但似乎如果您使用JacksonJaxbJsonProvider 而不是JacksonJsonProvider,它可能会起作用。如果您查看source for the JacksonFeature,您会看到它会检查已注册的JacksonJaxbJsonProvider。如果有,它不会注册它自己的。

    我不确定的一件事是自动发现。它注册的顺序,如果它会影响它是否捕获您注册的JacksonJaxbJsonProvider。你可以测试一下。


脚注

1。 See also

【讨论】:

我选择了带有JacksonJaxbJsonProvider 的选项,因为它看起来更直接并且不涉及额外的依赖项。它有效。 上一个链接现在变成了挂起的参考 @inxoy 我刚刚修好了。感谢您的提醒。 “禁用自动发现”链接现在已经失效,因为 Jersey 更改了他们的页面,建议您链接到这个问题:***.com/questions/30278303/… 刚刚修复了@MrChow【参考方案2】:

来自我的宠物项目:

<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-jsr310</artifactId>
    <version>$jackson.version</version>
</dependency>

public WebTarget getTarget(URI uri) 
    Client client = ClientBuilder
            .newClient()
            .register(JacksonConfig.class);
    return client.target(uri);

在哪里

@Provider
public class JacksonConfig implements ContextResolver<ObjectMapper> 

    private final ObjectMapper objectMapper;

    public JacksonConfig() 
        objectMapper = new ObjectMapper();
        objectMapper.registerModule(new JavaTimeModule());
        objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
    

    @Override
    public ObjectMapper getContext(Class<?> aClass) 
        return objectMapper;
    

【讨论】:

【参考方案3】:

Jackson 配置看起来不错,我尝试了以下方法并能够反序列化该值:

public class Test 

    public static void main(String[] args) throws Exception 
        ObjectMapper mapper = new ObjectMapper();
        mapper.registerModule(new JavaTimeModule());
        Model model = mapper.readValue("\"time\" : \"2014-07-02T04:00:00.000000Z\"", Model.class);
        System.out.println(model.getTime());
    



class Model
    private ZonedDateTime time;

    public ZonedDateTime getTime() 
        return time;
    
    public void setTime(ZonedDateTime time) 
        this.time = time;
    

我可以通过注释掉mapper.registerModule(new JavaTimeModule()); 来重现它。因此,看起来泽西客户端没有使用自定义 mapper 实例。您可以尝试按照here 的说明进行配置。

【讨论】:

试过了,没有变化:( 你是这个意思吗? ObjectMapper mapper = new ObjectMapper(); mapper.registerModule(new JavaTimeModule()); JacksonJsonProvider provider = new JacksonJsonProvider(); provider.setMapper(mapper); ClientConfig config = new ClientConfig(provider); Client client = ClientBuilder.newClient(config); 但是这也不起作用。 你能发布执行反序列化的代码吗?

以上是关于使用 ObjectMapper + JavaTimeModule 将 JacksonJsonProvider 注册到 Jersey 2 客户端的主要内容,如果未能解决你的问题,请参考以下文章

RepositoryRestMvcConfiguration 的 ObjectMapper 与 Spring Boot 默认的 ObjectMapper?

Swift - 嵌套对象的映射 (Objectmapper)

理解 Realm、Moya 和 ObjectMapper

ObjectMapper的使用

ObjectMapper - 嵌套动态键

ObjectMapper - 线程安全和性能的最佳实践