Формат дати Картографування в JSON Jackson


154

У мене формат дати, що надходить з API, такий:

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

Що таке РРРР-ДД-ММ HH: ММ ранку / годину GMT GMT. Я зіставляю це значення зі змінною Дата в POJO. Очевидно, що вона показує помилку конверсії.

Я хотів би знати 2 речі:

  1. Яке форматування потрібно використовувати для здійснення конверсії за допомогою Джексона? Чи хороший тип поля для цього?
  2. Взагалі, чи існує спосіб обробки змінних, перш ніж їх Джексон відобразить до членів Об'єкта? Щось на зразок, зміна формату, розрахунки тощо.

Це справді хороший приклад, розмістіть анотацію на полі класу: java.dzone.com/articles/how-serialize-javautildate
digz6666

Тепер у них є вікі-сторінка для обробки дати: wiki.fasterxml.com/JacksonFAQDateHandling
Сутра,

У ці дні вам більше не слід користуватися Dateкласом. java.time, сучасний API дати та часу Java, замінив його майже 5 років тому. Використовуйте його і FasterXML / jackson-module-java8 .
Оле ВВ

Відповіді:


124

Яке форматування потрібно використовувати для здійснення конверсії за допомогою Джексона? Чи хороший тип поля для цього?

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");
Джон Скаттергуд

Спробував усі ці рішення, але не зміг зберегти змінну Date мого POJO у значення ключа Map, також як Date. Я хочу, щоб потім інстанціювати BasicDbObject (MongoDB API) з Map, а отже, зберігати змінну в колекції DB MongoDB як Date (не як Long або String). Це навіть можливо? Дякую
RedEagle

1
Чи просто так просто використовувати Java 8 LocalDateTimeчи ZonedDateTimeзамість цього Date? Оскільки Dateв основному застаріла (або принаймні багато її методів), я хотів би використати ці альтернативи.
houcros

Javadocs для setSateFormat () кажуть, що цей виклик робить ObjectMapper більше не захищеною. Я створив класи JsonSerializer та JsonDeserializer.
МігельМуноз

Оскільки в цьому питанні прямо не згадується, 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

Привіт, які баночки мають цю анотацію. Я використовую версію jackson-mapper-asl 1.9.10. Я не отримую цього
krishna Ram

1
@Ramki: jackson-annotations> = 2.0
Олів'є Лекривейн

3
Ця анотація ідеально підходить лише на етапі серіалізації, але під час десеріалізації інформація про часовий пояс та локаль взагалі не використовується. Я спробував timezone = "CET" та часовий пояс "Європа / Будапешт" з locale = "hu", але це не працює і викликає дивні часові розмови в календарі. Лише спеціальна серіалізація з десеріалізацією працювала для мене, якщо потрібно, обробляючи часовий пояс. Ось ідеальний підручник, як вам потрібно використовувати baeldung.com/jackson-serialize-dates
Miklos

1
Це окремий баночний проект під назвою Анотації Джексона . Зразок запису з
помпою

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

Вам потрібно використовувати спеціальний серіалізатор та спеціальний десеріалізатор замість анотації @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
Учора я отримав відповідне рішення за свою відповідь. Я багато працював над цією темою, тому не розумію. Чи можу я отримати зворотній зв'язок, щоб дізнатисясь від цього? Буду вдячний за деякими записками у випадку голосування. Таким чином ми можемо дізнатися більше один від одного.
Міклош Криван

Чудова відповідь, дякую, що це мені справді допомогло! Незначна пропозиція - розгляньте про те, як використовувати CustomCalendarSerializer та CustomCalendarDeserializer як статичні класи в базовому класі батьків. Я думаю, що це зробить код трохи приємнішим :)
Стюарт

@Stuart - слід просто запропонувати цю реорганізацію коду як іншу відповідь або запропонувати редагувати. Міклош може не мати вільного часу для цього.
окудо

@MiklosKrivan Я поступив проти тебе з кількох причин. Ви повинні знати, що SimpleDateFormat не є безпечним для потоків, і відверто вам слід використовувати альтернативні бібліотеки для форматування дат (Joda, Commons-lang FastDateFormat тощо). Інша причина - це встановлення часового поясу та навіть локального рівня. Набагато переважніше використовувати GMT у середовищі серіалізації та дозволити вашому клієнту сказати, у якому часовому поясі він знаходиться, або навіть приєднати бажаний tz як окремий рядок. Встановіть, щоб ваш сервер був на GMT aka UTC. Джексон має вбудований формат ISO.
Адам Гент

1
Thx @AdamGent для ваших відгуків. Я розумію і приймаю ваші пропозиції. Але в цьому конкретному випадку я просто хотів зробити акцент на тому, що анотація JsonFormat з інформацією про локаль не працює так, як ми очікували. І як можна вирішити.
Міклош Криван

4

Просто повний приклад для весняного завантаження програми з RFC3339форматом 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

Щоб додати символи, такі як 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 для кожного окремого члена класу ви також можете розглянути питання про переосмислення серіалізації за замовчуванням Джексона, застосувавши власну серіалізацію на рівні програми, тобто всі члени класу типу Дата будуть серіалізовані Джексоном використовуючи цю власну серіалізацію без явного анотації на кожному полі. Якщо ви використовуєте 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 дякую, що вказали на це. У цьому контексті, використовуючи Джексона, клас 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);

(Ця відповідь ТА врятувала мені багато клопоту: https://stackoverflow.com/a/35212795/3149048 )

Джексон використовує SqlDateSerializer за замовчуванням для java.sql.Date, але в даний час цей серіалізатор не враховує формат дати, врахуйте цю проблему: https://github.com/FasterXML/jackson-databind/isissue/1407 . Вирішення проблеми полягає в тому, щоб зареєструвати інший серіалізатор для java.sql.Date, як показано в прикладі коду.


1

Хочу зазначити, що встановлення SimpleDateFormatподібного, описаного в іншій відповіді, працює лише для того, на java.util.Dateякий я припускаю, мається на увазі у запитанні. Але для java.sql.Dateформатера не працює. У моєму випадку було не зовсім очевидно, чому форматер не працював, оскільки в моделі, яку слід серіалізувати, поле насправді було, java.utl.Dateале фактичний об’єкт у кінцевому підсумку виявився a 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 / jackson-module-java8 .
Оле ВВ
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.