Формат даты Отображение в JSON Джексон

154

У меня есть формат даты из API, как это:

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

Который является ГГГГ-ДД-ММ ЧЧ: ММ am / pm GMT timestamp. Я сопоставляю это значение с переменной Date в POJO. Очевидно, это показывает ошибку преобразования.

Я хотел бы знать 2 вещи:

  1. Какое форматирование мне нужно использовать для конвертации с Джексоном? Является ли Date хорошим типом поля для этого?
  2. В общем, есть ли способ обработать переменные до того, как Джексон сопоставит их с членами Object? Что-то вроде смены формата, расчетов и т. Д.
Кевин Рэйв
источник
Это действительно хороший пример, поместите аннотацию в поле класса: java.dzone.com/articles/how-serialize-javautildate
digz6666
Теперь у них есть вики-страница для обработки дат: wiki.fasterxml.com/JacksonFAQDateHandling
Сутра
В эти дни вы больше не должны использовать Dateкласс. java.time, современный API даты и времени Java, заменил его почти 5 лет назад. Используйте его и FasterXML / jackson-modules-java8 .
Оле В.В.

Ответы:

125

Какое форматирование мне нужно использовать для конвертации с Джексоном? Является ли Date хорошим типом поля для этого?

Dateэто хороший тип поля для этого. Вы можете довольно легко разобрать JSON, используя ObjectMapper.setDateFormat:

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

В общем, есть ли способ обработать переменные до того, как Джексон сопоставит их с членами Object? Что-то вроде смены формата, расчетов и т. Д.

Да. У вас есть несколько вариантов, включая реализацию кастома JsonDeserializer, например расширение JsonDeserializer<Date>. Это хорошее начало.

pb2q
источник
2
12-часовой формат лучше, если он также включает обозначение AM / PM: DateFormat df = new SimpleDateFormat ("гггг-мм-дд чч: мм а я");
Джон Скаттергуд
Опробовал все эти решения, но не смог сохранить переменную Date моего POJO в значении ключа Map, также как Date. Я хочу, чтобы затем создать экземпляр BasicDbObject (MongoDB API) из карты, и, следовательно, сохранить переменную в коллекции базы данных MongoDB как Date (не как Long или String). Это вообще возможно? Спасибо
RedEagle
1
Так же просто использовать Java 8 LocalDateTimeили ZonedDateTimeвместо Date? Так Dateкак в основном устарел (или, по крайней мере, многие из его методов), я хотел бы использовать эти альтернативы.
Укрос
В 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
Привет, У какой банки есть эта аннотация. Я использую версию Jackson-Mapper-ASL ​​1.9.10. Я не понимаю
Кришна Рам
1
@Ramki: Джексон-аннотации> = 2.0
Оливье Лекривейн
3
Эта аннотация прекрасно работает только на этапе сериализации, но во время десериализации информация о часовом поясе и локали не используется вообще. Я пробовал timezone = "CET" и часовой пояс "Europe / Budapest" с locale = "hu", но ни один из них не работает и вызывает странный разговор о времени в Календаре. Только пользовательская сериализация с десериализацией работала для меня, обрабатывая часовой пояс по мере необходимости. Вот идеальный учебник, как вам нужно использовать baeldung.com/jackson-serialize-dates
Миклос Криван
1
Это отдельный jar-проект под названием « Jackson Annotations» . Пример записи в
помпе
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
У меня вчерашний ответ на мой ответ. Я много работал над этой темой, поэтому я не понимаю. Могу ли я получить отзывы, чтобы поучиться у него? Буду признателен за некоторые заметки в случае downvote. Таким образом, мы можем узнать больше друг от друга.
Миклош Криван
Отличный ответ, спасибо, это действительно помогло мне! Незначительное предложение - рассмотрите возможность размещения CustomCalendarSerializer и CustomCalendarDeserializer в качестве статических классов внутри родительского класса. Я думаю, что это сделало бы код немного лучше :)
Стюарт
@Stuart - вы должны просто предложить эту реорганизацию кода в качестве другого ответа или предложить редактирование. У Миклоса может не быть свободного времени, чтобы сделать это.
ocodo
@MiklosKrivan Я проголосовал против тебя по нескольким причинам. Вы должны знать, что SimpleDateFormat не является потокобезопасным, и, честно говоря, вы должны использовать альтернативные библиотеки для форматирования даты (Joda, Commons-lang FastDateFormat и т. Д.). Другая причина - установка часового пояса и даже локали. Гораздо предпочтительнее использовать GMT в среде сериализации и позволить своему клиенту сказать, в каком часовом поясе он находится, или даже присоединить предпочтительный tz в виде отдельной строки. Установите ваш сервер на GMT или UTC. Джексон имеет встроенный формат ISO.
Адам Гент
1
Спасибо @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"));
    }
}
BaiJiFeiLong
источник
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"
}
Odwori
источник
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) Я считаю хорошей идеей включить сериализатор и десериализатор как статические внутренние классы в одном классе. 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 для каждого отдельного члена класса вы также можете рассмотреть возможность переопределения сериализации по умолчанию Джексона, применяя пользовательскую сериализацию на уровне приложения, то есть все члены класса типа Date будут сериализованы Джексоном. используя эту пользовательскую сериализацию без явной аннотации для каждого поля. Если вы используете 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);

(Этот SO-ответ избавил меня от многих проблем: https://stackoverflow.com/a/35212795/3149048 )

Джексон по умолчанию использует SqlDateSerializer для java.sql.Date, но в настоящее время этот сериализатор не учитывает формат даты, см. Эту проблему: 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 не отформатировано правильно, убедитесь, что объект действительно a java.util.Date.

Здесь также упоминается, почему обработка java.sql.Dateне будет добавлена.

Тогда это приведет к серьезным переменам, и я не думаю, что это оправдано. Если бы мы начинали с нуля, я бы согласился с изменением, но так как все не так уж и много.

медная обезьяна
источник
1
Спасибо за указание на одно следствие этого плохого дизайна двух разных Dateклассов. В наши дни ни один из них не должен ни, ни, ни SimpleDateFormat. java.time, современный API даты и времени Java, заменил их почти 5 лет назад. Используйте его и FasterXML / jackson-modules-java8 .
Оле В.В.