Формат даты Сопоставление с JSON Jackson

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

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

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

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

  • Какое форматирование я должен использовать для выполнения преобразования с Джексоном? Является ли Date хорошим полем для этого?
  • В общем, есть ли способ обработать переменные до того, как они будут сопоставлены членам Object Jackson? Что-то вроде, изменение формата, расчёты и т.д.

Ответ 1

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

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

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

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

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

Ответ 2

Начиная с Jackson v2.0, вы можете использовать аннотацию @JsonFormat непосредственно на объектах Object;

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

Ответ 3

Конечно, существует автоматизированный способ, называемый сериализации и десериализации, и вы можете определить его с помощью конкретных аннотаций (@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

Есть примеры для полей Дата, но мне нужны были Календарь, поэтому здесь моя реализация:

Класс 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()));
        }
    }
}

Класс десериализатор:

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 десериализация дает отличный результат. Я думаю, из-за установки по умолчанию внутреннего встроенного часового пояса, что вы не можете изменить с помощью параметров аннотации (это был мой опыт с библиотекой Джексона 2.5.3 и версии 2.6.3).

Ответ 5

Просто полный пример для весенней загрузки приложения с RFC3339 datetime 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 [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"));
    }
}

Ответ 7

Основываясь на очень полезном ответе @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 для каждого отдельного члена класса вы также можете рассмотреть возможность переопределения стандартной сериализации по умолчанию Jackson, применяя пользовательскую сериализацию на уровне приложения, то есть все члены класса типа Date будут сериализованный Jackson с использованием этой пользовательской сериализации без явного аннотации в каждом поле. Если вы используете 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;
    }
}

Ответ 8

Если у кого-то есть проблемы с использованием настраиваемого формата даты для java.sql.Date, это самое простое решение:

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

(Этот SO-ответ спас мне много неприятностей: fooobar.com/questions/73588/...)

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

Ответ 9

Я хочу указать, что установка 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);

Поэтому, если вам интересно, почему ваше поле Date неправильно отформатировано, убедитесь, что объект действительно является java.util.Date.

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

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

Ответ 10

Работаю на меня. 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"
}