Формат Java 8 LocalDate Jackson


138

Для java.util.Дати, коли я це роблю

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

то в запиті JSON, коли я надсилаю

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

це працює.

Як мені це зробити для поля LocalDate Java 8 ??

Я намагався мати

@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

Мені ніколи не вдалося змусити це працювати просто, використовуючи анотації. Щоб змусити його працювати, я створив ContextResolverдля ObjectMapper, потім додав JSR310Module( оновлення: тепер це JavaTimeModuleзамість ) разом із ще одним застереженням, для якого потрібно було встановити позначення дати-дати-як-часу на помилкове. Детальніше дивіться в документації до модуля 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": {"рік": 0, "місяць": "Місяць", "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 (ви можете побачити посилання у питанні). Я не знаю, як багато не працюю з хитромудрістю. Але я вважаю, що балаканина є основною проблемою, чому контекстне вирішення не викликається.
Пол Самсота

Після подальшого тестування Анотація дійсно працює. Навіть якщо нам доведеться використовувати 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));
    }

}

Серіалізатор:

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
Це найкраща відповідь для мене. Дякую!
Ромео-молодший Маранан

7
Ці заняття включені в jackson-datatype-jsr310. Не потрібно вручну визначати їх у своєму проекті.
NeuroXc

1
Це рішення працювало для мене, використовуючи серіалізатори в jackson-datatype-jsr310.
Дейв

2
Це має бути нова найкраща відповідь.
Параметр лікаря

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 не існує в цій версії.
користувач3774109

Ця відповідь також працює для LocalDateTime(Джексон 2.9.5). Потрібна додаткова залежність, тому мій build.sbt виглядає так:"com.fasterxml.jackson.module" %% "jackson-module-scala" % "2.9.5", "com.fasterxml.jackson.datatype" % "jackson-datatype-jsr310" % "2.9.5"
ruhong

2
Це повинно мати більше голосів. Простий і ефективний.
мкасберг

Працювали чудово! Дякую.
MAD-HAX

Це вказувало мені в правильному напрямку, Дякую! Я хотів би додати , що у весняно-черевику все , що вам потрібно зробити , це додати наступне application.properties: spring.jackson.serialization.write-дат-як-часові мітки = брехня
Joost Lambregts

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
Чи працює це для десеріалізації? чи лише серіалізація? Не маючи успіху з десеріалізацією
перемогти

7
Мені довелося явно заявити про десеріалізатор@JsonDeserialize(using= LocalDateDeserializer.class)
перемогу

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>

Градле

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

Ніяка додаткова реалізація ContextResolver, серіалізатор або десеріалізатор не потрібна.


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

Ця відповідь є найближчим, який я отримав, щоб виправити свою проблему. Серіалізація працює, але десеріалізація провалюється через шаблон, який я використовував у @JsonFormat, я думаю (@JsonFormat (форма = 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налаштувань (ви можете зробити LocalDateSerializerгенерувати рядки, якщо ви відключите, 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

1
Правильно, він працює у yyyy-MM-ddформаті (двозначний місяць і день), а не 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 використовує ті самі стандартні параметри за замовчуванням для серіалізації, які НЕ використовують ідентифікатори Timezone, а натомість використовує лише компенсації часового поясу, сумісні з 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 для створення котлована для квасолі.


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

Код є в Котліні, але це, звичайно, буде працювати і для Java.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.