【问题标题】:Date format Mapping to JSON Jackson日期格式映射到 JSON Jackson
【发布时间】:2012-09-09 21:25:58
【问题描述】:

我有一个来自 API 的日期格式,如下所示:

"start_time": "2015-10-1 3:00 PM GMT+1:00"

这是 YYYY-DD-MM HH:MM am/pm GMT 时间戳。 我将此值映射到 POJO 中的日期变量。显然,它显示转换错误。

我想知道两件事:

  1. 我需要使用什么格式与 Jackson 进行转换? Date 是一个很好的字段类型吗?
  2. 一般来说,有没有办法在变量被 Jackson 映射到 Object 成员之前对其进行处理?诸如更改格式、计算等。

【问题讨论】:

标签: java json date jackson pojo


【解决方案1】:

自Jackson v2.0起,可以直接在Object成员上使用@JsonFormat注解;

@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm a z")
private Date date;

【讨论】:

  • 如果你想包含时区:@JsonFormat(shape=JsonFormat.Shape.STRING, pattern="yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", timezone="GMT")
  • @Ramki : jackson-annotations >= 2.0
  • 此注解仅在序列化阶段完美运行,但在反序列化过程中根本不使用时区和语言环境信息。我已经尝试过使用 locale="hu" 的 timezone="CET" 和 timezone "Europe/Budapest" 但两者都不起作用并且在日历上引起奇怪的时间对话。只有带有反序列化的自定义序列化对我根据需要处理时区有效。这是您需要如何使用baeldung.com/jackson-serialize-dates的完美教程
  • 这是一个单独的 jar 项目,名为 Jackson Annotations。示例pom entry
  • 直到我在 @JsonFormat 注释之前添加 @JsonSerialize(as = Date.class) 之前,这对我不起作用。
【解决方案2】:

我需要使用什么格式与 Jackson 进行转换? Date 是一个很好的字段类型吗?

Date 是一个很好的字段类型。您可以使用 ObjectMapper.setDateFormat 轻松地使 JSON 可解析:

DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm a z");
myObjectMapper.setDateFormat(df);

一般来说,有没有办法在 Jackson 将变量映射到 Object 成员之前对其进行处理?诸如更改格式、计算等。

是的。您有几个选择,包括实现自定义 JsonDeserializer,例如扩展JsonDeserializer<Date>This 是一个好的开始。

【讨论】:

  • 12 小时格式更好,如果格式还包括 AM/PM 名称:DateFormat df = new SimpleDateFormat("yyyy-MM-dd hh:mm a z");
  • 尝试了所有这些解决方案,但无法将我的 POJO 的日期变量存储到 Map 键值中,也作为日期。然后我希望它从 Map 中实例化一个 BasicDbObject (MongoDB API),然后将变量作为 Date (而不是 Long 或 String) 存储在 MongoDB DB 集合中。这甚至可能吗?谢谢
  • 使用 Java 8 LocalDateTimeZonedDateTime 代替 Date 是否同样容易?由于Date 基本上已被弃用(或至少它的许多方法),我想使用这些替代方案。
  • setSateFormat() 的 javadocs 说这个调用使 ObjectMapper 不再是线程安全的。我创建了 JsonSerializer 和 JsonDeserializer 类。
  • 由于问题没有明确提及java.util.Date,我想指出这不适用于java.sql.Date.,另请参阅下面的答案。
【解决方案3】:

当然有一种自动化的方式叫做序列化和反序列化,你可以用特定的注解来定义它(@JsonSerialize,@JsonDeserialize) pb2q 也提到过。

您可以同时使用 java.util.Date 和 java.util.Calendar ... 可能还有 JodaTime。

@JsonFormat 注释没有按我的意愿工作(它已将时区调整为不同的值)在反序列化期间(序列化工作完美):

@JsonFormat(locale = "hu", shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm", timezone = "CET")

@JsonFormat(locale = "hu", shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm", timezone = "Europe/Budapest")

如果你想要预测结果,你需要使用自定义序列化器和自定义反序列化器而不是 @JsonFormat 注释。我在这里找到了真正好的教程和解决方案http://www.baeldung.com/jackson-serialize-dates

Date 字段的示例,但我需要 Calendar 字段,所以这是我的实现

序列化器类:

public class CustomCalendarSerializer extends JsonSerializer<Calendar> {

    public static final SimpleDateFormat FORMATTER = new SimpleDateFormat("yyyy-MM-dd HH:mm");
    public static final Locale LOCALE_HUNGARIAN = new Locale("hu", "HU");
    public static final TimeZone LOCAL_TIME_ZONE = TimeZone.getTimeZone("Europe/Budapest");

    @Override
    public void serialize(Calendar value, JsonGenerator gen, SerializerProvider arg2)
            throws IOException, JsonProcessingException {
        if (value == null) {
            gen.writeNull();
        } else {
            gen.writeString(FORMATTER.format(value.getTime()));
        }
    }
}

反序列化器类:

public class CustomCalendarDeserializer extends JsonDeserializer<Calendar> {

    @Override
    public Calendar deserialize(JsonParser jsonparser, DeserializationContext context)
            throws IOException, JsonProcessingException {
        String dateAsString = jsonparser.getText();
        try {
            Date date = CustomCalendarSerializer.FORMATTER.parse(dateAsString);
            Calendar calendar = Calendar.getInstance(
                CustomCalendarSerializer.LOCAL_TIME_ZONE, 
                CustomCalendarSerializer.LOCALE_HUNGARIAN
            );
            calendar.setTime(date);
            return calendar;
        } catch (ParseException e) {
            throw new RuntimeException(e);
        }
    }
}

及上述类的用法

public class CalendarEntry {

    @JsonSerialize(using = CustomCalendarSerializer.class)
    @JsonDeserialize(using = CustomCalendarDeserializer.class)
    private Calendar calendar;

    // ... additional things ...
}

使用此实现,序列化和反序列化过程的执行连续产生原始值。

仅使用 @JsonFormat 注释反序列化会产生不同的结果我认为由于库内部时区默认设置,您无法使用注释参数更改(这是我对 Jackson 库 2.5.3 的经验)以及 2.6.3 版本)。

【讨论】:

  • 我昨天的回答被否决了。我在这个主题上做了很多工作,所以我不明白。我可以得到一些反馈来学习吗?如果不赞成投票,我将不胜感激。这样我们可以互相学习更多。
  • 很好的答案,谢谢它真的帮助了我!次要建议 - 考虑将 CustomCalendarSerializer 和 CustomCalendarDeserializer 作为封闭父类中的静态类。我认为这会使代码更好一点:)
  • @Stuart - 您应该简单地提供代码的这种重组作为另一个答案,或者提出修改。 Miklos 可能没有时间去做。
  • @MiklosKrivan 由于几个原因,我对你投了反对票。您应该知道 SimpleDateFormat 不是线程安全的,坦率地说,您应该使用替代库进行日期格式化(Joda、Commons-lang FastDateFormat 等)。另一个原因是时区甚至语言环境的设置。最好在序列化环境中使用 GMT 并让您的客户说出它所在的时区,甚至将首选 tz 作为单独的字符串附加。将您的服务器设置为 GMT 又名 UTC。 Jackson 有内置的 ISO 格式。
  • 感谢@AdamGent 提供您的反馈。我理解并接受您的建议。但在这种特殊情况下,我只想强调带有语言环境信息的 JsonFormat 注释没有按照我们预期的方式工作。以及如何解决。
【解决方案4】:

在日期中添加 T 和 Z 等字符

@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss'Z'")
private Date currentTime;

输出

{
    "currentTime": "2019-12-11T11:40:49Z"
}

【讨论】:

    【解决方案5】:

    spring boot 应用程序的完整示例,RFC3339 datetime 格式

    package bj.demo;
    
    import com.fasterxml.jackson.databind.ObjectMapper;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.boot.context.event.ApplicationReadyEvent;
    import org.springframework.context.ApplicationListener;
    
    import java.text.SimpleDateFormat;
    
    /**
     * Created by BaiJiFeiLong@gmail.com at 2018/5/4 10:22
     */
    @SpringBootApplication
    public class BarApp implements ApplicationListener<ApplicationReadyEvent> {
    
        public static void main(String[] args) {
            SpringApplication.run(BarApp.class, args);
        }
    
        @Autowired
        private ObjectMapper objectMapper;
    
        @Override
        public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) {
            objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX"));
        }
    }
    

    【讨论】:

      【解决方案6】:

      基于@miklov-kriven 非常有帮助的回答,我希望这两个额外的考虑点对某人有所帮助:

      (1) 我发现将序列化器和反序列化器作为静态内部类包含在同一个类中是个好主意。注意,使用 ThreadLocal 来保证 SimpleDateFormat 的线程安全。

      public class DateConverter {
      
          private static final ThreadLocal<SimpleDateFormat> sdf = 
              ThreadLocal.<SimpleDateFormat>withInitial(
                      () -> {return new SimpleDateFormat("yyyy-MM-dd HH:mm a z");});
      
          public static class Serialize extends JsonSerializer<Date> {
              @Override
              public void serialize(Date value, JsonGenerator jgen SerializerProvider provider) throws Exception {
                  if (value == null) {
                      jgen.writeNull();
                  }
                  else {
                      jgen.writeString(sdf.get().format(value));
                  }
              }
          }
      
          public static class Deserialize extends JsonDeserializer<Date> {
              @Overrride
              public Date deserialize(JsonParser jp, DeserializationContext ctxt) throws Exception {
                  String dateAsString = jp.getText();
                  try {
                      if (Strings.isNullOrEmpty(dateAsString)) {
                          return null;
                      }
                      else {
                          return new Date(sdf.get().parse(dateAsString).getTime());
                      }
                  }
                  catch (ParseException pe) {
                      throw new RuntimeException(pe);
                  }
              }
          }
      }
      

      (2) 作为在每个单独的类成员上使用 @JsonSerialize 和 @JsonDeserialize 注释的替代方法,您还可以考虑通过在应用程序级别应用自定义序列化来覆盖 Jackson 的默认序列化,即日期类型的所有类成员都将是Jackson 使用此自定义序列化进行序列化,而无需在每个字段上显式注释。例如,如果您使用 Spring Boot,一种方法如下:

      @SpringBootApplication
      public class Application {
          public static void main(String[] args) {
              SpringApplication.run(Application.class, args);
          }
      
          @Bean
          public Module customModule() {
              SimpleModule module = new SimpleModule();
              module.addSerializer(Date.class, new DateConverter.Serialize());
              module.addDeserializer(Date.class, new Dateconverter.Deserialize());
              return module;
          }
      }
      

      【讨论】:

      • 我对你投了反对票(我讨厌反对票,但我只是不希望人们使用你的答案)。 SimpleDateFormat 不是线程安全的。现在是 2016 年(您在 2016 年回答)。当有许多更快和线程安全的选项时,您不应该使用 SimpleDateFormat。你在这里提出的 Q/A 甚至有一个确切的误用:stackoverflow.com/questions/25680728/…
      • @AdamGent 感谢您指出这一点。在这种情况下,使用 Jackson,ObjectMapper 类是线程安全的,所以没关系。但是,我确实同意您的观点,即可以在非线程安全的上下文中复制和使用代码。所以我编辑了我的答案,以使访问 SimpleDateFormat 线程安全。我也承认有其他选择,主要是 java.time 包。
      【解决方案7】:

      为我工作。 SpringBoot。

       import com.alibaba.fastjson.annotation.JSONField;
      
       @JSONField(format = "yyyy-MM-dd HH:mm:ss")  
       private Date createTime;
      

      输出:

      { 
         "createTime": "2019-06-14 13:07:21"
      }
      

      【讨论】:

      • com.alibaba.fastjson.annotation.JSONField 是什么?
      【解决方案8】:

      如果有人在为 java.sql.Date 使用自定义日期格式时遇到问题,这是最简单的解决方案:

      ObjectMapper mapper = new ObjectMapper();
      SimpleModule module = new SimpleModule();
      module.addSerializer(java.sql.Date.class, new DateSerializer());
      mapper.registerModule(module);
      

      (这个 SO-answer 为我省去了很多麻烦:https://stackoverflow.com/a/35212795/3149048

      Jackson 默认为 java.sql.Date 使用 SqlDateSerializer,但目前,此序列化程序不考虑日期格式,请参阅此问题:https://github.com/FasterXML/jackson-databind/issues/1407。 解决方法是为 java.sql.Date 注册不同的序列化程序,如代码示例所示。

      【讨论】:

        【解决方案9】:

        我想指出,像另一个答案中描述的那样设置SimpleDateFormat 仅适用于java.util.Date,我认为这是问题中的意思。 但是对于java.sql.Date,格式化程序不起作用。 就我而言,格式化程序不起作用的原因不是很明显,因为在应该序列化的模型中,该字段实际上是java.utl.Date,但实际对象最终变成了java.sql.Date。 这是可能的,因为

        public class java.sql extends java.util.Date
        

        所以这实际上是有效的

        java.util.Date date = new java.sql.Date(1542381115815L);
        

        因此,如果您想知道为什么您的日期字段格式不正确,请确保该对象确实是 java.util.Date

        Here也提到了为什么不会添加处理java.sql.Date

        这将是重大变化,我认为这是没有必要的。如果我们从头开始,我会同意这种变化,但事实并非如此。

        【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-10-15
        • 2019-08-30
        • 1970-01-01
        • 2018-04-05
        • 1970-01-01
        相关资源
        最近更新 更多