【问题标题】:How to parse properly Date string to java.util.Date when deserializing JSON to Java POJO with Jackson ObjectMapper使用 Jackson ObjectMapper 将 JSON 反序列化为 Java POJO 时如何将日期字符串正确解析为 java.util.Date
【发布时间】:2021-11-08 16:43:29
【问题描述】:

我正在实现一个应用程序,它以 json 格式从 certin endponts 获取数据并尝试将 tem 反序列化为 Java 对象,但我在解析 JSON 中的日期时遇到问题。这就是 JSON 中日期的样子: "/Date(1633122000000+0300)/" 我在谷歌中找不到如何成功解析这种格式的信息。

{
  "Date": "/Date(1633122000000+0300)/",
  "Filled": 0,
  "Needed": 0,
  "Paid": 0
}

这是我用来反序列化数据以使用 Jackson ObjectMapper 的 pojo:

import java.util.Date;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class TimeByDateSheet {

    @JsonProperty("Date")
    @JsonFormat(timezone = "GMT+03:00")
    @JsonDeserialize(using = DateDeserializer.class, as=Date.class)
    private Date date;
    
    @JsonProperty("Filled")
    private Long filled;
 
    @JsonProperty("Needed")
    private Long needed;
    
   @JsonProperty("Paid")
   private Integer paid;

}

这是我的 DateDeserializer:

@SuppressWarnings("serial")
public class DateDeserializer extends JsonDeserializer<Date> {

@Override
public Date deserialize(JsonParser jsonParser, DeserializationContext context) 
throws IOException, JsonProcessingException {
    SimpleDateFormat simpleDateFormat = 
         new SimpleDateFormat("yyyy-MM-dd HH:mm:ss zzz", Locale.getDefault());
    String dateStr = jsonParser.getText();
    Date date;
    try{
        date = simpleDateFormat.parse(dateStr);
    }catch(ParseException e){
        throw new RuntimeException(e);
    }
    return date;
}

}

但它不能正常工作。我得到以下异常:

连接到目标VM,地址:'127.0.0.1:52760',传输:'socket' SLF4J:无法加载类“org.slf4j.impl.StaticLoggerBinder”。 SLF4J:默认为无操作(NOP)记录器实现 SLF4J:有关详细信息,请参阅http://www.slf4j.org/codes.html#StaticLoggerBinder。 线程“主”com.fasterxml.jackson.databind.JsonMappingException 中的异常:java.text.ParseException:无法解析的日期:“/Date(1633035600000+0300)/”(通过引用链:java.util.ArrayList[0]-> com.dataart.forecasts.pojo.timebydate.TimeByDateSheet["Date"]) 在 com.fasterxml.jackson.databind.JsonMappingException.wrapWithPath(JsonMappingException.java:392) 在 com.fasterxml.jackson.databind.JsonMappingException.wrapWithPath(JsonMappingException.java:351) 在 com.fasterxml.jackson.databind.deser.BeanDeserializerBase.wrapAndThrow(BeanDeserializerBase.java:1821) 在 com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:315) 在 com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:176) 在 com.fasterxml.jackson.databind.deser.std.CollectionDeserializer._deserializeFromArray(CollectionDeserializer.java:355) 在 com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:244) 在 com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:28) 在 com.fasterxml.jackson.databind.deser.DefaultDeserializationContext.readRootValue(DefaultDeserializationContext.java:322) 在 com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4675) 在 com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3630) 在 com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3613) 在 com.dataart.forecasts.DataProcessor.deserializeTimeByDateSheetsList(DataProcessor.java:198) 在 com.dataart.forecasts.ForecastReportApplication.main(ForecastReportApplication.java:50) 引起:java.lang.RuntimeException: java.text.ParseException: Unparseable date: "/Date(1633035600000+0300)/" 在 com.dataart.forecasts.DateDeserializer.deserialize(DateDeserializer.java:28) 在 com.dataart.forecasts.DateDeserializer.deserialize(DateDeserializer.java:16) 在 com.fasterxml.jackson.databind.deser.impl.MethodProperty.deserializeAndSet(MethodProperty.java:129) 在 com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:313) ... 10 更多 引起:java.text.ParseException:无法解析的日期:“/Date(1633035600000+0300)/” 在 java.base/java.text.DateFormat.parse(DateFormat.java:395) 在 com.dataart.forecasts.DateDeserializer.deserialize(DateDeserializer.java:26) ... 13 更多

请有人帮助我。我在互联网上搜索了很多,但找不到解决方案。 先感谢您! :)

【问题讨论】:

标签: java date parsing deserialization


【解决方案1】:

生成 JSON 时似乎存在问题。我真的不认为你想要这样格式化日期。现在,你有一些奇怪的文本围绕着一个以毫秒为单位的 unix 时间戳,然后是一个区域偏移量。您还使用了旧的且相当不受欢迎的 Date 和 SimpleDateFormat 类,而不是较新的 java.time API。但是,可以反序列化您的日期格式。这是一种方法:

public class DateDeserializer extends JsonDeserializer<Date> {
    @Override
    public Date deserialize(JsonParser jsonParser, DeserializationContext context) 
            throws IOException, JsonProcessingException {
        Pattern pattern = Pattern.compile("/Date\\((\\d+)([+-]\\d+)\\)/");
        Matcher matcher = pattern.matcher(jsonParser.getText());
        if (matcher.find()) {
            String timestamp = matcher.group(1);
            String offset = matcher.group(2);
            Instant instant = Instant.ofEpochMilli(Long.parseLong(timestamp));
            ZonedDateTime zdt = instant.atZone(ZoneId.of(offset));
            instant = zdt.toInstant();
            return Date.from(instant);
        } else {
          throw new RuntimeException("Invalid format: " + jsonParser.getText());
        }
    }
}

【讨论】:

  • 在 JSON 中经常看到奇怪的格式(有和没有偏移量)。我不知道它来自哪里。
  • @OleV.V.唔。在我的公司,我们使用 JSON,我们的日期总是像“2021-11-08T21:36:15.234Z”。
  • @OleV.V.我认为关键是我们使用我们自己的ObjectMapper 子类,并在构造函数中设置super.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);super.registerModule(new JavaTimeModule());
  • @DavidConrad 您的解决方案效果很好,只是稍微重构了模式以匹配其他 json 中一些略有不同的日期。
  • @DavidConrad 听起来我也在做。 :-)
【解决方案2】:

java.time

对于这个答案,我假设:

  1. JSON 中的时间可能带有或不带有 UTC 偏移量。
  2. 您可以在 java.time(现代 Java 日期和时间 API)上全力以赴,并将您的变量声明为 InstantOffsetDateTime 类型,例如(而不是 Date)。

对于带有偏移量的 JSON,例如 +0300,将您的变量声明为 OffsetDateTime。然后使用下面的解串器。

public class OdtDeserializer extends JsonDeserializer<OffsetDateTime> {

    private static final DateTimeFormatter JSON_DATE_FORMATTER = new DateTimeFormatterBuilder()
            .appendLiteral("/Date(")
            .appendValue(ChronoField.INSTANT_SECONDS)
            .appendValue(ChronoField.MILLI_OF_SECOND, 3)
            .appendOffset("+HHMM", "Z")
            .appendLiteral(")/")
            .toFormatter();

    @Override
    public OffsetDateTime deserialize(JsonParser jsonParser, DeserializationContext context)
            throws IOException {
        String dateStr = jsonParser.getText();
        return OffsetDateTime.parse(dateStr, JSON_DATE_FORMATTER);
    }

}

对于没有偏移量的 JSON,例如 /Date(1636510000000)/,声明您的变量 Instant。使用类似的解串器。省略格式化程序的偏移量。解析成Instant——语法有点不同。

public class InstantDeserializerWIthoutOffset extends JsonDeserializer<Instant> {

    private static final DateTimeFormatter JSON_DATE_FORMATTER = new DateTimeFormatterBuilder()
            .appendLiteral("/Date(")
            .appendValue(ChronoField.INSTANT_SECONDS)
            .appendValue(ChronoField.MILLI_OF_SECOND, 3)
            .appendLiteral(")/")
            .toFormatter();

    @Override
    public Instant deserialize(JsonParser jsonParser, DeserializationContext context)
            throws IOException {
        String dateStr = jsonParser.getText();
        return JSON_DATE_FORMATTER.parse(dateStr, Instant::from);
    }

}

对于可能带有或不带有偏移量的 JSON,仍然使用 Instant,只需修改后一个反序列化器的格式化程序以包含可选的偏移量:

    private static final DateTimeFormatter JSON_DATE_FORMATTER = new DateTimeFormatterBuilder()
            .appendLiteral("/Date(")
            .appendValue(ChronoField.INSTANT_SECONDS)
            .appendValue(ChronoField.MILLI_OF_SECOND, 3)
            .optionalStart()
            .appendOffset("+HHMM", "Z")
            .optionalEnd()
            .appendLiteral(")/")
            .toFormatter();

如果您无法修改 POJO 类并且需要继续使用 Date,请将我的 Instant 解串器修改为 Date 解串器,方法是更改​​声明并返回 Date,如下所示:

        String dateStr = jsonParser.getText();
        Instant inst = JSON_DATE_FORMATTER.parse(dateStr, Instant::from);
        return Date.from(inst);

【讨论】:

    【解决方案3】:

    最终解决方案同时捕获:/Date(1633035600000+0300)/ 和 /Date(-62135596800000)/(后者也出现在其中一个 JSON 中的一个位置)。谢谢@DavidConrad

    @Override
        public Date deserialize(JsonParser jsonParser, DeserializationContext context) throws IOException, JsonProcessingException {
            String dateString = jsonParser.getText();
            Pattern pattern = Pattern.compile("/Date\\((-)?(\\d+)([+-]\\d+)?\\)/");
            Matcher matcher = pattern.matcher(dateString);
            if (!matcher.find()) {
                throw new RuntimeException("Invalid format: " + dateString);
            }
            String timestamp = matcher.group(2);
            String offset = matcher.group(3);
            Instant instant = Instant.ofEpochMilli(Long.parseLong(timestamp));
            if (nonNull(offset)) {
                ZonedDateTime zdt = instant.atZone(ZoneId.of(offset));
                instant = zdt.toInstant();
            }
            return Date.from(instant);
        }
    

    【讨论】:

      【解决方案4】:

      @DavidConrad 谢谢,我会试试你的解决方案。 顺便说一句,现在我做了一个适合我的解决方法:

      @Override
          public Date deserialize(JsonParser jsonParser, DeserializationContext context) throws IOException, JsonProcessingException {
              SimpleDateFormat dateFormattter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
              String dateStr = jsonParser.getText();
              String timeZone = dateStr.substring(dateStr.indexOf("+") + 1, dateStr.indexOf(")"));
              String timeZoneShift = String.format("%s:%s",
                      timeZone.substring(0, timeZone.length()/2),
                      timeZone.substring(timeZone.length()/2));
              dateFormattter.setTimeZone(TimeZone.getTimeZone(String.format("GMT+%s", timeZoneShift)));
              Long millis = 0L;
              if (dateStr.contains("+") && !dateStr.contains("-")) {
                  millis = Long.parseLong(dateStr.substring(dateStr.indexOf("(") + 1, dateStr.indexOf("+")));
              } else if (dateStr.contains("+") && !dateStr.contains("-")) {
                  millis = Long.parseLong(dateStr.substring(dateStr.indexOf("(") + 1, dateStr.indexOf(")")));
              }
              Date date = new Date(millis);
              String stringDate= dateFormattter.format(date);
              try {
                  date = dateFormattter.parse(stringDate);
              } catch (ParseException e) {
                  throw new RuntimeException(e);
              }
              return date;
          }
      

      【讨论】:

      • 感谢分享您的临时解决方案。这太复杂了。此外,偏移量的定界(例如+0300)似乎没有考虑到它可能以减号而不是加号开头。在 David Conrad 明智地说过之后,我们更希望看到您使用 java.time(现代 Java 日期和时间 API)的解决方案。
      • 只是稍微重构了模式以匹配其他 json 中一些稍微不同的日期 其他 JSON 有什么不同?从您的代码中很难看出——它们是否没有 UTC 偏移量?正如我提到的,我以前见过。
      • 这里有一个特定的日期是这种格式:/Date(-62135596800000)/ 所以我也必须修改正则表达式来捕捉它:/Date((-)?(\d+)( [+-]?\d+)?)
      • 啊,第一年。0001-01-01T00:00:00Z。以这种方式表示一个虚拟值是愚蠢的,但我想你将不得不忍受它?自 epoch 以来,我还没有以负毫秒测试我的答案中的代码,结果可能不准确。
      猜你喜欢
      • 1970-01-01
      • 2015-03-23
      • 2020-12-10
      • 2022-01-08
      • 2012-04-07
      • 1970-01-01
      • 2021-01-08
      • 2018-07-24
      • 1970-01-01
      相关资源
      最近更新 更多