【问题标题】:Map a JSON field that can have different types with Jackson?用杰克逊映射一个可以有不同类型的 JSON 字段?
【发布时间】:2011-10-04 18:57:14
【问题描述】:

我从 Web 服务获取 JSON,但无法影响 JSON 格式。下面的 JSON 代码只是说明问题的一个示例。字段cars 可以是包含Car 对象的对象,也可以是空字符串。如果我可以更改 Web 服务,我会将空字符串更改为像 "cars" : {} 这样的空对象,而不是 "cars" : ""

尝试将 JSON 映射到此 Java 对象时:

public class Person {
    public int id;
    public String name;
    public Map<String, Car> cars;
}

这行得通:

{
    "id" : "1234",
    "name" : "John Doe",
    "cars" : {
        "Tesla Model S" : {
            "color" : "silver",
            "buying_date" : "2012-06-01"
        },
        "Toyota Yaris" : {
            "color" : "blue",
            "buying_date" : "2005-01-01"
        }
    }
}

这失败了:

{
    "id" : "1",
    "name" : "The Dude",
    "cars" : ""
}

在杰克逊处理这个案件的最佳方式是什么?如果有空字符串,我想为字段cars 获取null。我尝试使用ACCEPT_EMPTY_STRING_AS_NULL_OBJECT,但没有帮助。

【问题讨论】:

    标签: json mapping jackson


    【解决方案1】:

    字段 cars 可以包含 Car 对象的列表 ... 这有效:

    {
        "id" : "1234",
        "name" : "John Doe",
        "cars" : {
            "Tesla Model S" : {
                "color" : "silver",
                "buying_date" : "2012-06-01"
            },
            "Toyota Yaris" : {
                "color" : "blue",
                "buying_date" : "2005-01-01"
            }
        }
    }
    

    “汽车”元素值不是列表(也称为数组)。它是一个 JSON 对象,也可以认为是一个地图类型的集合,但它不是一个列表。

    因此,换个说法,目标是将有时是对象,有时是空字符串的 JSON 反序列化为 Java Map

    为了解决这个问题,我很惊讶ACCEPT_EMPTY_STRING_AS_NULL_OBJECT 没用。我建议在http://jira.codehaus.org/browse/JACKSON 记录问题。

    你可以实现custom deserialization。以下是一个示例解决方案。如果目标数据结构有其他 Map 引用,则需要相应更改此解决方案。

    import java.io.File;
    import java.io.IOException;
    import java.util.HashMap;
    import java.util.Map;
    
    import org.codehaus.jackson.JsonNode;
    import org.codehaus.jackson.JsonParser;
    import org.codehaus.jackson.JsonProcessingException;
    import org.codehaus.jackson.ObjectCodec;
    import org.codehaus.jackson.Version;
    import org.codehaus.jackson.map.DeserializationContext;
    import org.codehaus.jackson.map.JsonDeserializer;
    import org.codehaus.jackson.map.ObjectMapper;
    import org.codehaus.jackson.map.module.SimpleModule;
    import org.codehaus.jackson.type.TypeReference;
    
    public class Foo
    {
      public static void main(String[] args) throws Exception
      {
        SimpleModule module = new SimpleModule("CarsDeserializer", Version.unknownVersion());
        module.addDeserializer(Map.class, new CarsDeserializer());
    
        ObjectMapper mapper = new ObjectMapper().withModule(module);
    
        Person person1 = mapper.readValue(new File("input1.json"), Person.class);
        System.out.println(mapper.writeValueAsString(person1));
        // {"id":1234,"name":"John Doe","cars":{"Tesla Model S":{"color":"silver","buying_date":"2012-06-01"},"Toyota Yaris":{"color":"blue","buying_date":"2005-01-01"}}}
    
        Person person2 = mapper.readValue(new File("input2.json"), Person.class);
        System.out.println(mapper.writeValueAsString(person2));
        // {"id":1,"name":"The Dude","cars":{}}
      }
    }
    
    class Person
    {
      public int id;
      public String name;
      public Map<String, Car> cars;
    }
    
    class Car
    {
      public String color;
      public String buying_date;
    }
    
    class CarsDeserializer extends JsonDeserializer<Map<String, Car>>
    {
      @Override
      public Map<String, Car> deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException,
          JsonProcessingException
      {
        ObjectCodec codec = jp.getCodec();
        JsonNode node = codec.readTree(jp);
        if (!"".equals(node.getTextValue()))
        {
          ObjectMapper mapper = new ObjectMapper();
          return mapper.readValue(node, new TypeReference<Map<String, Car>>() {});
        }
        return new HashMap<String, Car>(); // or return null, if preferred
      }
    }
    

    【讨论】:

    • 感谢有关列表的提示。我相应地更改了描述。所以我可能不得不写一个自定义的反序列化。
    • 确实如此。我刚刚发布了一个带有这样一个例子的更新。 (如果重复类型声明对于您的需要来说太笨拙,可以更改为使用上下文反序列化器。另外,请注意在自定义反序列化器中需要第二个解码器(ObjectMapper/ObjectCodec),因为原始配置为将 Map 反序列化传递回自定义反序列化器。第二个解码器可以在外部配置并在实例构建期间传递给 CarsDeserializer。)
    • 如果字段是像public Car car; 这样的简单对象,ACCEPT_EMPTY_STRING_AS_NULL_OBJECT 确实有效,但如果它是像 public Map&lt;String, Car&gt; cars; 这样的映射,则不会。
    • 对,对我来说这应该被认为是一个错误,或者至少是需要改进的东西。
    • 我不仅需要创建Map&lt;String, Car&gt; 对象,还需要创建其他一些地图,比如Map&lt;String, Bike&gt;。所以我创建了一个通用的MapsGenericDeserializer&lt;T&gt; extends JsonDeserializer&lt;Map&lt;String, T&gt;&gt;。现在我希望 ContextualDeserializer 的 createContextual() 方法返回这些反序列化器之一,具体取决于 BeanProperty 参数类型。然而这不起作用,因为方法public JsonDeserializer&lt;Map&lt;String, ?&gt;&gt; createContextual() 不能返回new MapsGenericDeserializer&lt;Car&gt;()。你知道如何解决这个问题吗?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-11-15
    相关资源
    最近更新 更多