Nigdy nie udało mi się to prosto za pomocą adnotacji. Aby dostać się do pracy, stworzyłem ContextResolver
dla ObjectMapper
, potem dodał JSR310Module
( aktualizacja: teraz jest JavaTimeModule
zamiast ), 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-annotation
wersja ś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();
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 JSR310Module
wersji 2.7 programu Jackson jest przestarzały. Zamiast tego powinieneś zarejestrować moduł JavaTimeModule
. To wciąż ta sama zależność.
getContext
metodzie. 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ć problemObjectMapper
jak 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.@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)); } }
źródło
jackson-datatype-jsr310
. Nie ma potrzeby ręcznego definiowania ich w projekcie.jackson-datatype-jsr310
.ObjectMapper mapper = new ObjectMapper(); mapper.registerModule(new JavaTimeModule()); mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
dla mnie działa dobrze.
źródło
new com.fasterxml.jackson.datatype.jsr310.JSR310Module()
dla wersji 2.5.4 Jackson. Klasa JavaTimeModule nie istnieje w tej wersji.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"
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
@JsonFormat
dzieła:import com.fasterxml.jackson.annotation.JsonFormat; @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd") private LocalDate birthDate;
źródło
@JsonDeserialize(using= LocalDateDeserializer.class)
@JsonFormat
tylko do zmiany formatu danych wyjściowych. stackoverflow.com/a/53251526/816759 działa idealne z@JsonFormat
,@JsonDeserialize
,@JsonSerialize
spring.jackson.serialization.write-dates-as-timestamps=false
do swojegoapplication.properties
, a formatuje goyyyy-MM-dd
automatycznie. Nie ma potrzeby@JsonFormat
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.
źródło
ObjectMapper
nie zostałaJavaTimeModule
zarejestrowana. Jeśli Twoja instancja ObjectMapper jest dostarczana z frameworka spring / MessageConverter. Zrobili trochę magii, żeby ich połączyć. W innym przypadku należyregisterModule
włączyćLocalDateDeserializer
domyślnie dla wszystkich „LocalDate” w POJOPonieważ
LocalDateSerializer
domyślnie zamienia go na „[rok, miesiąc, dzień]” (tablica json) zamiast „rok-miesiąc-dzień” (ciąg json), a ponieważ nie chcę wymagać żadnej specjalnejObjectMapper
konfiguracji (możesz zrobićLocalDateSerializer
generowanie ciągów, jeśli wyłączysz,SerializationFeature.WRITE_DATES_AS_TIMESTAMPS
ale wymaga to dodatkowej konfiguracjiObjectMapper
), 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.źródło
"2018-12-07"
zamiast"2018-12-7"
innego pojawi się błąd.yyyy-MM-dd
formatem (2-cyfrowy miesiąc i dzień), a nieyyyy-M-d
(1-cyfrowy miesiąc lub dzień).@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") @JsonSerialize(using = LocalDateTimeSerializer.class) @JsonDeserialize(using = LocalDateTimeDeserializer.class) private LocalDateTime createdDate;
źródło
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
źródło
MAPPER.registerModule(new JavaTimeModule());
linię. Pozwoliło mi to sformatować obiekty LocalDate w formacie „2020-02-20”. Nie potrzebowałemMAPPER.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
linii, do tego, czego szukałemPoniż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;
źródło
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+
źródło
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
źródło
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.
źródło
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); } } }
źródło
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.
źródło