รูปแบบวันที่แมปกับ JSON Jackson


154

ฉันมีรูปแบบวันที่ที่มาจาก API เช่นนี้:

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

ซึ่งเป็น YYYY-DD-MM HH: MM am / pm GMT timestamp ฉันกำลังจับคู่ค่านี้กับตัวแปร Date ใน POJO เห็นได้ชัดว่ามันแสดงข้อผิดพลาดในการแปลง

ฉันอยากรู้ 2 สิ่ง:

  1. การจัดรูปแบบที่ฉันต้องใช้เพื่อดำเนินการแปลงกับ Jackson คืออะไร วันที่เป็นฟิลด์ชนิดที่ดีสำหรับสิ่งนี้หรือไม่
  2. โดยทั่วไปมีวิธีการประมวลผลตัวแปรก่อนที่พวกเขาได้รับการแมปกับสมาชิกวัตถุโดยแจ็กสัน? การเปลี่ยนรูปแบบการคำนวณ ฯลฯ

นี่เป็นตัวอย่างที่ดีจริงๆวางคำอธิบายประกอบลงในฟิลด์ของคลาส: java.dzone.com/articles/how-serialize-javautildate
digz6666

ตอนนี้พวกเขามีหน้า wiki สำหรับการจัดการวันที่: wiki.fasterxml.com/JacksonFAQDateHandling
Sutra

วันนี้คุณไม่ควรใช้Dateชั้นเรียนอีกต่อไป java.time ซึ่งเป็น API วันที่และเวลา Java สมัยใหม่ได้แทนที่มาเกือบ 5 ปีที่แล้ว ใช้มันและFasterXML / แจ็คสันโมดูล java8
Ole VV

คำตอบ:


124

การจัดรูปแบบที่ฉันต้องใช้เพื่อดำเนินการแปลงกับ Jackson คืออะไร วันที่เป็นฟิลด์ชนิดที่ดีสำหรับสิ่งนี้หรือไม่

Dateเป็นประเภทฟิลด์ที่ดีสำหรับสิ่งนี้ คุณสามารถทำให้ JSON แจงได้อย่างง่ายดายโดยใช้ObjectMapper.setDateFormat:

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

โดยทั่วไปมีวิธีการประมวลผลตัวแปรก่อนที่พวกเขาได้รับการแมปกับสมาชิกวัตถุโดยแจ็กสัน? การเปลี่ยนรูปแบบการคำนวณ ฯลฯ

ใช่. คุณมีตัวเลือกไม่กี่รวมทั้งการดำเนินการที่กำหนดเองเช่นการขยายJsonDeserializer นี้เป็นการเริ่มต้นที่ดีJsonDeserializer<Date>


2
รูปแบบ 12 ชั่วโมงจะดีกว่าถ้ารูปแบบนั้นมีการกำหนด AM / PM ด้วย: DateFormat df = SimpleDateFormat ใหม่ ("yyyy-MM-dd hh: mm a z");
John Scattergood

พยายามแก้ไขปัญหาเหล่านี้ทั้งหมด แต่ไม่สามารถเก็บตัวแปร Date ของ POJO ของฉันลงในค่าคีย์แผนที่เช่นเดียวกับวันที่ ฉันต้องการสิ่งนี้เพื่อยกตัวอย่าง BasicDbObject (MongoDB API) จาก Map และเก็บตัวแปรไว้ในคอลเลกชัน MongoDB DB เป็น Date (ไม่ใช่ Long หรือ String) เป็นไปได้ไหม ขอบคุณ
RedEagle

1
มันเป็นเพียงเป็นเรื่องง่ายที่จะใช้ Java 8 LocalDateTimeหรือZonedDateTimeแทนDate? เนื่องจากDateเลิกใช้งานไปแล้ว (หรืออย่างน้อยก็หลายวิธี) ฉันต้องการใช้ทางเลือกเหล่านั้น
houcros

javadocs สำหรับ setSateFormat () บอกว่าการโทรนี้ทำให้ ObjectMapper ไม่ปลอดภัยอีกต่อไป ฉันสร้างคลาส JsonSerializer และ JsonDeserializer
MiguelMunoz

เนื่องจากคำถามไม่ได้กล่าวถึงjava.util.Dateอย่างชัดเจนฉันต้องการชี้ให้เห็นว่าสิ่งนี้ไม่ได้ผลสำหรับjava.sql.Date.ดูคำตอบของฉันด้านล่างด้วย
ลิงทองเหลือง

329

ตั้งแต่ Jackson v2.0 คุณสามารถใช้คำอธิบายประกอบ @JsonFormat โดยตรงกับสมาชิก Object;

@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

สวัสดีโถไหนมีคำอธิบายประกอบนั้น ฉันใช้เวอร์ชั่น 1.9.10 ของ jackson-mapper-asl ฉันไม่เข้าใจ
กฤษณะราม

1
@Ramki: คำอธิบายประกอบของแจ็คสัน> = 2.0
Olivier Lecrivain

3
คำอธิบายประกอบนี้ใช้งานได้ดีในเฟสการทำให้เป็นอนุกรมเท่านั้น แต่ในระหว่างการดีซีเรียลไลเซชันข้อมูลเขตเวลาและสถานที่ไม่ได้ใช้เลย ฉันลองใช้เขตเวลา = "CET" และเขตเวลา "ยุโรป / บูดาเปสต์" โดยใช้ locale = "hu" แต่ไม่สามารถใช้งานได้และทำให้เกิดการสนทนาเวลาแปลก ๆ บนปฏิทิน การทำให้เป็นอนุกรมที่กำหนดเองเท่านั้นที่มีการดีซีเรียลไลเซชันใช้ได้สำหรับฉันในการจัดการเขตเวลาตามที่ต้องการ นี่คือการสอนที่สมบูรณ์แบบว่าคุณจำเป็นต้องใช้baeldung.com/jackson-serialize-dates อย่างไร
Miklos Krivan

1
นี้เป็นโครงการที่ขวดแยกต่างหากที่เรียกว่าแจ็คสันคำอธิบายประกอบ ตัวอย่างรายการ pom
Bub

52

แน่นอนว่ามีวิธีการอัตโนมัติที่เรียกว่าการทำให้เป็นอนุกรมและการดีซีเรียลไลเซชันและคุณสามารถกำหนดมันด้วยคำอธิบายประกอบที่เฉพาะเจาะจง ( @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")

คุณต้องใช้ serializer ที่กำหนดเองและ deserializer ที่กำหนดเองแทนการเพิ่มความคิดเห็น @JsonFormat ถ้าคุณต้องการผลการทำนาย ฉันได้พบกับการสอนและวิธีแก้ปัญหาที่ดีจริง ๆ ที่นี่http://www.baeldung.com/jackson-serialize-dates

มีตัวอย่างสำหรับฟิลด์Dateแต่ฉันต้องการสำหรับฟิลด์Calendarดังนั้นนี่คือการใช้งานของฉัน :

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

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

และการใช้คลาสข้างต้น:

public class CalendarEntry {

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

    // ... additional things ...
}

การใช้การใช้งานนี้การดำเนินการของกระบวนการทำให้เป็นอนุกรมและ

การใช้คำอธิบายประกอบ @JsonFormat เท่านั้นการดีซีเรียลไลซ์เซชั่นให้ผลลัพธ์ที่แตกต่างฉันคิดว่าเนื่องจากการตั้งค่าเริ่มต้นของเขตเวลาภายในของไลบรารีสิ่งที่คุณไม่สามารถเปลี่ยนแปลงได้ด้วยพารามิเตอร์คำอธิบายประกอบ (นั่นคือประสบการณ์ของฉันกับห้องสมุด


4
ฉันลงคะแนนให้เมื่อวานนี้ ฉันทำงานมากในหัวข้อนี้ฉันจึงไม่เข้าใจ ฉันขอคำติชมเพื่อเรียนรู้จากมันได้ไหม ฉันจะขอบคุณบันทึกบางอย่างในกรณีที่มีการลงคะแนน วิธีนี้เราสามารถเรียนรู้เพิ่มเติมจากกันและกัน
Miklos Krivan

คำตอบที่ดีขอบคุณมันช่วยฉันได้จริงๆ! ข้อเสนอแนะเล็ก ๆ น้อย ๆ - พิจารณาวาง CustomCalendarSerializer และ CustomCalendarDeserializer เป็นคลาสสแตติกภายในคลาสพาเรนต์ที่ล้อมรอบ ผมคิดว่านี่จะทำให้รหัสที่ดีกว่าเล็ก ๆ น้อย ๆ :)
สจ๊วร์

@ Stuart - คุณควรเสนอการปรับโครงสร้างโค้ดนี้เป็นคำตอบอื่นหรือเสนอการแก้ไข Miklos อาจไม่มีเวลาให้ทำ
ocodo

@MiklosKrivan ฉันได้ลงคะแนนคุณด้วยเหตุผลหลายประการ คุณควรรู้ว่า SimpleDateFormat ไม่ใช่ threadsafe และตรงไปตรงมาคุณควรใช้ไลบรารีทางเลือกสำหรับการจัดรูปแบบวันที่ (Joda, Commons-lang FastDateFormat เป็นต้น) อีกเหตุผลคือการตั้งค่าของเขตเวลาและแม้แต่สถานที่ เป็นการดีกว่าที่จะใช้ GMT ในสภาพแวดล้อมการทำให้เป็นอนุกรมและให้ลูกค้าของคุณบอกว่าเขตเวลาใดอยู่ในหรือแม้กระทั่งแนบ tz ที่ต้องการเป็นสตริงแยกต่างหาก ตั้งค่าเซิร์ฟเวอร์ของคุณเป็น GMT หรือ UTC UTC Jackson มีรูปแบบ ISO ในตัว
Adam Gent

1
ขอบคุณ @ AdamGent สำหรับความคิดเห็นของคุณ ฉันเข้าใจและยอมรับข้อเสนอแนะของคุณ แต่ในกรณีนี้ฉันต้องการเน้นว่าคำอธิบายประกอบ JsonFormat กับข้อมูลสถานที่ไม่ทำงานอย่างที่เราคาดหวัง และจะแก้ไขได้อย่างไร
Miklos Krivan

4

เป็นเพียงตัวอย่างที่สมบูรณ์สำหรับแอพพลิเคชั่นบู๊ทสปริงด้วย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) ฉันคิดว่ามันเป็นความคิดที่ดีที่จะรวม serializer และ de-serializer เป็นชั้นในคงที่ในชั้นเรียนเดียวกัน NB, การใช้ 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 สำหรับสมาชิกแต่ละคลาสคุณยังสามารถพิจารณาการแทนที่อนุกรมเริ่มต้นของแจ็คสันโดยใช้การทำให้เป็นอนุกรมที่กำหนดเองในระดับแอปพลิเคชันนั่นคือสมาชิกชั้นเรียนทุกประเภท ใช้การทำให้เป็นอนุกรมที่กำหนดเองนี้โดยไม่มีคำอธิบายประกอบอย่างชัดเจนในแต่ละฟิลด์ หากคุณใช้ 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 ไม่ใช่ threadsafe นี่คือปี 2016 (คุณตอบในปี 2559) คุณไม่ควรใช้ SimpleDateFormat เมื่อมีตัวเลือกที่เร็วกว่าและเธรดที่ปลอดภัยมากมาย แม้มีการใช้สิ่งที่คุณเสนอ Q / A ในทางที่ผิด: stackoverflow.com/questions/25680728/…
Adam Gent

2
@ AdamGent ขอบคุณสำหรับการชี้ให้เห็น ในบริบทนี้การใช้ Jackson คลาส ObjectMapper เป็นเธรดที่ปลอดภัยดังนั้นจึงไม่สำคัญ อย่างไรก็ตามฉันคิดว่าคุณอาจคัดลอกและใช้รหัสในบริบทที่ไม่ปลอดภัยสำหรับเธรด ดังนั้นฉันได้แก้ไขคำตอบของฉันเพื่อให้เข้าถึง SimpleDateFormat thread ปลอดภัย ฉันยังรับทราบว่ามีทางเลือกเป็นหลักแพ็คเกจ java.time
Stuart

2

หากใครมีปัญหากับการใช้ Dateformat แบบกำหนดเองสำหรับ java.sql.Date นี่เป็นวิธีที่ง่ายที่สุด:

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

(คำตอบ SO นี้ช่วยฉันได้มากปัญหา: https://stackoverflow.com/a/35212795/3149048 )

แจ็คสันใช้ SqlDateSerializer โดยเริ่มต้นสำหรับ java.sql.Date แต่ปัจจุบัน serializer นี้ไม่ได้ใช้ dateformat เข้าบัญชีให้ดูปัญหานี้: https://github.com/FasterXML/jackson-databind/issues/1407 วิธีแก้ปัญหาคือการลงทะเบียน serializer ที่แตกต่างกันสำหรับ java.sql.Date ดังที่แสดงในตัวอย่างโค้ด


1

ฉันต้องการชี้ให้เห็นว่าการตั้งค่า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ดังนั้นหากคุณกำลังสงสัยว่าทำไมฟิลด์วันของคุณไม่ได้รูปแบบถูกต้องตรวจสอบให้แน่ใจว่าวัตถุที่เป็นจริง

ที่นี่ยังกล่าวถึงเหตุผลที่java.sql.Dateจะไม่เพิ่มการจัดการ

นี่จะเป็นการทำลายการเปลี่ยนแปลงและฉันไม่คิดว่าจะได้รับการรับประกัน หากเราเริ่มต้นจากศูนย์ฉันจะเห็นด้วยกับการเปลี่ยนแปลง แต่เป็นสิ่งที่ไม่มาก


1
ขอบคุณสำหรับการชี้ให้เห็นหนึ่งในผลของการออกแบบที่ไม่ดีของทั้งสองDateคลาสที่แตกต่างกัน วันนี้ไม่ควรใด ๆ SimpleDateFormatของพวกเขาแม้ว่ามิได้ java.time ซึ่งเป็น API วันที่และเวลา Java สมัยใหม่ได้แทนที่พวกเขาเกือบ 5 ปีที่แล้ว ใช้มันและFasterXML / แจ็คสันโมดูล java8
Ole VV
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.