日期格式映射到JSON Jackson


154

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

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

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

我想知道两件事:

  1. 与Jackson进行转换时,我需要使用哪种格式?Date是否适合此字段类型?
  2. 通常,是否有一种方法可以在Jackson将变量映射到Object成员之前对其进行处理?诸如更改格式,计算等。

这是一个很好的例子,将注释放在类的字段上:java.dzone.com/articles/how-serialize-javautildate
digz6666 2014年

现在,他们有了一个用于日期处理的Wiki页面:wiki.fasterxml.com/JacksonFAQDateHandling
Sutra

这些天,您不应再使用该Date课程。近5年前,现代Java日期和时间API java.time已取代它。使用它和FasterXML / jackson-modules-java8
Ole VV

Answers:


124

与Jackson进行转换时,我需要使用哪种格式?Date是否适合此字段类型?

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

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

通常,是否有一种方法可以在Jackson将变量映射到Object成员之前对其进行处理?诸如更改格式,计算等。

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


2
如果格式还包含AM / PM指定,则12小时格式会更好:DateFormat df = new SimpleDateFormat(“ yyyy-MM-dd hh:mm a z”);
John Scattergood,

尝试了所有这些解决方案,但无法将我的POJO的Date变量存储到Map键值(也作为Date)中。然后,我希望此方法从Map实例化BasicDbObject(MongoDB API),并因此将变量存储为MongoDB DB集合中的Date(而不是Long或String)。这有可能吗?谢谢
RedEagle

1
难道仅仅是因为易于使用的Java 8 LocalDateTimeZonedDateTime代替Date?由于Date基本上已弃用(或至少使用了许多方法),因此我想使用这些替代方法。
houcros

setSateFormat()的javadocs说,此调用使ObjectMapper不再是线程安全的。我创建了JsonSerializer和JsonDeserializer类。
MiguelMunoz '18

由于该问题未java.util.Date明确提及,因此我想指出,这不适用于“ java.sql.Date.另请参阅下面的答案”。
黄铜猴

329

从Jackson v2.0开始,您可以直接在Object成员上使用@JsonFormat注释;

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

61
如果要包括时区:@JsonFormat(shape=JsonFormat.Shape.STRING, pattern="yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", timezone="GMT")
realPK 2015年

嗨,哪个罐子有那个注释。我正在使用jackson-mapper-asl 1.9.10版本。我不明白
克里希纳·拉姆

1
@Ramki:杰克逊注释> = 2.0
Olivier Lecrivain

3
该注释仅在序列化阶段有效,而在反序列化期间,根本不使用时区和语言环境信息。我已经尝试使用locale =“ hu”设置了timezone =“ CET”和时区“ Europe / Budapest”,但是都无效,并且在Calendar上引起了奇怪的时间对话。只有自定义序列化和反序列化才可以按时处理时区。这是完美的教程,您需要如何使用baeldung.com/jackson-serialize-dates
Miklos Krivan 2015年

1
这是一个单独的jar项目,名为Jackson注释pom条目
bub,

52

当然,有一种称为序​​列化和反序列化的自动化方法,您也可以使用pb2q提到的特定注释(@JsonSerialize@JsonDeserialize)对其进行定义。

您可以同时使用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找到了很好的教程和解决方案

有一些日期字段的示例,但我需要日历字段,因此这是我的实现

串行器类:

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版本的经验)。


4
昨天我对我的回答不满意。我在这个主题上做了很多工作,所以我听不懂。我可以从中获得一些反馈吗?如果不满意,请多多注意。这样我们可以互相学习更多。
Miklos Krivan '16

很好的答案,谢谢,它真的帮助了我!较小的建议-考虑将CustomCalendarSerializer和CustomCalendarDeserializer作为包含在父类中的静态类。我认为这会使代码更好:)
Stuart

@Stuart-您应该只提供此代码重组作为另一个答案,或者提出修改建议。Miklos可能没有时间这样做。
ocodo

@MiklosKrivan我出于几个原因拒绝了您的投票。您应该知道SimpleDateFormat不是线程安全的,并且坦率地说,您应该使用替代库进行日期格式设置(Joda,Commons-lang FastDateFormat等)。另一个原因是设置时区,甚至是区域设置。最好在序列化环境中使用GMT,并让您的客户说出它所在的时区,甚至将首选的tz作为单独的字符串附加。将您的服务器设置为GMT或UTC。Jackson具有内置的ISO格式。
亚当·根特

1
感谢@AdamGent的反馈。我了解并接受您的建议。但是在这种特殊情况下,我只是想重点说明带有区域设置信息的JsonFormat注释无法按我们期望的方式工作。以及如何解决。
米克洛斯·克里文

4

只是具有日期时间格式的Spring Boot应用程序的完整示例RFC3339

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

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"
}

3

为我工作。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"
}

2

以@ 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的默认序列化,即Date类型的所有类成员都将由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/...
亚当根特

2
@AdamGent感谢您指出这一点。在这种情况下,使用Jackson可以使ObjectMapper类是线程安全的,因此无关紧要。但是,我确实同意您的观点,认为可以在非线程安全的上下文中复制和使用代码。因此,我已经编辑了答案以确保对SimpleDateFormat线程的访问安全。我也承认还有其他选择,主要是java.time包。
斯图尔特

2

如果有人对使用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

杰克逊默认将SqlDateSerializer用于java.sql.Date,但是当前,此序列化程序未考虑dateformat,请参阅以下问题:https : //github.com/FasterXML/jackson-databind/issues/1407。解决方法是为代码示例中所示的java.sql.Date注册一个不同的序列化程序。


1

我想指出的是,设置SimpleDateFormat其他答案中描述的“顶”仅适用于java.util.Date我认为是问题所在的顶视图。但是对于java.sql.Date格式化程序不起作用。在我的情况下,为什么格式化程序不起作用并不十分明显,因为在应该序列化的模型中,字段实际上是a,java.utl.Date但是实际对象最终变成a java.sql.Date。这是可能的,因为

public class java.sql extends java.util.Date

所以这实际上是有效的

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

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

这里也被提及为什么处理java.sql.Date将不会添加。

这将是突破性的变化,我认为这不是必须的。如果我们从头开始,我会同意所做的更改,但是事情并没有那么多。


1
感谢您指出两种不同Date类别的这种不良设计的后果。如今,也不应该是其中任何一个SimpleDateFormat。近5年前,现代Java日期和时间API java.time取代了它们。使用它和FasterXML / jackson-modules-java8
Ole VV
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.