Định dạng ngày Ánh xạ tới JSON Jackson


154

Tôi có định dạng Ngày đến từ API như thế này:

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

Đó là YYYY-DD-MM HH: MM am / pm GMT dấu thời gian. Tôi đang ánh xạ giá trị này đến một biến ngày trong POJO. Rõ ràng, nó hiển thị lỗi chuyển đổi.

Tôi muốn biết 2 điều:

  1. Định dạng tôi cần sử dụng để thực hiện chuyển đổi với Jackson là gì? Ngày có phải là một loại lĩnh vực tốt cho việc này?
  2. Nói chung, có cách nào để xử lý các biến trước khi chúng được ánh xạ tới các thành viên Object của Jackson không? Một cái gì đó như, thay đổi định dạng, tính toán, vv

Đây thực sự là một ví dụ hay, đặt chú thích trên trường của lớp: java.dzone.com/articles/how-serialize-javautildate
digz6666

Bây giờ họ có một trang wiki để xử lý ngày: wiki.fasterxml.com/JacksonFAQDateHandling
Kinh điển

Những ngày này bạn không còn nên sử dụng Datelớp học. java.time, API ngày và giờ Java hiện đại, đã thay thế nó gần 5 năm trước. Sử dụng nó và FasterXML / jackson-mô-đun-java8 .
Ole VV

Câu trả lời:


124

Định dạng tôi cần sử dụng để thực hiện chuyển đổi với Jackson là gì? Ngày có phải là một loại lĩnh vực tốt cho việc này?

Datelà một loại lĩnh vực tốt cho việc này. Bạn có thể làm cho phân tích cú pháp JSON có thể dễ dàng bằng cách sử dụng ObjectMapper.setDateFormat:

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

Nói chung, có cách nào để xử lý các biến trước khi chúng được ánh xạ tới các thành viên Object của Jackson không? Một cái gì đó như, thay đổi định dạng, tính toán, vv

Đúng. Bạn có một vài tùy chọn, bao gồm triển khai một tùy chỉnh JsonDeserializer, ví dụ như mở rộng JsonDeserializer<Date>. Đây là một khởi đầu tốt.


2
Định dạng 12 giờ sẽ tốt hơn nếu định dạng cũng bao gồm ký hiệu AM / PM: DateFormat df = new SimpleDateFormat ("yyyy-MM-dd hh: mm a z");
John Scattergood 8/2/2016

Đã thử tất cả các giải pháp này nhưng không thể lưu trữ biến Ngày của POJO của tôi vào giá trị khóa Bản đồ, cũng như Ngày. Tôi muốn điều này sau đó khởi tạo BasicDbObject (API MongoDB) từ Bản đồ và do đó lưu trữ biến trong bộ sưu tập DB MongoDB dưới dạng Ngày (không phải là Dài hoặc Chuỗi). Điều này thậm chí có thể? Cảm ơn bạn
RedEagle

1
Nó có dễ sử dụng Java 8 LocalDateTimehay ZonedDateTimethay vì Datekhông? Vì Datevề cơ bản không được dùng nữa (hoặc ít nhất là nhiều phương pháp của nó), tôi muốn sử dụng các phương án đó.
hoccros

Các javadocs cho setSateFormat () nói rằng cuộc gọi này làm cho ObjectMapper không còn Thread an toàn. Tôi đã tạo các lớp JsonSerializer và JsonDeserializer.
MiguelMunoz

Vì câu hỏi không đề cập java.util.Daterõ ràng, tôi muốn chỉ ra rằng điều này không hiệu quả đối với java.sql.Date.Xem thêm câu trả lời của tôi dưới đây.
khỉ đồng

329

Kể từ Jackson v2.0, bạn có thể sử dụng chú thích @JsonFormat trực tiếp trên các thành viên Object;

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

61
Nếu bạn muốn bao gồm múi giờ:@JsonFormat(shape=JsonFormat.Shape.STRING, pattern="yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", timezone="GMT")
realPK 13/03/2015

Xin chào, bình nào có chú thích đó. Tôi đang sử dụng phiên bản jackson-mapper-asl 1.9.10. Tôi không hiểu điều đó
krishna Ram

1
@Ramki: chú thích jackson> = 2.0
Olivier Lecrivain

3
Chú thích này chỉ hoạt động hoàn hảo trong giai đoạn tuần tự hóa nhưng trong quá trình khử lưu huỳnh, thông tin về múi giờ và miền địa phương hoàn toàn không được sử dụng. Tôi đã thử timezone = "CET" và múi giờ "Châu Âu / Budapest" với locale = "hu" nhưng không hoạt động và gây ra cuộc trò chuyện thời gian kỳ lạ trên Lịch. Chỉ có tuần tự hóa tùy chỉnh với khử lưu huỳnh làm việc cho tôi xử lý múi giờ theo yêu cầu. Đây là hướng dẫn hoàn hảo về cách bạn cần sử dụng baeldung.com/jackson-serialize-dates
Miklos

1
Đây là một dự án jar riêng biệt được gọi là Jackson Annotations . Mục nhập pom
bong bóng

52

Tất nhiên, có một cách tự động gọi là tuần tự hóa và giải tuần tự hóa và bạn có thể định nghĩa nó với các chú thích cụ thể ( @JsonSerialize , @JsonDeserialize ) như được đề cập bởi pb2q.

Bạn có thể sử dụng cả java.util.Date và java.util.CalWiki ... và có thể là JodaTime.

Các chú thích @JsonFormat không hoạt động với tôi như tôi muốn (nó đã điều chỉnh múi giờ thành giá trị khác nhau) trong quá trình khử lưu huỳnh (tuần tự hóa hoạt động hoàn hảo):

@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")

Bạn cần sử dụng serializer tùy chỉnh và deserializer tùy chỉnh thay vì chú thích @JsonFormat nếu bạn muốn kết quả dự đoán. Tôi đã tìm thấy hướng dẫn và giải pháp thực sự tốt ở đây http://www.baeldung.com/jackson-serialize-dates

Có các ví dụ cho các trường Ngày nhưng tôi cần cho các trường Lịch vì vậy đây là cách triển khai của tôi :

Lớp serializer :

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

Lớp deserializer :

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

và việc sử dụng các lớp trên:

public class CalendarEntry {

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

    // ... additional things ...
}

Sử dụng thực hiện này việc thực hiện quá trình tuần tự hóa và giải tuần tự hóa liên tiếp dẫn đến giá trị gốc.

Chỉ sử dụng chú thích @JsonFormat, việc khử lưu huỳnh sẽ mang lại kết quả khác vì tôi thiết lập mặc định múi giờ bên trong thư viện, điều bạn không thể thay đổi với các tham số chú thích (đó cũng là kinh nghiệm của tôi với phiên bản thư viện Jackson 2.5.3 và 2.6.3).


4
Tôi đã nhận được downvote cho câu trả lời của tôi ngày hôm qua. Tôi đã làm việc rất nhiều về chủ đề này vì vậy tôi không hiểu. Tôi có thể có một số phản hồi để học hỏi từ nó? Tôi sẽ đánh giá cao một số lưu ý trong trường hợp downvote. Bằng cách này chúng ta có thể học hỏi nhiều hơn từ nhau.
Miklos Krivan

Câu trả lời tuyệt vời, cảm ơn nó thực sự đã giúp tôi! Gợi ý nhỏ - xem xét việc đặt CustomCalWikiSerializer và CustomCalWikiDeserializer làm các lớp tĩnh trong một lớp cha kèm theo. Tôi nghĩ rằng điều này sẽ làm cho mã đẹp hơn một chút :)
Stuart

@Stuart - bạn chỉ cần cung cấp việc sắp xếp lại mã này dưới dạng câu trả lời khác hoặc đề xuất chỉnh sửa. Miklos có thể không có thời gian rảnh để làm điều đó.
ocodo

@MiklosKrivan Tôi đã đánh giá thấp bạn vì nhiều lý do. Bạn nên biết rằng SimpleDateFormat không phải là chủ đề an toàn và thật lòng bạn nên sử dụng các thư viện thay thế để định dạng ngày (Joda, Commons-lang FastDateFormat, v.v.). Lý do khác là cài đặt múi giờ và thậm chí cả miền địa phương. Tốt hơn hết là sử dụng GMT trong môi trường tuần tự hóa và để khách hàng của bạn nói rằng múi giờ đó nằm trong hoặc thậm chí đính kèm tz ưa thích dưới dạng một chuỗi riêng biệt. Đặt máy chủ của bạn ở chế độ GMT aka UTC. Jackson đã xây dựng định dạng ISO.
Adam Gent

1
Thx @AdamGent cho phản hồi của bạn. Tôi hiểu và chấp nhận đề xuất của bạn. Nhưng trong trường hợp cụ thể này, tôi chỉ muốn tập trung rằng chú thích JsonFormat với thông tin bản địa không hoạt động theo cách chúng ta mong đợi. Và làm thế nào có thể được giải quyết.
Miklos Krivan

4

Chỉ là một ví dụ hoàn chỉnh cho ứng dụng khởi động mùa xuân với RFC3339định dạng 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"));
    }
}

4

Để thêm các ký tự như T và Z trong ngày của bạn

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

đầu ra

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

3

Làm việc cho tôi. SpringBoot.

 import com.alibaba.fastjson.annotation.JSONField;

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

đầu ra:

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

2

Dựa trên câu trả lời rất hữu ích của @ miklov-kriven, tôi hy vọng hai điểm cân nhắc bổ sung này chứng minh hữu ích cho ai đó:

(1) Tôi thấy một ý tưởng hay là bao gồm serializer và de-serializer như các lớp bên trong tĩnh trong cùng một lớp. NB, sử dụng ThreadLocal để đảm bảo an toàn cho luồng của 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) Thay thế cho việc sử dụng các chú thích @JsonSerialize và @JsonDeserialize trên mỗi thành viên lớp riêng lẻ, bạn cũng có thể xem xét ghi đè tuần tự hóa mặc định của Jackson bằng cách áp dụng tuần tự hóa tùy chỉnh ở cấp ứng dụng, đó là tất cả các thành viên lớp loại Ngày sẽ được nối tiếp bởi Jackson sử dụng tuần tự hóa tùy chỉnh này mà không cần chú thích rõ ràng trên mỗi trường. Nếu bạn đang sử dụng Spring Boot chẳng hạn, một cách để làm điều này sẽ như sau:

@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;
    }
}

Tôi đánh giá thấp bạn (Tôi ghét bỏ qua nhưng tôi không muốn mọi người sử dụng câu trả lời của bạn). SimpleDateFormat không phải là chủ đề an toàn. Đó là năm 2016 (bạn đã trả lời vào năm 2016). Bạn không nên sử dụng SimpleDateFormat khi có nhiều tùy chọn an toàn và nhanh hơn. Thậm chí còn có một sự lạm dụng chính xác những gì bạn đang đề xuất Q / A ở đây: stackoverflow.com/questions/25680728/ợi
Adam Gent

2
@AdamGent cảm ơn bạn đã chỉ ra điều này. Trong ngữ cảnh này, sử dụng Jackson, lớp ObjectMapper là luồng an toàn nên không thành vấn đề. Tuy nhiên, tôi có quan điểm của bạn rằng mã có thể được sao chép và sử dụng trong ngữ cảnh an toàn không có luồng. Vì vậy, tôi đã chỉnh sửa câu trả lời của mình để đảm bảo quyền truy cập vào chủ đề SimpleDateFormat an toàn. Tôi cũng thừa nhận có những lựa chọn thay thế, chủ yếu là gói java.time.
Stuart

2

Nếu bất cứ ai có vấn đề với việc sử dụng một định dạng ngày tùy chỉnh cho java.sql.Date, đây là giải pháp đơn giản nhất:

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

(Câu trả lời SO này đã tiết kiệm cho tôi rất nhiều rắc rối: https://stackoverflow.com/a/35212795/3149048 )

Jackson sử dụng SqlDateSerializer theo mặc định cho java.sql.Date, nhưng hiện tại, trình tuần tự hóa này không tính đến dateformat, hãy xem vấn đề này: https://github.com/FasterXML/jackson-databind/issues/1407 . Cách giải quyết là đăng ký một serializer khác cho java.sql.Date như trong ví dụ mã.


1

Tôi muốn chỉ ra rằng việc đặt một câu SimpleDateFormatgiống như được mô tả trong câu trả lời khác chỉ hoạt động cho java.util.Datecâu hỏi mà tôi cho là có nghĩa trong câu hỏi. Nhưng đối với java.sql.Datecác định dạng không hoạt động. Trong trường hợp của tôi, không rõ ràng tại sao bộ định dạng không hoạt động bởi vì trong mô hình nên được tuần tự hóa, trường thực tế là một java.utl.Datenhưng đối tượng thực tế đã kết thúc java.sql.Date. Điều này là có thể bởi vì

public class java.sql extends java.util.Date

Vì vậy, điều này thực sự hợp lệ

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

Vì vậy, nếu bạn đang tự hỏi tại sao trường Ngày của bạn không được định dạng chính xác, hãy chắc chắn rằng đối tượng đó thực sự là a java.util.Date.

Ở đây cũng được đề cập tại sao xử lý java.sql.Datesẽ không được thêm vào.

Điều này sau đó sẽ phá vỡ sự thay đổi và tôi không nghĩ rằng nó được bảo hành. Nếu chúng tôi đã bắt đầu từ đầu, tôi sẽ đồng ý với sự thay đổi, nhưng vì mọi thứ không quá nhiều.


1
Cảm ơn đã chỉ ra một hậu quả của thiết kế xấu này của hai Datelớp khác nhau . Ngày nay, người ta không nên bất kỳ ai trong số họ, mặc dù, cũng không SimpleDateFormat. java.time, API ngày và giờ Java hiện đại, đã thay thế chúng gần 5 năm trước. Sử dụng nó và FasterXML / jackson-mô-đun-java8 .
Ole VV
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.