Java 8 LocalDate формат Джексона


139

Для java.util.Date когда я делаю

@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "dd/MM/yyyy")  
  private Date dateOfBirth;

тогда в JSON запрос когда отправлю

{ {"dateOfBirth":"01/01/2000"} }  

оно работает.

Как мне сделать это для Java 8 LocalDate поля ??

Я пытался

@JsonDeserialize(using = LocalDateDeserializer.class)  
@JsonSerialize(using = LocalDateSerializer.class)  
private LocalDate dateOfBirth;  

Это не сработало.

Может кто-нибудь, пожалуйста, дайте мне знать, как правильно сделать это ..

Ниже приведены зависимости

<dependency>
    <groupId>org.jboss.resteasy</groupId>
    <artifactId>jaxrs-api</artifactId>
     <version>3.0.9.Final</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.jaxrs</groupId>
    <artifactId>jackson-jaxrs-json-provider</artifactId>
    <version>2.4.2</version>
</dependency>
<dependency>
    <groupId>com.wordnik</groupId>
    <artifactId>swagger-annotations</artifactId>
    <version>1.3.10</version>
</dependency>

Ответы:


106

Я никогда не мог заставить это работать просто, используя аннотации. Чтобы заставить его работать, я создал ContextResolverfor ObjectMapper, затем добавил JSR310Module( update: теперь оно JavaTimeModuleвзято ) вместе с еще одним предупреждением, которое было необходимо для установки write-date-as-timestamp в false. Подробнее смотрите в документации по модулю JSR310 . Вот пример того, что я использовал.

зависимость

<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-jsr310</artifactId>
    <version>2.4.0</version>
</dependency>

Примечание. Одна проблема, с которой я столкнулся, заключается в том, что jackson-annotationверсия, загруженная другой зависимостью, использует версию 2.3.2, которая отменяет версию 2.4, требуемую для jsr310. Я получил NoClassDefFound для ObjectIdResolverкласса 2.4. Так что мне просто нужно выстроить включенные версии зависимостей

ContextResolver

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JSR310Module;
import javax.ws.rs.ext.ContextResolver;
import javax.ws.rs.ext.Provider;

@Provider
public class ObjectMapperContextResolver implements ContextResolver<ObjectMapper> {  
    private final ObjectMapper MAPPER;

    public ObjectMapperContextResolver() {
        MAPPER = new ObjectMapper();
        // Now you should use JavaTimeModule instead
        MAPPER.registerModule(new JSR310Module());
        MAPPER.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
    }

    @Override
    public ObjectMapper getContext(Class<?> type) {
        return MAPPER;
    }  
}

Ресурсный класс

@Path("person")
public class LocalDateResource {

    @GET
    @Produces(MediaType.APPLICATION_JSON)
    public Response getPerson() {
        Person person = new Person();
        person.birthDate = LocalDate.now();
        return Response.ok(person).build();
    }

    @POST
    @Consumes(MediaType.APPLICATION_JSON)
    public Response createPerson(Person person) {
        return Response.ok(
                DateTimeFormatter.ISO_DATE.format(person.birthDate)).build();
    }

    public static class Person {
        public LocalDate birthDate;
    }
}

Тест

curl -v http://localhost:8080/api/person
Результат: {"birthDate":"2015-03-01"}

curl -v -POST -H "Content-Type:application/json" -d "{\"birthDate\":\"2015-03-01\"}" http://localhost:8080/api/person
Результат: 2015-03-01


Смотрите также здесь для решения JAXB.

ОБНОВИТЬ

JSR310ModuleНежелателен версии 2.7 Джексона. Вместо этого вы должны зарегистрировать модуль JavaTimeModule. Это все та же зависимость.


1
Привет Peeskillet, поле birthDate, генерируется как "birthDate": {"year": 0, "month": "Month", "dayOfMonth": 0, "dayOfWeek": "DayOfWeek": "era": {" значение ": 0}," dayOfYear ": 0," leapYear ": false," monthValue ": 0," хронология ": {" id ":" "," calendarType ":" "}} как я могу сделать это просто как "дата рождения" ???
JAB

Проверьте, вызван ли ContextResolver. Добавьте оператор печати в getContextметод. Если этот метод вызывается, я не вижу причин для этого не работать. Если он не вызывается, это может быть что-то, что должно быть исправлено в конфигурации приложения. Для этого мне нужно увидеть больше, чем вы предоставили. Как и версия Resteasy, зависимости, конфигурация приложения либо web.xml, либо подкласс приложения. В основном достаточно, чтобы воспроизвести проблему
Пол Самсота

ContextResolver не называется Peeskillet. Я повторно регистрирую его в файле web.xml как <context-param> <param-name> resteasy.resources </ param-name> <param-value> com.bac.ObjectMapperContextResolver </ param-value> </ context-param> обновленный вопрос для зависимостей, которые я использую
JAB

Swagger, кажется, проблема. Я бы сказал, чтобы отключить его, но, видя из этого вопроса, есть проблема, которая была подана, с конфликтом между ObjectMapper Swagger и пытается использовать свой собственный. Вы можете попробовать и отключить их, и в ContextResolver установить все конфигурации ObjectMapperкак swagger (вы можете увидеть ссылку в вопросе). Я не знаю, так как я не много работаю с чванством. Но я думаю, что чванство - главная проблема, почему не вызывается contextresolver.
Пол Самсота

После дальнейшего тестирования, аннотации делают работу. Даже если нам нужно использовать Swagger ObjectMapper, мы уже настраиваем метки времени как ложные для нас. Так что это должно работать. Для лучшей помощи я настоятельно рекомендую вам предоставить MCVE, который демонстрирует проблему.
Пол Самсота

95

@JsonSerialize и @JsonDeserialize отлично работали для меня. Они устраняют необходимость импортировать дополнительный модуль jsr310:

@JsonDeserialize(using = LocalDateDeserializer.class)  
@JsonSerialize(using = LocalDateSerializer.class)  
private LocalDate dateOfBirth;

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

public class LocalDateDeserializer extends StdDeserializer<LocalDate> {

    private static final long serialVersionUID = 1L;

    protected LocalDateDeserializer() {
        super(LocalDate.class);
    }


    @Override
    public LocalDate deserialize(JsonParser jp, DeserializationContext ctxt)
            throws IOException, JsonProcessingException {
        return LocalDate.parse(jp.readValueAs(String.class));
    }

}

Serializer:

public class LocalDateSerializer extends StdSerializer<LocalDate> {

    private static final long serialVersionUID = 1L;

    public LocalDateSerializer(){
        super(LocalDate.class);
    }

    @Override
    public void serialize(LocalDate value, JsonGenerator gen, SerializerProvider sp) throws IOException, JsonProcessingException {
        gen.writeString(value.format(DateTimeFormatter.ISO_LOCAL_DATE));
    }
}

2
Это лучший ответ для меня. Спасибо!
Romeo Jr Maranan

7
Эти классы включены в jackson-datatype-jsr310. Нет необходимости вручную определять их в вашем проекте.
NeuroXc

1
Это решение сработало для меня, используя сериализаторы в jackson-datatype-jsr310.
Дэйв

2
Это должен быть новый лучший ответ.
Doctor Parameter

1
Если вы используете сериализаторы и десериализаторы в jackson-datatype-jsr310, лучше добавьте @JsonFormat (shape = JsonFormat.Shape.STRING) в свое поле. Без этого формата значение будет сериализовано как [год, месяц, день], хотя десериализация будет работать.
Цзянь Чен

74
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);

у меня отлично работает


2
new com.fasterxml.jackson.datatype.jsr310.JSR310Module()для версии 2.5.4 Джексона. Класс JavaTimeModule в этой версии не существует.
user3774109

Этот ответ также работает для LocalDateTime(Джексон 2.9.5). Требуется 1 дополнительная зависимость, поэтому мой build.sbt выглядит так:"com.fasterxml.jackson.module" %% "jackson-module-scala" % "2.9.5", "com.fasterxml.jackson.datatype" % "jackson-datatype-jsr310" % "2.9.5"
ruhong

2
У этого должно быть намного больше голосов. Просто и эффективно.
mkasberg

Работал отлично! Спасибо.
MAD-HAX

Это указывало мне в правильном направлении, Спасибо! Я бы добавил, что при весенней загрузке все, что вам нужно сделать, это добавить в application.properties следующее: spring.jackson.serialization.write -ates-as-timestamps = false
Joost

46

В веб-приложении Spring Boot с версией Jackson и JSR 310 "2.8.5"

compile "com.fasterxml.jackson.core:jackson-databind:2.8.5"
runtime "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.8.5"

В @JsonFormatработе:

import com.fasterxml.jackson.annotation.JsonFormat;

@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd")
private LocalDate birthDate;

2
Это работает для десериализации? или только сериализация? Отсутствие успеха с десериализацией
rewolf 09

7
Мне пришлось явно объявить десериализатор@JsonDeserialize(using= LocalDateDeserializer.class)
rewolf

1
безусловно, самый простой!
Антонио

@JsonFormatпросто для изменения формата выходных данных. stackoverflow.com/a/53251526/816759 отлично работает с @JsonFormat, @JsonDeserialize,@JsonSerialize
Baha

В Spring Boot после добавления зависимости JSR310 все, что вам нужно сделать, это добавить spring.jackson.serialization.write-dates-as-timestamps=falseв ваш файл application.properties, и он yyyy-MM-ddавтоматически форматирует его . Нет необходимости@JsonFormat
esfandia

31

Самым простым решением (которое также поддерживает десериализацию и сериализацию) является

import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;

@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "dd/MM/yyyy")
@JsonDeserialize(using = LocalDateDeserializer.class)
@JsonSerialize(using = LocalDateSerializer.class)
private LocalDate dateOfBirth;

При использовании следующих зависимостей в вашем проекте.

специалист

<dependency>
   <groupId>com.fasterxml.jackson.core</groupId>
   <artifactId>jackson-databind</artifactId>
   <version>2.9.7</version>
</dependency>
<dependency>
   <groupId>com.fasterxml.jackson.datatype</groupId>
   <artifactId>jackson-datatype-jsr310</artifactId>
   <version>2.9.7</version>
</dependency>

Gradle

compile "com.fasterxml.jackson.core:jackson-databind:2.9.7"
compile "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.9.7"

Никакой дополнительной реализации ContextResolver, Serializer или Deserializer не требуется.


Блестящий, самый простой. FYI для тех, у кого много зависимостей, мне пришлось обновить некоторые другие библиотеки, которые включали аннотации Джексона.
BRT

Этот ответ - самый близкий к решению моей проблемы. Сериализация работает, но десериализация не удается из-за шаблона, который я использовал с @JsonFormat, я думаю (@JsonFormat (shape = JsonFormat.Shape.STRING, pattern = "dd-MM-yyyy_HH: mm: SS").
fsakiyama

Если у вас произошла ошибка десериализации, скорее всего, вы ObjectMapperне JavaTimeModuleзарегистрировались. Если ваш экземпляр ObjectMapper предоставлен из инфраструктуры spring / MessageConverter. Они сотворили магию, чтобы соединить их. В противном случае следует registerModuleвключить LocalDateDeserializerпо умолчанию для всех «LocalDate» в POJO
Dennis C

19

Поскольку LocalDateSerializerпо умолчанию он превращается в «[год, месяц, день]» (массив json), а не в «год-месяц-день» (строка json), и поскольку я не хочу требовать какой-либо специальной ObjectMapperнастройки (вы можете make LocalDateSerializergenerate strings, если вы отключите, SerializationFeature.WRITE_DATES_AS_TIMESTAMPSно для этого потребуется дополнительная настройка ObjectMapper), я использую следующее:

импорт:

import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;

код:

// generates "yyyy-MM-dd" output
@JsonSerialize(using = ToStringSerializer.class)
// handles "yyyy-MM-dd" input just fine (note: "yyyy-M-d" format will not work)
@JsonDeserialize(using = LocalDateDeserializer.class)
private LocalDate localDate;

И теперь я могу просто new ObjectMapper()читать и писать свои объекты без какой-либо специальной настройки.


3
Одна вещь, которую я хотел бы добавить, - это передать дату, поскольку "2018-12-07"вместо этого "2018-12-7"вы получите ошибку.
Kid101 06

1
Правильно, он работает с yyyy-MM-ddформатом (2 цифры месяца и дня), а не yyyy-M-d(1 цифра месяца или дня).
Shadow Man

8

Просто обновление ответа Кристофера.

Поскольку версия 2.6.0

<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-jsr310</artifactId>
    <version>2.9.0</version>
</dependency>

Используйте JavaTimeModule вместо JSR310Module (устарело).

@Provider
public class ObjectMapperContextResolver implements ContextResolver<ObjectMapper> {  
    private final ObjectMapper MAPPER;

    public ObjectMapperContextResolver() {
        MAPPER = new ObjectMapper();
        MAPPER.registerModule(new JavaTimeModule());
        MAPPER.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
    }

    @Override
    public ObjectMapper getContext(Class<?> type) {
        return MAPPER;
    }  
}

Согласно документации , новый JavaTimeModule использует те же стандартные настройки по умолчанию для сериализации, которая НЕ использует идентификаторы часовых поясов, а вместо этого использует только смещения часовых поясов, совместимые с ISO-8601.

Поведение можно изменить с помощью SerializationFeature.WRITE_DATES_WITH_ZONE_ID


Это помогло мне. В моем случае мне нужно было добавить MAPPER.registerModule(new JavaTimeModule());строку. Это позволило мне отформатировать объекты LocalDate в формате «2020-02-20». MAPPER.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);Линия мне не нужна, то , что я искал
Куга

6

Следующая аннотация сработала для меня.

Никаких дополнительных зависимостей не требуется.

    @JsonProperty("created_at")
    @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX")
    @JsonDeserialize(using = LocalDateTimeDeserializer.class)
    @JsonSerialize(using = LocalDateTimeSerializer.class)
    private LocalDateTime createdAt;

5
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@JsonSerialize(using = LocalDateTimeSerializer.class)
@JsonDeserialize(using = LocalDateTimeDeserializer.class)
private LocalDateTime createdDate;

4

https://stackoverflow.com/a/53251526/1282532 - самый простой способ сериализации / десериализации свойства. У меня есть два опасения относительно этого подхода - до некоторой степени нарушение принципа DRY и высокая степень связи между pojo и mapper.

public class Trade {
    @JsonFormat(pattern = "yyyyMMdd")
    @JsonDeserialize(using = LocalDateDeserializer.class)
    @JsonSerialize(using = LocalDateSerializer.class)
    private LocalDate tradeDate;
    @JsonFormat(pattern = "yyyyMMdd")
    @JsonDeserialize(using = LocalDateDeserializer.class)
    @JsonSerialize(using = LocalDateSerializer.class)
    private LocalDate maturityDate;
    @JsonFormat(pattern = "yyyyMMdd")
    @JsonDeserialize(using = LocalDateDeserializer.class)
    @JsonSerialize(using = LocalDateSerializer.class)
    private LocalDate entryDate;
}

Если у вас есть POJO с несколькими полями LocalDate, лучше настроить mapper вместо POJO. Это может быть так же просто, как https://stackoverflow.com/a/35062824/1282532 если вы используете значения ISO-8601 («2019-01-31»).

Если вам нужно обрабатывать нестандартный формат, код будет таким:

ObjectMapper mapper = new ObjectMapper();
JavaTimeModule javaTimeModule = new JavaTimeModule();
javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern("yyyyMMdd")));
javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern("yyyyMMdd")));
mapper.registerModule(javaTimeModule);

Логика написана только один раз, ее можно повторно использовать для нескольких POJO


3

Самый простой и самый короткий:

@JsonFormat(pattern = "yyyy-MM-dd")
private LocalDate localDate;

@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime localDateTime;

при загрузке Spring> = 2.2+ зависимость не требуется


1

Начиная с 2020 и Jackson 2.10.1 нет необходимости в каком-либо специальном коде, просто нужно сказать Джексону, что вы хотите:

ObjectMapper objectMapper = new ObjectMapper();

// Register module that knows how to serialize java.time objects
// Provided by jackson-datatype-jsr310
objectMapper.registerModule(new JavaTimeModule());

// Ask Jackson to serialize dates as String (ISO-8601 by default)
objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);

Об этом уже упоминалось в этом ответе , я добавляю модульный тест, проверяющий функциональность:

import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import lombok.Data;
import org.junit.jupiter.api.Test;

import java.time.LocalDate;

import static org.junit.jupiter.api.Assertions.assertEquals;

public class LocalDateSerializationTest {

    @Data
    static class TestBean {
        // Accept default ISO-8601 format
        LocalDate birthDate;
        // Use custom format
        @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "dd/MM/yyyy")
        LocalDate birthDateWithCustomFormat;
    }

    @Test
    void serializeDeserializeTest() throws JsonProcessingException {
        ObjectMapper objectMapper = new ObjectMapper();

        // Register module that knows how to serialize java.time objects
        objectMapper.registerModule(new JavaTimeModule());

        // Ask Jackson to serialize dates as String (ISO-8601 by default)
        objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);

        // The JSON string after serialization
        String json = "{\"birthDate\":\"2000-01-02\",\"birthDateWithCustomFormat\":\"03/02/2001\"}";

        // The object after deserialization
        TestBean object = new TestBean();
        object.setBirthDate(LocalDate.of(2000, 1, 2));
        object.setBirthDateWithCustomFormat(LocalDate.of(2001, 2, 3));

        // Assert serialization
        assertEquals(json, objectMapper.writeValueAsString(object));

        // Assert deserialization
        assertEquals(object, objectMapper.readValue(json, TestBean.class));
    }
}

TestBean использует Lombok для создания шаблона для bean-компонента.


0

В классе конфигурации определяют LocalDateSerializer и LocalDateDeserializer класс и зарегистрировать их в ObjectMapper через JavaTimeModule , как показано ниже:

@Configuration
public class AppConfig
{
@Bean
    public ObjectMapper objectMapper()
    {
        ObjectMapper mapper = new ObjectMapper();
        mapper.setSerializationInclusion(Include.NON_EMPTY);
        //other mapper configs
        // Customize de-serialization


        JavaTimeModule javaTimeModule = new JavaTimeModule();
        javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer());
        javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer());
        mapper.registerModule(javaTimeModule);

        return mapper;
    }

    public class LocalDateSerializer extends JsonSerializer<LocalDate> {
        @Override
        public void serialize(LocalDate value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
            gen.writeString(value.format(Constant.DATE_TIME_FORMATTER));
        }
    }

    public class LocalDateDeserializer extends JsonDeserializer<LocalDate> {

        @Override
        public LocalDate deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
            return LocalDate.parse(p.getValueAsString(), Constant.DATE_TIME_FORMATTER);
        }
    }
}

0

Если ваш запрос содержит такой объект:

{
    "year": 1900,
    "month": 1,
    "day": 20
}

Тогда вы можете использовать:

data class DateObject(
    val day: Int,
    val month: Int,
    val year: Int
)
class LocalDateConverter : StdConverter<DateObject, LocalDate>() {
    override fun convert(value: DateObject): LocalDate {
        return value.run { LocalDate.of(year, month, day) }
    }
}

Над полем:

@JsonDeserialize(converter = LocalDateConverter::class)
val dateOfBirth: LocalDate

Код находится на Kotlin, но, конечно, это сработает и для Java.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.