Format Java 8 LocalDate Jackson

152

Dla java.util.Date kiedy to zrobię

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

następnie w żądaniu JSON, kiedy wysyłam

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

to działa.

Jak mam to zrobić dla pola LocalDate Java 8 ?

Próbowałem mieć

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

To nie zadziałało.

Czy ktoś może mi powiedzieć, jak należy to zrobić.

Poniżej znajdują się zależności

<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>
UKŁUCIE
źródło

Odpowiedzi:

112

Nigdy nie udało mi się to prosto za pomocą adnotacji. Aby dostać się do pracy, stworzyłem ContextResolverdla ObjectMapper, potem dodał JSR310Module( aktualizacja: teraz jest JavaTimeModulezamiast ), wraz z jeszcze jednym zastrzeżeniem, co było trzeba ustawić write-data-as-datownik na false. Zobacz więcej w dokumentacji modułu JSR310 . Oto przykład tego, czego użyłem.

Zależność

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

Uwaga: Jednym z problemów, z którymi się spotkałem, jest to, że jackson-annotationwersja ściągnięta przez inną zależność, używana wersja 2.3.2, która anulowała 2.4 wymagane przez jsr310. To, co się stało, to dostałem NoClassDefFound dla ObjectIdResolver, czyli klasę 2.4. Musiałem więc po prostu wyrównać dołączone wersje zależności

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;
    }  
}

Klasa zasobów

@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;
    }
}

Test

curl -v http://localhost:8080/api/person
Wynik: {"birthDate":"2015-03-01"}

curl -v -POST -H "Content-Type:application/json" -d "{\"birthDate\":\"2015-03-01\"}" http://localhost:8080/api/person
Wynik: 2015-03-01


Zobacz także tutaj rozwiązanie JAXB.

AKTUALIZACJA

Od JSR310Modulewersji 2.7 programu Jackson jest przestarzały. Zamiast tego powinieneś zarejestrować moduł JavaTimeModule. To wciąż ta sama zależność.

Paul Samsotha
źródło
1
Cześć Peeskillet, pole birthDate, jest generowane jako "birthDate": {"year": 0, "month": "Month", "dayOfMonth": 0, "dayOfWeek": "DayOfWeek", "era": {" value ": 0}," dayOfYear ": 0," leapYear ": false," monthValue ": 0," chronology ": {" id ":" "," calendarType ":" "}} jak mogę to zrobić po prostu jako „birthDate” ???
JAB
Sprawdź, czy wywoływana jest metoda ContextResolver. Dodaj instrukcję print w getContextmetodzie. Jeśli ta metoda jest wywoływana, nie widzę powodu, dla którego to nie działa. Jeśli nie jest wywoływana, może to być coś, co należy naprawić w konfiguracji aplikacji. W tym celu musiałbym zobaczyć więcej niż to, co zapewniłeś. Podobnie jak wersja Resteasy, zależności, konfiguracja aplikacji web.xml lub podklasa aplikacji. Zasadniczo wystarczy, aby odtworzyć problem
Paul Samsotha
ContextResolver nie nazywa się Peeskillet. Ponownie rejestruję go w web.xml jako <context-param> <param-name> resteasy.resources </param-name> <param-value> com.bac.ObjectMapperContextResolver </param-value> </context-param> zaktualizowane pytanie dotyczące zależności, których używam
JAB
Wydaje się, że problemem jest Swagger. Powiedziałbym, żeby go wyłączyć, ale widząc z tego pytania , jest problem, który został zgłoszony, z konfliktem między ObjectMapper Swaggera a próbą użycia własnego. Możesz spróbować wyłączyć ich, a w ContextResolver ustawić wszystkie konfiguracje tak, ObjectMapperjak robi to swagger (możesz zobaczyć link w pytaniu). Nie wiem, bo nie pracuję zbytnio z dumą. Ale myślę, że głównym problemem jest swagger, dlaczego nie wywołuje się contextresolvera.
Paul Samsotha
Po dalszych badań, Adnotacja wykonuje pracę. Nawet jeśli musimy użyć Swagger ObjectMapper, to już skonfiguruj dla nas znaczniki czasu jako fałszywe. Więc to powinno działać. Aby uzyskać lepszą pomoc, zdecydowanie sugeruję podanie MCVE, które pokazuje problem.
Paul Samsotha
98

@JsonSerialize i @JsonDeserialize działały dobrze dla mnie. Eliminują konieczność importowania dodatkowego modułu jsr310:

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

Deserializator:

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));
    }

}

Serializator:

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));
    }
}
Christopher Yang
źródło
2
To dla mnie najlepsza odpowiedź. Dzięki!
Romeo Jr Maranan
8
Te zajęcia są zawarte w jackson-datatype-jsr310. Nie ma potrzeby ręcznego definiowania ich w projekcie.
NeuroXc
1
To rozwiązanie zadziałało dla mnie, używając serializatorów w jackson-datatype-jsr310.
Dave
2
To powinna być nowa najlepsza odpowiedź.
Parametr lekarza
2
Jeśli używasz serializatorów i deserializatorów w jackson-datatype-jsr310, lepiej dodaj @JsonFormat (shape = JsonFormat.Shape.STRING) do swojego pola. Bez tego formatu wartość zostanie serializowana jako [rok, miesiąc, dzień], chociaż deserializacja będzie działać.
Jian Chen
79
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);

dla mnie działa dobrze.

欧阳 世雄
źródło
2
new com.fasterxml.jackson.datatype.jsr310.JSR310Module()dla wersji 2.5.4 Jackson. Klasa JavaTimeModule nie istnieje w tej wersji.
user3774109
Ta odpowiedź działa również w przypadku LocalDateTime(jackson 2.9.5). Wymagana jest 1 dodatkowa zależność, więc moja kompilacja.sbt wygląda następująco:"com.fasterxml.jackson.module" %% "jackson-module-scala" % "2.9.5", "com.fasterxml.jackson.datatype" % "jackson-datatype-jsr310" % "2.9.5"
ruhong
2
To powinno mieć znacznie więcej głosów. Proste i skuteczne.
mkasberg
Działało idealnie! Dziękuję Ci.
MAD-HAX
1
To wskazało mi właściwy kierunek. Dziękuję! Dodam, że w spring-boot wystarczy dodać do application.properties: spring.jackson.serialization.write-dates-as-timestamps = false
Joost Lambregts
50

W aplikacji internetowej Spring Boot, z Jacksonem i JSR 310 w wersji „2.8.5”

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

Do @JsonFormatdzieła:

import com.fasterxml.jackson.annotation.JsonFormat;

@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd")
private LocalDate birthDate;
Tsolak Barseghyan
źródło
2
Czy to działa w przypadku deserializacji? czy tylko serializacja? Brak sukcesu z deserializacją
rewolf
7
Musiałem wyraźnie zadeklarować deserializator@JsonDeserialize(using= LocalDateDeserializer.class)
rewolf
1
zdecydowanie najprostszy!
Antonio
@JsonFormattylko do zmiany formatu danych wyjściowych. stackoverflow.com/a/53251526/816759 działa idealne z @JsonFormat, @JsonDeserialize,@JsonSerialize
Baha
1
W Spring Boot, po dodaniu zależności JSR310, wszystko, co musisz zrobić, to dodać spring.jackson.serialization.write-dates-as-timestamps=falsedo swojego application.properties, a formatuje go yyyy-MM-ddautomatycznie. Nie ma potrzeby@JsonFormat
esfandia
33

Najprostszym rozwiązaniem (które obsługuje również deserializację i serializację) jest

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;

Podczas korzystania z następujących zależności w projekcie.

Maven

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

Nie jest wymagana żadna dodatkowa implementacja ContextResolver, Serializer lub Deserializer.

Paul Wasilewski
źródło
Genialne, daleko i najłatwiejsze. Do Twojej wiadomości dla każdego z wieloma zależnościami, musiałem zaktualizować kilka innych bibliotek, które zawierały adnotacje Jacksona.
brt
Ta odpowiedź jest najbliższa, aby rozwiązać mój problem. Serializacja działa, ale deserializacja kończy się niepowodzeniem z powodu wzorca, którego użyłem z @JsonFormat, jak sądzę (@JsonFormat (shape = JsonFormat.Shape.STRING, pattern = "dd-MM-yyyy_HH: mm: SS").
fsakiyama
Jeśli masz nieudaną deserializację, najprawdopodobniej ObjectMappernie została JavaTimeModulezarejestrowana. Jeśli Twoja instancja ObjectMapper jest dostarczana z frameworka spring / MessageConverter. Zrobili trochę magii, żeby ich połączyć. W innym przypadku należy registerModulewłączyć LocalDateDeserializerdomyślnie dla wszystkich „LocalDate” w POJO
Dennis C
20

Ponieważ LocalDateSerializerdomyślnie zamienia go na „[rok, miesiąc, dzień]” (tablica json) zamiast „rok-miesiąc-dzień” (ciąg json), a ponieważ nie chcę wymagać żadnej specjalnej ObjectMapperkonfiguracji (możesz zrobić LocalDateSerializergenerowanie ciągów, jeśli wyłączysz, SerializationFeature.WRITE_DATES_AS_TIMESTAMPSale wymaga to dodatkowej konfiguracji ObjectMapper), używam:

import:

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

kod:

// 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;

A teraz mogę po prostu używać new ObjectMapper()do czytania i zapisywania moich obiektów bez specjalnej konfiguracji.

Człowiek cień
źródło
3
Jedną rzeczą, którą chciałbym dodać, jest przekazanie daty, ponieważ "2018-12-07"zamiast "2018-12-7"innego pojawi się błąd.
Kid101
1
Prawidłowo, działa z yyyy-MM-ddformatem (2-cyfrowy miesiąc i dzień), a nie yyyy-M-d(1-cyfrowy miesiąc lub dzień).
Shadow Man,
11
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@JsonSerialize(using = LocalDateTimeSerializer.class)
@JsonDeserialize(using = LocalDateTimeDeserializer.class)
private LocalDateTime createdDate;
slisnychyi
źródło
Działa lepiej niż jakiekolwiek inne rozwiązanie, dzięki.
Rouven H.
To działa. Wymaga również zależności dla jackson-datatype-jsr310
W0lfw00ds
8

Tylko aktualizacja odpowiedzi Christophera.

Od wersji 2.6.0

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

Użyj JavaTimeModule zamiast JSR310Module (przestarzałe).

@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;
    }  
}

Zgodnie z dokumentacją , nowy moduł JavaTimeModule używa tych samych standardowych ustawień do domyślnej serializacji, która NIE używa identyfikatorów stref czasowych, a zamiast tego używa tylko przesunięć stref czasowych zgodnych z ISO-8601.

Zachowanie można zmienić za pomocą SerializationFeature.WRITE_DATES_WITH_ZONE_ID

bdzzaid
źródło
To mi pomogło. W moim przypadku musiałem dodać MAPPER.registerModule(new JavaTimeModule());linię. Pozwoliło mi to sformatować obiekty LocalDate w formacie „2020-02-20”. Nie potrzebowałem MAPPER.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);linii, do tego, czego szukałem
Cuga
8

Poniższa adnotacja działała dobrze dla mnie.

Nie są potrzebne żadne dodatkowe zależności.

    @JsonProperty("created_at")
    @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX")
    @JsonDeserialize(using = LocalDateTimeDeserializer.class)
    @JsonSerialize(using = LocalDateTimeSerializer.class)
    private LocalDateTime createdAt;
KayV
źródło
7

Jak dotąd najprostsze i najkrótsze:

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

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

żadna zależność nie jest wymagana w przypadku Spring boot> = 2.2+

Anil Bhaskar
źródło
4

https://stackoverflow.com/a/53251526/1282532 to najprostszy sposób serializacji / deserializacji właściwości. Mam dwa zastrzeżenia co do tego podejścia - do pewnego momentu naruszenie zasady DRY i duże sprzężenie między pojo i 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;
}

W przypadku, gdy masz POJO z wieloma polami LocalDate, lepiej jest skonfigurować program mapujący zamiast POJO. Może to być tak proste, jak https://stackoverflow.com/a/35062824/1282532, jeśli używasz wartości ISO-8601 („2019-01-31”)

W przypadku konieczności obsługi formatu niestandardowego kod będzie wyglądał następująco:

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

Logika jest napisana tylko raz, może być ponownie wykorzystana w wielu POJO

Pavel
źródło
2

Od 2020 roku i Jacksona 2.10.1 nie ma potrzeby stosowania żadnego specjalnego kodu, wystarczy powiedzieć Jacksonowi, czego chcesz:

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

Wspomniano już o tym w tej odpowiedzi , dodaję test jednostkowy weryfikujący funkcjonalność:

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 używa Lombok do generowania schematu kotłowego dla ziarna.

Bogdan Calmac
źródło
1

W klasie konfiguracyjnej zdefiniuj klasę LocalDateSerializer i LocalDateDeserializer i zarejestruj je w ObjectMapper poprzez JavaTimeModule jak poniżej:

@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);
        }
    }
}
nanosoft
źródło
0

Jeśli Twoje żądanie zawiera taki obiekt:

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

Następnie możesz użyć:

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

Nad polem:

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

Kod jest w Kotlinie, ale to oczywiście zadziałaby również dla Javy.

mowwwalker
źródło