【问题标题】:How do I (un)marshall a Date as a time-stamp with jackson我如何(取消)将日期编组为杰克逊的时间戳
【发布时间】:2015-04-11 14:27:17
【问题描述】:

我在将 java.util.Date 对象编组为时间戳时遇到了问题。理想情况下,时间戳应该是 UTC-0 格式,而不是服务器的本地时区。虽然如果需要,我可以很容易地解决这个问题。

注意:我知道这里有几个关于堆栈溢出的类似主题,但我遇到的每个人要么已经过时(就正在使用的 API 而言),要么与将 Date 对象序列化为字符串有关。

这是我的 POM 文件的摘录:

<dependency>
    <groupId>javax.ws.rs</groupId>
    <artifactId>javax.ws.rs-api</artifactId>
    <version>2.0.1</version>
</dependency>
<dependency>
    <groupId>org.glassfish.jersey.media</groupId>
    <artifactId>jersey-media-json-jackson</artifactId>
    <version>2.15</version>
</dependency>

示例模型类:

public class PersonJSON {
    private Date birthDate;
}

预期输出(假设出生日期为 2015 年 2 月 1 日 00:00:00 UTC-0):

{"birthDate":1422748800000}

当前输出:

{"birthDate":"2015-02-01"}

是否可以解决这个问题(最好使用注释)?

解决方案

事实证明,错误是由从 Java.sql.Date 到 Java.util.Date 的隐式转换引起的。转换不会引发异常,并且 sql.Date 在逻辑上扩展了 util.Date 它应该可以工作,但它没有。解决方案是从 Java.sql.Date 中提取时间戳并将其用作 Java.util.Date 构造函数的输入。

如果您确实想更改格式,peeskillet 列出的方法是正确的,前提是您有一个有效的 Java.util.Date 对象。

【问题讨论】:

    标签: json jersey jackson jax-rs java.util.date


    【解决方案1】:

    我也遇到了这个问题。在我的例子中,Hibernate ORM 将java.util.Date 实体属性加载为java.sql.Date 对象,这些对象没有像我预期的那样序列化为时间戳(这是杰克逊在序列化java.util.Date 对象时采用的默认序列化策略)。

    我的解决方法是明确指示 java.sql.Date 对象使用杰克逊的股票 com.fasterxml.jackson.databind.ser.std.DateSerializer 类,就像它用于 java.util.Date 对象一样:

    private Client buildClient() {
      return ClientBuilder.newBuilder()
        .register(getJacksonJsonProvider())
        .build();
    }
    
    private JacksonJsonProvider getJacksonJsonProvider() {
      JacksonJsonProvider jjp = new JacksonJaxbJsonProvider();
      jjp.setMapper(getJsonObjectMapper());
      return jjp;
    }
    
    private ObjectMapper getJsonObjectMapper() {
       ObjectMapper mapper = new ObjectMapper();
       mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
       SimpleModule module = new SimpleModule();
       module.addSerializer(java.sql.Date.class, new DateSerializer()); // <-- My Fix
       mapper.registerModule(module);
       return mapper;
    }
    

    【讨论】:

    • 谢谢。我想知道是否有更标准的方法可以强制杰克逊将子类视为父亲类,但这对我现在有效。
    【解决方案2】:

    您只需要在ObjectMapper 上配置SerializationFeature.WRITE_DATES_AS_TIMESTAMPS。从我测试的结果来看,我不需要配置反序列化,它可以很好地传递时间戳。

    @Provider
    public class ObjectMapperContextResolver implements ContextResolver<ObjectMapper> {
    
        private final ObjectMapper mapper;
    
        public ObjectMapperContextResolver() {
            mapper = new ObjectMapper();
            mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, true);
        }
    
        @Override
        public ObjectMapper getContext(Class<?> type) { return mapper; }
    
    }
    

    至于通过每个字段的注释设置此功能,我不确定如何在不编写自定义序列化程序的情况下使用 Jackson 执行此操作(但我不怀疑是否有其他方法)。您始终可以使用 Jackson 提供程序附带的 JAXB 注释支持。只需写一个XmlAdapter 并用@XmlJavaTypeAdatper(YourDateAdapter.class) 注释该字段。这里是an example


    更新

    完整示例。

    必需的依赖项

    <dependency>
        <groupId>org.glassfish.jersey.test-framework.providers</groupId>
        <artifactId>jersey-test-framework-provider-grizzly2</artifactId>
        <version>2.15</version>
    </dependency>
    <dependency>
        <groupId>org.glassfish.jersey.media</groupId>
        <artifactId>jersey-media-json-jackson</artifactId>
        <version>2.15</version>
    </dependency>
    

    测试(你需要从上面注册上下文解析器)

    import java.util.Date;
    import javax.ws.rs.GET;
    import javax.ws.rs.Path;
    import javax.ws.rs.Produces;
    import javax.ws.rs.core.Application;
    import javax.ws.rs.core.MediaType;
    import javax.ws.rs.core.Response;
    import jersey.stackoverflow.provider.ObjectMapperContextResolver;
    import org.glassfish.jersey.server.ResourceConfig;
    import org.glassfish.jersey.test.JerseyTest;
    import org.junit.Test;
    
    public class ObjectMapperTest extends JerseyTest {
    
        public static class Person {
            public Date birthDate;
        }
    
        @Path("/person") @Produces(MediaType.APPLICATION_JSON)
        public static class PersonResource {
            @GET 
            public Response getPerson() {
                Person person = new Person();
                person.birthDate = new Date();
                return Response.ok(person).build();
            }
        }
    
        @Override
        public Application configure() {
            return new ResourceConfig(PersonResource.class,
                                      ObjectMapperContextResolver.class);
        }
    
        @Test
        public void test() {
            String personJson = target("person").request().get(String.class);
            System.out.println(personJson);
        }
    }
    

    结果:{"birthDate":1423738762437}


    更新 2

    我不认为这个答案是正确的。默认情况下,杰克逊似乎已经序列化为时间戳。如果我们不想要时间戳,那么我们将上述配置设置为false。但这仍然不能回答 OP 的问题。不知道是什么问题。

    【讨论】:

    • 我可以将此自定义提供程序/ContextResolver 存储在包含其余应用程序的包中的任何位置,然后 Jackson 会自动检测并使用它而不是默认的 ContextResolver 实现吗?对不起,如果这是一个愚蠢的问题,但我之前从未实现过任何自定义 JAX-RS 接口。
    • 如果您的所有资源类都被包扫描拾取(意味着您没有显式注册它们),那么是的,应该自动拾取此提供程序(因为 @Provider 注释)。只需将其放在您指定扫描的包的同一级别或子级别的包中即可。如果您没有指定路径,并且您的资源仍然被拾取,那么您可能已经扫描了整个类路径。在这种情况下,它在什么包中并不重要。是的,Jackson 提供了一个 MessageBodyReader,它将调用这个 ContextResolver
    • 嗯。当我按照您的建议实现自定义 ContextResolver 时,我可以清楚地看到每个请求都会调用 getContext 方法和构造函数。然而,JSON 输出保持不变。仍然它似乎是正确的解决方案,所以我想我会将问题标记为已解决。谢谢你:)
    • 当我添加“mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);”行时,ContextResolver 确实按预期删除了 NULL_VALUES;所以它只是在努力处理的 Date 对象。当您对此进行测试时,您是否使用了相同的应用程序服务器?如果您使用了其他东西,您可以发布您的 POM 或列出您的服务器包含的库。我开始怀疑我的项目中包含的某个库中是否存在错误。
    • 您是否包含任何实际的 Jackson 依赖项?我见过人们不小心将 Jackson 1 和 2 混合在一起,他们会使用 1 个注释和 2 个 ObjectMapper,然后想知道为什么注释不起作用。至于 pom,我只有jersey-media-json-jackson:2.15jersey-container-servlet:2.15。这就是让 jersey webapp 运行所需的全部内容
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-02-26
    • 2015-09-06
    • 2014-01-05
    • 1970-01-01
    相关资源
    最近更新 更多