简体   繁体   English

Java 8 LocalDate Jackson 格式

[英]Java 8 LocalDate Jackson format

For java.util.Date when I do对于java.util.Date当我做

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

then in JSON request when I send然后在我发送时在 JSON 请求中

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

it works.有用。

How should I do this for Java 8's LocalDate field??我应该如何为Java 8 的 LocalDate字段执行此操作?

I tried having我试着有

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

It didn't work.它没有用。

Can someone please let me know what's the right way to do this..有人可以让我知道这样做的正确方法是什么..

Below are dependencies以下是依赖项

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

I was never able to get this to work simple using annotations.我从来没有能够使用注释让它简单地工作。 To get it to work, I created a ContextResolver for ObjectMapper , then I added the JSR310Module ( update: now it is JavaTimeModule instead ), along with one more caveat, which was the need to set write-date-as-timestamp to false.为了得到它的工作,我创建了一个ContextResolverObjectMapper ,然后我添加了JSR310Module更新:现在是JavaTimeModule代替),多一个警告沿,这是需要一套写日期的时间戳为false。 See more at the documentation for the JSR310 module .在 JSR310 模块的文档中查看更多信息。 Here's an example of what I used.这是我使用的示例。

Dependency依赖

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

Note: One problem I faced with this is that the jackson-annotation version pulled in by another dependency, used version 2.3.2, which cancelled out the 2.4 required by the jsr310 .注意:我遇到的一个问题是jackson-annotation版本由另一个依赖项jsr310 ,使用版本 2.3.2,它取消了jsr310所需的 2.4。 What happened was I got a NoClassDefFound for ObjectIdResolver , which is a 2.4 class.发生的事情是我为ObjectIdResolver获得了一个 NoClassDefFound ,这是一个 2.4 类。 So I just needed to line up the included dependency versions所以我只需要排列包含的依赖版本

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

Resource class资源类

@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
Result: {"birthDate":"2015-03-01"}结果: {"birthDate":"2015-03-01"}

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


See also here for JAXB solution.另请参阅此处了解 JAXB 解决方案。

UPDATE更新

The JSR310Module is deprecated as of version 2.7 of Jackson. JSR310Module从 Jackson 2.7 版JSR310Module被弃用。 Instead, you should register the module JavaTimeModule .相反,您应该注册模块JavaTimeModule It is still the same dependency.它仍然是相同的依赖关系。

@JsonSerialize and @JsonDeserialize worked fine for me. @JsonSerialize 和 @JsonDeserialize 对我来说效果很好。 They eliminate the need to import the additional jsr310 module:它们无需导入额外的 jsr310 模块:

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

Deserializer:解串器:

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));
    }
}
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);

In Spring Boot web app, with Jackson and JSR 310 version "2.8.5"在 Spring Boot Web 应用程序中,使用JacksonJSR 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"

The @JsonFormat works: @JsonFormat有效:

import com.fasterxml.jackson.annotation.JsonFormat;

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

The simplest solution (which supports deserialization and serialization as well) is最简单的解决方案(也支持反序列化和序列化)是

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;

While using the following dependencies in your project.在您的项目中使用以下依赖项时。

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"

No additional implementation of a ContextResolver, Serializer or Deserializer is required.不需要额外的 ContextResolver、Serializer 或 Deserializer 实现。

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

Since LocalDateSerializer turns it into "[year,month,day]" (a json array) rather than "year-month-day" (a json string) by default, and since I don't want to require any special ObjectMapper setup (you can make LocalDateSerializer generate strings if you disable SerializationFeature.WRITE_DATES_AS_TIMESTAMPS but that requires additional setup to your ObjectMapper ), I use the following:由于LocalDateSerializer将其转换为“[year,month,day]”(一个 json 数组)而不是“year-month-day”(一个 json 字符串),并且因为我不想需要任何特殊的ObjectMapper设置(如果禁用SerializationFeature.WRITE_DATES_AS_TIMESTAMPS ,则可以使LocalDateSerializer生成字符串,但这需要对ObjectMapper额外设置),我使用以下内容:

imports:进口:

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

code:代码:

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

And now I can just use new ObjectMapper() to read and write my objects without any special setup.现在我可以使用new ObjectMapper()来读取和写入我的对象而无需任何特殊设置。

The following annotation worked fine for me.以下注释对我来说效果很好。

No extra dependencies needed.不需要额外的依赖。

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

Simplest and shortest so far:迄今为止最简单和最短的:

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

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

no dependency required with Spring boot >= 2.2+ Spring boot >= 2.2+ 不需要依赖项

Just an update of Christopher answer.只是克里斯托弗回答的更新。

Since the version 2.6.02.6.0版本

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

Use the JavaTimeModule instead of JSR310Module (deprecated).使用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;
    }  
}

According to the documentation , the new JavaTimeModule uses same standard settings to default to serialization that does NOT use Timezone Ids, and instead only uses ISO-8601 compliant Timezone offsets.根据文档,新的 JavaTimeModule 使用相同的标准设置默认为不使用时区 ID 的序列化,而是仅使用符合 ISO-8601 的时区偏移量。

Behavior may be changed using SerializationFeature.WRITE_DATES_WITH_ZONE_ID可以使用SerializationFeature.WRITE_DATES_WITH_ZONE_ID更改行为

https://stackoverflow.com/a/53251526/1282532 is the simplest way to serialize/deserialize property. https://stackoverflow.com/a/53251526/1282532是序列化/反序列化属性的最简单方法。 I have two concerns regarding this approach - up to some point violation of DRY principle and high coupling between pojo and mapper.我对这种方法有两个担忧——在某种程度上违反了 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;
}

In case you have POJO with multiple LocalDate fields it's better to configure mapper instead of POJO.如果您的 POJO 具有多个 LocalDate 字段,最好配置映射器而不是 POJO。 It can be as simple as https://stackoverflow.com/a/35062824/1282532 if you are using ISO-8601 values ("2019-01-31")如果您使用的是 ISO-8601 值(“2019-01-31”),它可以像https://stackoverflow.com/a/35062824/1282532一样简单

In case you need to handle custom format the code will be like this:如果您需要处理自定义格式,代码将如下所示:

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

The logic is written just once, it can be reused for multiple POJO逻辑只写一次,可以复用多个POJO

As of 2020 and Jackson 2.10.1 there's no need for any special code, it's just a matter of telling Jackson what you want:从 2020 年和 Jackson 2.10.1 开始,不需要任何特殊代码,只需告诉 Jackson 你想要什么:

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

This has already been mentioned in this answer , I'm adding a unit test verifying the functionality:这已经在这个答案中提到了,我正在添加一个单元测试来验证功能:

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 uses Lombok to generate the boilerplate for the bean. TestBean 使用 Lombok 为 bean 生成样板。

In configuration class define LocalDateSerializer and LocalDateDeserializer class and register them to ObjectMapper via JavaTimeModule like below:在配置类中定义LocalDateSerializerLocalDateDeserializer类,并通过JavaTimeModule将它们注册到ObjectMapper ,如下所示:

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

A bit easier For Spring:对于 Spring 来说更容易一些:

///...

@Configuration
public class ApplicationCtxBeans {
//....
    @Bean
    public ObjectMapper objectMapper() {
        ObjectMapper MAPPER = new ObjectMapper(); 
        MAPPER.registerModule(new JavaTimeModule()); // to handle LocalDateTime etc
        return MAPPER;
    }
//...
}

Usage:用法:

@Service
public class SomeService {
    
//...
    @Autowired
    ObjectMapper jsonMapper;
//...
  JsonNode node = jsonMapper.readTree(
    jsonMapper.writeValueAsString(instance_Of_Class_With_LocalDate_Fields)
  );
//...
}

If your request contains an object like this:如果您的请求包含这样的对象:

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

Then you can use:然后你可以使用:

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

Above the field:场上:

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

The code is in Kotlin but this would work for Java too of course.代码在 Kotlin 中,但这当然也适用于 Java。

With spring boot 2.3.9.RELEASE,I just registered java time module with no explicit annotation in POJO class having LocalDate field & it worked.使用 spring boot 2.3.9.RELEASE,我刚刚在具有 LocalDate 字段的 POJO 类中注册了没有显式注释的 java 时间模块,它工作正常。

var objectMapper = new ObjectMapper();
objectMapper.registerModule(new JavaTimeModule());

annotation in Pojo without using additional dependencies Pojo 中的注释而不使用额外的依赖项

@DateTimeFormat (pattern = "yyyy/MM/dd", iso = DateTimeFormat.ISO.DATE)
private LocalDate enddate;

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM