【问题标题】:Deserialising string to map with multiple types using jackson使用杰克逊反序列化字符串以映射多种类型
【发布时间】:2018-10-14 17:41:55
【问题描述】:

我见过像this one 这样的答案,它显示了使用TypeFactory.constructMapType(...) 将JSON 字符串反序列化为键/值组合不是String 的映射。我有一种情况,我的字符串应该反序列化为多种不同类型,而不仅仅是一种。

我意识到一种解决方案是定义我自己的类而不使用Map,但我想知道是否可以使用纯配置代替?

这是我的测试代码。

import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;

import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.joda.time.LocalDateTime;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.type.MapType;
import com.fasterxml.jackson.databind.type.TypeFactory;
import com.fasterxml.jackson.datatype.joda.JodaModule;

public class JodaTimeMapTest {

   public static void main(final String[] args) throws Exception {
      // Map with dates.
      final DateTime now = new DateTime().withZone(DateTimeZone.UTC);
      final LocalDateTime nowLocal = new LocalDateTime();
      final LocalDateTime notNowLocal = new LocalDateTime(2007, 3, 25, 2, 30, 0);
      final Map<String, Object> dateMap = new HashMap<>();
      dateMap.put("now", now);
      dateMap.put("nowLocal", nowLocal);
      dateMap.put("notNowLocal", notNowLocal);

      // Serialise map to string.
      final ObjectMapper mapper = mapper();
      final String dateMapJson = mapper.writeValueAsString(dateMap);

      // De-serialise string to map.
      final TypeFactory typeFactory = mapper.getTypeFactory();
      final MapType mapType = typeFactory.constructMapType(HashMap.class, String.class, Object.class);
      final HashMap<String, Object> dateMapFromJson = mapper.readValue(dateMapJson, mapType);

      // First one has dates, second has strings.
      printMap(dateMap);
      printMap(dateMapFromJson);
   }

   private static void printMap(final Map<String, Object> map) {
      System.out.println(map.entrySet().stream().map(entry -> {
         return entry.getKey() + ", type = " + entry.getValue().getClass().getName() + ", value = " + entry.getValue();
      }).collect(Collectors.joining(System.lineSeparator())));
   }

   private static ObjectMapper mapper() {
      final ObjectMapper mapper = new ObjectMapper();
      mapper.registerModule(new JodaModule());
      mapper.configure(com.fasterxml.jackson.databind.SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
      return mapper;

   }

}

这个类的输出表明,读入,Jakcson 只能假设这些是字符串:

now, type = org.joda.time.DateTime, value = 2018-05-04T09:10:26.063Z
notNowLocal, type = org.joda.time.LocalDateTime, value = 2007-03-25T02:30:00.000
nowLocal, type = org.joda.time.LocalDateTime, value = 2018-05-04T19:10:26.193
now, type = java.lang.String, value = 2018-05-04T09:10:26.063Z
notNowLocal, type = java.lang.String, value = 2007-03-25T02:30:00.000
nowLocal, type = java.lang.String, value = 2018-05-04T19:10:26.193

示例解决方案

基于answer aussie gave,这是一个适合我的解决方案。在我的示例中,我只需要地图 key 来确定 value 是哪种 Joda 日期/时间类。

首先是我对澳大利亚人告诉我的反序列化器的实现。

import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;

import org.joda.time.DateTime;
import org.joda.time.LocalDateTime;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;

/** De-serialise values from a map that contains Joda times and strings. */
public class JodaMapDeserialiser extends StdDeserializer<Object> {

   /** Mapping between keys in the map to a type of Joda time. */
   enum DateType {
      DATE_TIME("now"), LOCAL_DATE_TIME("notNowLocal", "nowLocal");

      final List<String> keys;

      DateType(final String... keys) {
         this.keys = Arrays.asList(keys);
      }

      public static DateType forKeyString(final String keyString) {
         return Stream.of(values()).filter(dateTypes -> dateTypes.keys.contains(keyString)) //
               .findFirst().orElse(null);
      }
   }

   public JodaMapDeserialiser() {
      super(Object.class);
   }

   @Override
   public Object deserialize(final JsonParser p, final DeserializationContext ctxt)
         throws IOException, JsonProcessingException {

      // Each entry in the map has a key and value.
      final String value = p.readValueAs(String.class);
      final String key = p.getCurrentName();

      // Convert the value depending on what the key is.
      switch (DateType.forKeyString(key)) {
         case DATE_TIME:
            return DateTime.parse(value);

         case LOCAL_DATE_TIME:
            return LocalDateTime.parse(value);

         default:
            return value;
      }
   }
}

这里有一些稍微修改过的测试代码。

import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;

import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.joda.time.LocalDateTime;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.type.MapType;
import com.fasterxml.jackson.databind.type.TypeFactory;
import com.fasterxml.jackson.datatype.joda.JodaModule;

public class JodaTimeMapTest {

   public static void main(final String[] args) throws Exception {

      // Map with dates.
      final DateTime now = new DateTime().withZone(DateTimeZone.UTC);
      final LocalDateTime nowLocal = new LocalDateTime();
      final LocalDateTime notNowLocal = new LocalDateTime(2007, 3, 25, 2, 30, 0);
      final Map<String, Object> dateMap = new HashMap<>();
      dateMap.put("now", now);
      dateMap.put("nowLocal", nowLocal);
      dateMap.put("notNowLocal", notNowLocal);

      // Serialise map to string.
      final ObjectMapper mapper = mapper();
      final String dateMapJson = mapper.writeValueAsString(dateMap);

      // De-serialise string to map.
      final TypeFactory typeFactory = mapper.getTypeFactory();
      final MapType mapType = typeFactory.constructMapType(HashMap.class, String.class, Object.class);
      final HashMap<String, Object> dateMapFromJson = mapper.readValue(dateMapJson, mapType);

      // First one has dates, second has strings.
      System.out.println("Actual map.");
      printMap(dateMap);
      System.out.println("Map de-serialised from JSON.");
      printMap(dateMapFromJson);
      System.out.println("Maps are equal: " + dateMap.equals(dateMapFromJson));
   }

   private static void printMap(final Map<String, Object> map) {
      System.out.println(map.entrySet().stream().map(entry -> {
         return "  " + entry.getKey() + ", type = " + entry.getValue().getClass().getName() + ", value = "
               + entry.getValue();
      }).collect(Collectors.joining(System.lineSeparator())));
   }

   private static ObjectMapper mapper() {
      final ObjectMapper mapper = new ObjectMapper();
      mapper.registerModule(new JodaModule());
      mapper.configure(com.fasterxml.jackson.databind.SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);

      final SimpleModule dateDeserializerModule = new SimpleModule();
      dateDeserializerModule.addDeserializer(Object.class, new JodaMapDeserialiser());
      mapper.registerModule(dateDeserializerModule);

      return mapper;

   }
}

输出是:

Actual map.
  now, type = org.joda.time.DateTime, value = 2018-05-05T04:03:20.684Z
  notNowLocal, type = org.joda.time.LocalDateTime, value = 2007-03-25T02:30:00.000
  nowLocal, type = org.joda.time.LocalDateTime, value = 2018-05-05T14:03:20.809
Map de-serialised from JSON.
  now, type = org.joda.time.DateTime, value = 2018-05-05T04:03:20.684Z
  notNowLocal, type = org.joda.time.LocalDateTime, value = 2007-03-25T02:30:00.000
  nowLocal, type = org.joda.time.LocalDateTime, value = 2018-05-05T14:03:20.809
Maps are equal: true

最后,我的 maven 依赖项(joda time 包含在 jackson-datatype-joda 中)。

<dependency>
   <groupId>com.fasterxml.jackson.core</groupId>
   <artifactId>jackson-core</artifactId>
   <version>2.9.5</version>
</dependency>
<dependency>
   <groupId>com.fasterxml.jackson.datatype</groupId>
   <artifactId>jackson-datatype-joda</artifactId>
   <version>2.9.5</version>
</dependency>

其他选项

总的来说,我找到的选项:

  • 为单个类型组合创建类型定义:HashmapString 键和 DateTime 值。
  • 创建自定义类以将键/值映射到。
  • 创建反序列化程序以定义如何将字符串转换为对象的规则。

为了进一步探索我发现的不同选项,我写了this blog post

【问题讨论】:

    标签: java json jackson jodatime json-deserialization


    【解决方案1】:

    由于您注册的 Jodamodule,您的日期对象被序列化为字符串:“now”:“2018-05-04T11:42:15.454Z”

    当您反序列化 Json 字符串时,您期望一个带有字符串键和对象值的 HashMap。 Jackson 怎么知道这些对象应该是不同类型的日期,它只看到字符串..?

    您可以做的是为此创建一个自定义反序列化器并实现正确反序列化每个日期的逻辑(例如,您可以通过正则表达式确定类型)。

    public class MyDateDeserializer extends StdDeserializer<Object> {
        public MyDateDeserializer() {
            super(Object.class);
        }
        @Override
        public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
            return convertStringToTheProperDate(p.readValueAs(String.class));
        }
        private Object convertStringToTheProperDate(String dateAsString) {
           // implement the logic to convert the string to the proper type
           return null;
        }
    }
    

    然后注册反序列化器:

    SimpleModule dateDeserializerModule = new SimpleModule();
    dateDeserializerModule.addDeserializer(Object.class, new MyDateDeserializer());
    mapper.registerModule(dateDeserializerModule);
    

    【讨论】:

    • 有什么例子吗?我还没有找到任何可以告诉我如何使用嘿名称作为示例的任何内容。
    • 我在刚刚编写的答案中包含了一个示例,以便您了解如何创建和注册它。添加后,ObjectMapper 将使用此反序列化器反序列化所有 Object 类。
    • 我对这个例子的想法是我会使用正则表达式来区分日期类型,所以你不关心键
    • 谢谢!我会试一试并报告。
    • 对于我的情况,我只需要测试 key 就知道 value 应该是什么(因为在实际代码中这是为了,有很多条目,只有两三个包含日期 - 其余的都可以作为字符串)。根据您的建议@aussie,我已经用我的答案更新了我的问题 - 非常感谢。 :D
    猜你喜欢
    • 2012-11-19
    • 1970-01-01
    • 2021-09-09
    • 1970-01-01
    • 2015-01-09
    • 1970-01-01
    • 1970-01-01
    • 2012-12-22
    • 2018-10-19
    相关资源
    最近更新 更多