ทำให้เป็นอันดับ / deserialize java 8 java.time ด้วย mapper แจ็คสัน JSON


221

ฉันจะใช้ mapper Jackson JSON กับ Java 8 LocalDateTime ได้อย่างไร

org.codehaus.jackson.map.JsonMappingException: ไม่สามารถยกตัวอย่างค่าของประเภท [ประเภทที่เรียบง่าย, คลาส java.time.LocalDateTime] จาก JSON String; ไม่มีตัวสร้างสตริงเดี่ยว / วิธีการของโรงงาน (ผ่านสายอ้างอิง: MyDTO ["field1"] -> SubDTO ["date"])


4
คุณแน่ใจหรือไม่ว่าต้องการแมป LocalDateTime กับ JSon เท่าที่ฉันรู้ JSon ไม่มีรูปแบบสำหรับวันที่แม้ว่า JavaScript ใช้ ISO-8601 ปัญหาคือ LocalDateTime ไม่มีเขตเวลา ... ดังนั้นหากคุณใช้ JSON เป็นสื่อในการส่งข้อมูลวันที่ / เวลาคุณอาจประสบปัญหาหากลูกค้าจะตีความว่าเขตเวลาขาดเป็นค่าเริ่มต้น UTC (หรือของมันเอง เขตเวลา). ถ้านั่นคือสิ่งที่คุณต้องการจะทำแน่นอนมันดี แต่เพียงตรวจสอบว่าคุณได้พิจารณาใช้ ZonedDateTime แทน
arcuri82

คำตอบ:


276

ไม่จำเป็นต้องใช้ serializers / deserializers ที่กำหนดเองที่นี่ ใช้โมดูล datetime ของ jackson-modules-java8 :

โมดูลประเภทข้อมูลที่จะทำให้แจ็กสันรู้จักชนิดข้อมูล Java 8 Date & Time API (JSR-310)

โมดูลนี้เพิ่มการสนับสนุนสำหรับชั้นเรียนค่อนข้างน้อย:

  • ระยะเวลา
  • ทันที
  • LocalDateTime
  • LOCALDATE
  • เวลาท้องถิ่น
  • MONTHDAY
  • OffsetDateTime
  • OffsetTime
  • ระยะเวลา
  • ปี
  • yearMonth
  • ZonedDateTime
  • ZoneId
  • ZoneOffset

59
โอ้ใช่! ผมว่านี้ไม่ได้ทำงานเพราะผมไม่ทราบว่าจะต้องทำอย่างใดอย่างหนึ่งของการปรับแต่งเหล่านี้ไปยังวัตถุ mapper นี้ หรือregisterModule(new JSR310Module()) findAndRegisterModules()ดู github.com/FasterXML/jackson-datatype-jsr310และนี่คือวิธีปรับแต่งออบเจกต์ mapper ของคุณถ้าคุณใช้ Spring framework: stackoverflow.com/questions/7854030/ …
Alexander Taylor

10
ไม่ทำงานกับสิ่งที่ง่ายที่สุดที่ฉันเคยลองOffsetDateTime @Test public void testJacksonOffsetDateTimeDeserializer() throws IOException { ObjectMapper mapper = new ObjectMapper().registerModule(new JavaTimeModule()); String json = "\"2015-10-20T11:00:00-8:30\""; mapper.readValue(json, OffsetDateTime.class); }
Abhijit Sarkar

9
หากคุณใช้กรอบงาน Spring ที่ผ่านมาไม่จำเป็นต้องปรับแต่งตัวเองอีกต่อไปเช่นเดียวกับโมดูลแจ็กสันที่รู้จักกันดีเช่นตอนนี้มีการลงทะเบียนโดยอัตโนมัติหากตรวจพบใน classpath: spring.io/blog/2014/12/02/ …
vorburger

37
JSR310Module ค่อนข้างล้าสมัยไปแล้วใช้ JavaTimeModule แทน
Jacob Eckel

17
@MattBall ฉันจะแนะนำให้เพิ่มที่นอกเหนือจากการใช้แจ็คสันประเภทข้อมูล-jsr310 คุณจะต้องลงทะเบียนกับ JavaTimeModule mapper objectMapper.registerModule(new JavaTimeModule());วัตถุของคุณ: ทั้งในอนุกรมและ deserialization
GrandAdmiral

76

อัปเดต: ออกจากคำตอบนี้ด้วยเหตุผลทางประวัติศาสตร์ แต่ฉันไม่แนะนำ โปรดดูคำตอบที่ยอมรับด้านบน

บอกแจ็กสันให้ทำแผนที่โดยใช้คลาสการทำให้เป็นอนุกรม [de] ที่กำหนดเองของคุณ:

@JsonSerialize(using = LocalDateTimeSerializer.class)
@JsonDeserialize(using = LocalDateTimeDeserializer.class)
private LocalDateTime ignoreUntil;

ให้คลาสที่กำหนดเอง:

public class LocalDateTimeSerializer extends JsonSerializer<LocalDateTime> {
    @Override
    public void serialize(LocalDateTime arg0, JsonGenerator arg1, SerializerProvider arg2) throws IOException {
        arg1.writeString(arg0.toString());
    }
}

public class LocalDateTimeDeserializer extends JsonDeserializer<LocalDateTime> {
    @Override
    public LocalDateTime deserialize(JsonParser arg0, DeserializationContext arg1) throws IOException {
        return LocalDateTime.parse(arg0.getText());
    }
}

ความจริงแบบสุ่ม: ถ้าฉันอยู่เหนือคลาสและไม่ทำให้เป็นแบบคงที่ข้อความแสดงข้อผิดพลาดแปลก: org.springframework.web.HttpMediaTypeNotSupportedException: Content type 'application/json;charset=UTF-8' not supported


ในขณะที่เขียนนี้ความได้เปรียบเพียงอย่างเดียวของคำตอบอื่น ๆ ไม่ได้ขึ้นอยู่กับ FasterXML ไม่ว่าจะเป็นอะไร
Alexander Taylor


4
อ้อเข้าใจแล้ว. รายการปอมโบราณจำนวนมากในโครงการที่ฉันกำลังทำอยู่ ดังนั้นไม่ต้อง org.codehaus อีกต่อไปตอนนี้เป็น com.fasterxml ทั้งหมด ขอบคุณ!
อเล็กซานเดเทย์เลอร์

2
นอกจากนี้ฉันไม่สามารถใช้งานบิวด์อินได้เพราะฉันต้องการส่งฟอร์แมตเตอร์ ISO_DATE_TIME ไม่แน่ใจว่าเป็นไปได้ที่จะทำเช่นนั้นเมื่อใช้ JSR310Module
isaac.hazan

เมื่อใช้กับสปริงฉันต้องสร้างถั่วทั้งสองLocalDateTimeSerializerและLocalDateTimeDeserializer
BenR

53

หากคุณกำลังใช้คลาส ObjectMapper ที่เร็วกว่าโดยค่าเริ่มต้น ObjectMapper ไม่เข้าใจคลาส LocalDateTime ดังนั้นคุณต้องเพิ่มการพึ่งพาอื่นใน gradle / maven ของคุณ:

compile 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.7.3'

ตอนนี้คุณต้องลงทะเบียนการสนับสนุนประเภทข้อมูลที่เสนอโดยห้องสมุดนี้เป็นวัตถุ objectmapper ของคุณซึ่งสามารถทำได้โดยทำตาม:

ObjectMapper objectMapper = new ObjectMapper();
objectMapper.findAndRegisterModules();

ตอนนี้ใน jsonString ของคุณคุณสามารถใส่ฟิลด์ java.LocalDateTime ของคุณได้อย่างง่ายดายดังนี้:

{
    "user_id": 1,
    "score": 9,
    "date_time": "2016-05-28T17:39:44.937"
}

ด้วยการทำสิ่งนี้ไฟล์ Json ของคุณกับการแปลงวัตถุ Java จะทำงานได้ดีคุณสามารถอ่านไฟล์ได้โดยทำตาม:

objectMapper.readValue(jsonString, new TypeReference<List<User>>() {
            });

4
findAndRegisterModules()เป็นชิ้นส่วนที่สำคัญที่ขาดหายไปเมื่อสร้างObjectMapper
Xaero Degreaz

2
แล้ว Instant ล่ะ
powder366

1
ฉันยังเพิ่มบรรทัดนี้: objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
Janet

นี่คือรหัสที่สมบูรณ์ที่จำเป็น: ObjectMapper objectMapper = new ObjectMapper (); objectMapper.findAndRegisterModules (); objectMapper.configure (SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
khush

42

การพึ่งพา Maven นี้จะช่วยแก้ปัญหาของคุณ:

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

สิ่งหนึ่งที่ฉันพยายามคือสำหรับเขตเวลา ZonedDateTime จะเปลี่ยนเป็น GMT ในระหว่างการดีซีเรียลไลเซชัน ปรากฎว่าโดยค่าเริ่มต้นแจ็คสันแทนที่ด้วยหนึ่งจากบริบท .. เพื่อให้โซนหนึ่งจะต้องปิดการใช้งาน 'คุณสมบัติ' นี้

Jackson2ObjectMapperBuilder.json()
    .featuresToDisable(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE)

5
ขอบคุณมากสำหรับ DeserializationFeature ADJUST_DATES_TO_CONTEXT_TIME_ZONE คำใบ้
Andrei Urvantcev

5
นอกจากการปิดการใช้งานDeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONEแล้วฉันยังต้องปิดการใช้งานSerializationFeature.WRITE_DATES_AS_TIMESTAMPSเพื่อให้ทุกอย่างเริ่มทำงานได้ตามที่ควร
แมตต์วัตสัน

16

ผมมีปัญหาที่คล้ายกันในขณะที่ใช้บูตฤดูใบไม้ผลิ ด้วย Spring boot 1.5.1 ปล่อยทั้งหมดที่ฉันต้องทำคือการเพิ่มการพึ่งพา:

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

7

หากคุณกำลังใช้ Jersey คุณต้องเพิ่มการพึ่งพา Maven (jackson-datatype-jsr310) ตามที่คนอื่นแนะนำและลงทะเบียนอินสแตนซ์ mapper วัตถุของคุณเช่น:

@Provider
public class JacksonObjectMapper implements ContextResolver<ObjectMapper> {

  final ObjectMapper defaultObjectMapper;

  public JacksonObjectMapper() {
    defaultObjectMapper = createDefaultMapper();
  }

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

  private static ObjectMapper createDefaultMapper() {
    final ObjectMapper mapper = new ObjectMapper();    
    mapper.registerModule(new JavaTimeModule());
    return mapper;
  }
}

เมื่อลงทะเบียนแจ็คสันในแหล่งข้อมูลของคุณคุณต้องเพิ่มผู้ทำแผนที่เช่น:

final ResourceConfig rc = new ResourceConfig().packages("<your package>");
rc
  .register(JacksonObjectMapper.class)
  .register(JacksonJaxbJsonProvider.class);

6

หากคุณไม่สามารถใช้ไม่jackson-modules-java8ว่าด้วยเหตุผลใดก็ตามคุณสามารถ (ยกเลิก) เป็นอันดับเขตข้อมูลทันทีที่longใช้@JsonIgnoreและ@JsonGetter& @JsonSetter:

public class MyBean {

    private Instant time = Instant.now();

    @JsonIgnore
    public Instant getTime() {
        return this.time;
    }

    public void setTime(Instant time) {
        this.time = time;
    }

    @JsonGetter
    private long getEpochTime() {
        return this.time.toEpochMilli();
    }

    @JsonSetter
    private void setEpochTime(long time) {
        this.time = Instant.ofEpochMilli(time);
    }
}

ตัวอย่าง:

@Test
public void testJsonTime() throws Exception {
    String json = new ObjectMapper().writeValueAsString(new MyBean());
    System.out.println(json);
    MyBean myBean = new ObjectMapper().readValue(json, MyBean.class);
    System.out.println(myBean.getTime());
}

อัตราผลตอบแทน

{"epochTime":1506432517242}
2017-09-26T13:28:37.242Z

นี่เป็นวิธีการที่สะอาดมากและช่วยให้ผู้ใช้ในชั้นเรียนของคุณใช้สิ่งที่พวกเขาต้องการ
Ashhar Hasan

3

ฉันใช้รูปแบบเวลานี้: "{birthDate": "2018-05-24T13:56:13Z}"เพื่อ deserialize จาก json เป็น java.time.Instant (ดูภาพหน้าจอ)

ป้อนคำอธิบายรูปภาพที่นี่


3

นี่เป็นเพียงตัวอย่างวิธีใช้ในการทดสอบหน่วยที่ฉันแฮ็คเพื่อแก้ไขข้อบกพร่องนี้ ส่วนผสมที่สำคัญคือ

  • mapper.registerModule(new JavaTimeModule());
  • Maven พึ่งพา <artifactId>jackson-datatype-jsr310</artifactId>

รหัส:

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.testng.Assert;
import org.testng.annotations.Test;

import java.io.IOException;
import java.io.Serializable;
import java.time.Instant;

class Mumu implements Serializable {
    private Instant from;
    private String text;

    Mumu(Instant from, String text) {
        this.from = from;
        this.text = text;
    }

    public Mumu() {
    }

    public Instant getFrom() {
        return from;
    }

    public String getText() {
        return text;
    }

    @Override
    public String toString() {
        return "Mumu{" +
                "from=" + from +
                ", text='" + text + '\'' +
                '}';
    }
}
public class Scratch {


    @Test
    public void JacksonInstant() throws IOException {
        ObjectMapper mapper = new ObjectMapper();
        mapper.registerModule(new JavaTimeModule());

        Mumu before = new Mumu(Instant.now(), "before");
        String jsonInString = mapper.writeValueAsString(before);


        System.out.println("-- BEFORE --");
        System.out.println(before);
        System.out.println(jsonInString);

        Mumu after = mapper.readValue(jsonInString, Mumu.class);
        System.out.println("-- AFTER --");
        System.out.println(after);

        Assert.assertEquals(after.toString(), before.toString());
    }

}

2

คุณสามารถตั้งค่านี้ในapplication.ymlไฟล์เพื่อแก้ไขเวลาทันทีซึ่งก็คือ Date API ใน java8:

spring.jackson.serialization.write-dates-as-timestamps=false

2

หากคุณใช้ Spring boot และมีปัญหานี้กับ OffsetDateTime คุณต้องใช้ registerModules ตามที่ได้รับคำตอบข้างต้นโดย @greperror (ตอบ 28 พฤษภาคม 16 ที่ 13:04) แต่โปรดทราบว่ามีความแตกต่างอย่างหนึ่ง การพึ่งพาดังกล่าวไม่จำเป็นต้องเพิ่มเนื่องจากฉันเดาว่าการบูทสปริงมีอยู่แล้ว ฉันมีปัญหานี้กับ Spring boot และใช้งานได้โดยไม่ต้องเพิ่มการพึ่งพานี้


1

สำหรับผู้ที่ใช้Spring Boot 2.x

ไม่จำเป็นต้องทำสิ่งใด ๆ ข้างต้น - Java 8 LocalDateTime ถูกทำให้เป็นอนุกรม / ยกเลิกการทำให้เป็นอนุกรมออกจากกล่อง ฉันต้องทำตามข้างต้นทั้งหมดใน 1.x แต่ด้วย Boot 2.x มันทำงานได้อย่างราบรื่น

ดูการอ้างอิงนี้ด้วยรูปแบบ JSON Java 8 LocalDateTime ใน Spring Boot


1

หากมีปัญหาขณะใช้SpringBootที่นี่คือฉันจะแก้ไขปัญหาได้อย่างไรโดยไม่ต้องเพิ่มการพึ่งพาใหม่

ในSpring 2.1.3แจ็คสันคาดว่าสตริงวัน2019-05-21T07:37:11.000นี้ในyyyy-MM-dd HH:mm:ss.SSSรูปแบบที่จะ LocalDateTimede-อันดับใน ตรวจสอบให้แน่ใจสตริงวันแยกวันที่และเวลาที่มีไม่ได้อยู่กับT spaceวินาที ( ss) และมิลลิวินาที ( SSS) อาจถูกปัดเศษ

@JsonProperty("last_charge_date")
public LocalDateTime lastChargeDate;

1

หากคุณใช้Jackson Serializerนี่คือวิธีใช้โมดูลวันที่:

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.module.paramnames.ParameterNamesModule;
import org.apache.kafka.common.serialization.Serializer;

public class JacksonSerializer<T> implements Serializer<T> {

    private final ObjectMapper mapper = new ObjectMapper()
            .registerModule(new ParameterNamesModule())
            .registerModule(new Jdk8Module())
            .registerModule(new JavaTimeModule());

    @Override
    public byte[] serialize(String s, T object) {
        try {
            return mapper.writeValueAsBytes(object);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
        return null;
    }
}

0

หากคุณพิจารณาใช้ fastjson คุณสามารถแก้ปัญหาของคุณจดบันทึกเวอร์ชันได้

 <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>1.2.56</version>
 </dependency>

0

หากคุณมีปัญหานี้เนื่องจาก GraphQL Java Tools และพยายามInstantรวบรวมJava จากสตริงวันที่คุณต้องตั้งค่า SchemaParser ของคุณเพื่อใช้ ObjectMapper ด้วยการกำหนดค่าบางอย่าง:

ในคลาส GraphQLSchemaBuilder ของคุณให้ฉีด ObjectMapper และเพิ่มโมดูลนี้:

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

และเพิ่มลงในตัวเลือก:

final SchemaParserOptions options = SchemaParserOptions.newOptions()
            .objectMapperProvider(fieldDefinition -> objectMapper)
            .typeDefinitionFactory(new YourTypeDefinitionFactory())
            .build();

ดูhttps://github.com/graphql-java-kickstart/graphql-spring-boot/issues/32

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.