关于java:日期格式映射到JSON Jackson

Date format Mapping to JSON Jackson

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

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

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

我想知道两件事:

  • 我需要使用什么格式来与杰克逊进行转换? Date是一个很好的字段类型吗?
  • 通常,有没有办法在变量被Jackson映射到Object成员之前处理变量? 比如,更改格式,计算等。

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

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


    What is the formatting I need to use to carry out conversion with Jackson? Is Date a good field type for this?

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

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

    In general, is there a way to process the variables before they get mapped to Object members by Jackson? Something like, changing the format, calculations, etc.

    是。您有几个选项,包括实现自定义JsonDeserializer,例如延伸JsonDeserializer。这是一个好的开始。


    当然,有一种称为序列化和反序列化的自动方式,您可以使用pb2q中提到的特定注释(@ JsonSerialize,@ JsonDeserialize)来定义它。

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

    在反序列化过程中,@ JsonFormat注释对我不起作用(它已将时区调整为不同的值)(序列化工作完美):

    1
    2
    3
    @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找到了真正好的教程和解决方案

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

    序列化程序类:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    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()));
            }
        }
    }

    反序列化器类:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    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);
            }
        }
    }

    以及上述类的用法:

    1
    2
    3
    4
    5
    6
    7
    8
    public class CalendarEntry {

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

        // ... additional things ...
    }

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

    只使用@JsonFormat注释反序列化给出了不同的结果我认为因为库内部时区默认设置你不能用注释参数改变(这是我对Jackson库2.5.3和2.6.3版本的体验)。


    这是使用RFC3339日期时间格式的春季启动应用程序的完整示例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    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 [email protected] 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"));
        }
    }

    为我工作SpringBoot。

    1
    2
    3
    4
     import com.alibaba.fastjson.annotation.JSONField;

     @JSONField(format ="yyyy-MM-dd HH:mm:ss")  
     private Date createTime;

    输出:

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

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

    1
    public class java.sql extends java.util.Date

    所以这实际上是有效的

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

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

    这里还提到了为什么不添加处理java.sql.Date

    This would then be breaking change, and I don't think that is warranted. If we were starting from scratch I would agree with the change, but as things are not so much.


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

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

    (这个答案给我带来了很多麻烦:https://stackoverflow.com/a/35212795/3149048)

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


    在@ miklov-kriven的非常有用的答案的基础上,我希望这两个额外的考虑点对某人有用:

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    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,则执行此操作的方法如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    @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;
        }
    }