сериализация / десериализация java 8 java.time с помощью картографа JSON Джексона

221

Как я могу использовать JSON JSON Mapper с 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

Там нет необходимости использовать пользовательские сериализаторы / десериализаторы здесь. Используйте модуль datetime из jackson-modules-java8 :

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

Этот модуль добавляет поддержку для нескольких классов:

  • продолжительность
  • Мгновенное
  • LocalDateTime
  • LocalDate
  • Местное время
  • День месяца
  • OffsetDateTime
  • OffsetTime
  • период
  • Год
  • Год месяц
  • ZonedDateTime
  • ZoneId
  • ZoneOffset
Мэтт Болл
источник
59
О да! Я думал, что это не сработало, потому что я не осознавал, что нужно выполнить одну из этих настроек для объекта mapper: registerModule(new JSR310Module())или findAndRegisterModules(). См. Github.com/FasterXML/jackson-datatype-jsr310, и здесь вы узнаете, как настроить объект mapper, если вы используете 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 Framework, вам больше не нужно настраивать себя, так как хорошо известные модули Джексона, такие как этот, теперь автоматически регистрируются, если они обнаружены в classpath: spring.io/blog/2014/12/02/…
vorburger
37
JSR310Module уже немного устарел, вместо этого используйте JavaTimeModule
Jacob Eckel
17
@MattBall я предложил бы добавить , что, в дополнении к использованию ДЖЕКСОН-тип_данному-jsr310, вам нужно зарегистрировать JavaTimeModule с объектом картографом: objectMapper.registerModule(new JavaTimeModule());. Как по сериализации, так и по десериализации.
Гранд-адмирал
76

Обновление: оставив этот ответ по историческим причинам, но я не рекомендую его. Пожалуйста, смотрите принятый ответ выше.

Скажите Джексону, чтобы он отображал, используя ваши пользовательские классы сериализации:

@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
FasterXML - это Джексон. accelexml.com github.com/fasterxml/jackson
Мэтт Болл
4
О, я вижу. так много древних помпонов в проекте, над которым я работаю. так что не надо больше org.codehaus, теперь это все com.fasterxml. Спасибо!
Александр Тейлор
2
Также я не смог использовать встроенный, потому что мне нужно было передать форматер ISO_DATE_TIME. Не уверен, что это можно сделать при использовании JSR310Module.
Исаак. Хазан
Используя это с Spring, я должен был создать bean-компонент LocalDateTimeSerializerиLocalDateTimeDeserializer
BenR
53

Если вы используете класс ObjectMapper fastxml, по умолчанию 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>>() {
            });
greperror
источник
4
findAndRegisterModules()была критическая часть, которая отсутствовала для меня при созданииObjectMapper
Xaero Degreaz
2
Что насчет Мгновенного?
powder366
1
Я также добавил эту строку: objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
Джанет
это полный необходимый код: 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.
Андрей Урванцев
5
Помимо отключения, DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONEмне также пришлось отключить SerializationFeature.WRITE_DATES_AS_TIMESTAMPSвсе, чтобы начать работать как надо.
Мэтт Уотсон
16

У меня была похожая проблема при использовании Spring boot . В Spring boot 1.5.1.RELEASE все, что мне нужно было сделать, это добавить зависимость:

<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());
  • 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файле, чтобы разрешить мгновенное время, которое является API даты в java8:

spring.jackson.serialization.write-dates-as-timestamps=false
Janki
источник
2

Если вы используете загрузку Spring и имеете эту проблему с OffsetDateTime, тогда вам нужно использовать registerModules, как было сказано выше @greperror (отвечено 28 мая '16 в 13:04), но обратите внимание, что есть одно отличие. Упомянутую зависимость не нужно добавлять, так как я предполагаю, что у весенней загрузки она уже есть. У меня была эта проблема с загрузкой Spring, и она работала для меня без добавления этой зависимости.

VC2019
источник
1

Для тех, кто использует Spring Boot 2.x

Нет необходимости делать что-либо из вышеперечисленного - Java 8 LocalDateTime сериализуется / десериализуется из коробки. Мне нужно было сделать все вышеперечисленное в 1.x, но с Boot 2.x все работает без проблем.

См. Также эту ссылку JSON Java 8 LocalDateTime формат в Spring Boot

HopeKing
источник
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;
    }
}
edubriguenti
источник
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/issues/32.

Янак Мина
источник