serialize / deserialize java 8 java.time за допомогою картографа Jackson JSON


221

Як я можу використовувати картограф Jackson JSON з Java 8 LocalDateTime?

org.codehaus.jackson.map.JsonMappingException: Неможливо встановити значення типу [простий тип, клас java.time.LocalDateTime] від JSON String; відсутній одностроковий конструктор / заводський метод (через довідковий ланцюг: MyDTO ["field1"] -> SubDTO ["дата"])


4
Ви впевнені, що хочете зіставити LocalDateTime на JSon? Наскільки мені відомо, JSon не має формату дат, хоча JavaScript використовує ISO-8601. Проблема полягає в тому, що LocalDateTime не має часового поясу ... тому, якщо ви використовуєте JSON як носій для надсилання інформації про дату / час, ви можете потрапити в проблеми, якщо клієнт інтерпретуватиме відсутність часового поясу як UTC за замовчуванням (або його власне) часовий пояс). Якщо це те, що ти хочеш зробити, звичайно, це добре. Але просто перевірте, чи розглядали ви замість цього
ZonedDateTime

Відповіді:


276

Тут не потрібно використовувати спеціальні серіалізатори / десеріалізатори. Використовуйте модуль дати jackson-module-java8 :

Модуль типу даних, щоб змусити Джексона розпізнавати типи даних Java 8 Date & Time API (JSR-310).

Цей модуль додає підтримку для декількох класів:

  • Тривалість
  • Миттєвий
  • LocalDateTime
  • LocalDate
  • Місцевий час
  • MonthDay
  • OffsetDateTime
  • OffsetTime
  • Період
  • Рік
  • РікМісяць
  • ZonedDateTime
  • ZoneId
  • ZoneOffset

59
О, так! Я хоч і не працював, тому що я не розумів, що треба виконати одну з цих налаштувань для об’єкта картографування: registerModule(new JSR310Module())або findAndRegisterModules(). Дивіться сторінку github.com/FasterXML/jackson-datatype-jsr310, і ось, як налаштувати об’єкт картографування, якщо ви використовуєте Spring Framework: stackoverflow.com/questions/7854030/…
Олександр Тейлор

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); }
ким

9
Якщо ви використовуєте нещодавню рамку Spring, більше не потрібно налаштовувати себе, а також відомі модулі Джексона, такі як цей, тепер автоматично реєструються, якщо вони виявляються на classpath: spring.io/blog/2014/12/02/…
vorburger

37
JSR310Module вже трохи застарів, замість цього використовуйте JavaTimeModule
Jacob

17
@MattBall я запропонував би додати , що, в доповненні до використання ДЖЕКСОН-тип_даних-jsr310, вам потрібно зареєструвати JavaTimeModule з об'єктом картографом: objectMapper.registerModule(new JavaTimeModule());. Як щодо серіалізації, так і десеріалізації.
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, що б це не було.
Олександр Тейлор

4
Більш швидкий - це Джексон. quickxml.com github.com/fasterxml/jackson
Метт Бал

4
о Я бачу. так багато старовинних записів у проекті, над яким я працюю. тому більше немає org.codehaus, тепер усе com.fasterxml. Дякую!
Олександр Тейлор

2
Крім того, я не зміг використати вбудований, тому що мені потрібно було пройти формат ISO_DATE_TIME. Не впевнений, чи можливо це зробити при використанні JSR310Module.
isaac.hazan

Використовуючи це за допомогою Spring, я повинен був створити боб обох LocalDateTimeSerializerіLocalDateTimeDeserializer
BenR

53

Якщо ви використовуєте клас ObjectMapper швидшогоxml, за замовчуванням ObjectMapper не розуміє клас LocalDateTime, тому вам потрібно додати ще одну залежність у вашому gradle / maven:

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

Тепер вам потрібно зареєструвати підтримку типу даних, запропоновану цією бібліотекою, у ваш об'єктmapper-об'єкт, це можна зробити, виконавши наступне:

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
А що з Миттєвим?
порошок366

1
Я також додав цей рядок: objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
Жанет

це повний необхідний код: ObjectMapper objectMapper = новий 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.
Андрій Урванцев

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

Якщо ви використовуєте Джерсі, тоді вам потрібно додати залежність Maven (jackson-datatype-jsr310), як запропонували інші, і зареєструвати екземпляр вашого об'єктного картографа так:

@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

Це дуже чистий метод і дозволяє користувачам вашого класу використовувати будь-який спосіб, який вони хочуть.
Ашар Хасан

3

Я використовую цей формат часу: "{birthDate": "2018-05-24T13:56:13Z}"для деріаріалізації з json в java.time.Instant (див. Скріншот)

введіть тут опис зображення


3

Це лише приклад того, як його використовувати в одиничному тесті, який я зламав для налагодження цієї проблеми. Ключові компоненти є

  • mapper.registerModule(new JavaTimeModule());
  • залежність від <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файлі, щоб вирішити миттєвий час, який є API API в java8:

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

2

Якщо ви використовуєте Spring boot і маєте цю проблему з OffsetDateTime, тоді потрібно використовувати registerModules, як відповів вище @greperror (відповів 28 травня 1616 в 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форматі десертифікується в LocalDateTime. Переконайтесь, що рядок дати розділяє дату та час, а 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 і ви намагаєтеся маршалити Java Instantз рядка дати, вам потрібно налаштувати 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/isissue/32

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