使用Jackson反序列化具有不同对象的类型

16
我可以为您进行翻译。这段文字描述了一个Web服务的结果,它返回布尔值或单例映射。例如:
布尔值结果:

true

false

单例映射结果:

{"key":"value"}

{
    id: 24428,
    rated: false
}

地图结果:
{
    id: 78,
    rated: {
        value: 10
    }
}

我可以轻松地分别映射它们,但如何通用地做到这一点?

基本上,我想将其映射到一个类,例如:

public class Rating {
    private int id;
    private int rated;
    ...
    public void setRated(?) {
        // if value == false, set rated = -1;
        // else decode "value" as rated
    }
}

所有多态示例都使用@JsonTypeInfo基于数据中的属性进行映射,但在这种情况下我没有这个选项。


编辑
代码更新部分:

@JsonProperty("rated")
public void setRating(JsonNode ratedNode) {
    JsonNode valueNode = ratedNode.get("value");
    // if the node doesn't exist then it's the boolean value
    if (valueNode == null) {
        // Use a default value
        this.rating = -1;
    } else {
        // Convert the value to an integer
        this.rating = valueNode.asInt();
    }
}
4个回答

30

不不不,你不需要编写自定义反序列化器。只需先使用“未命名”映射:

public class Response {
  public long id;
  public Object rated;
}
// OR
public class Response {
  public long id;
  public JsonNode rated;
}
Response r = mapper.readValue(source, Response.class);

第一种方法返回值为Booleanjava.util.Map,对于"rated";第二种情况返回JsonNode

从这里,您可以直接访问数据,或者更有趣的是将其转换为实际值:

if (r.rated instanceof Boolean) {
    // handle that
} else {
    ActualRated actual = mapper.convertValue(r.rated, ActualRated.class);
}
// or, if you used JsonNode, use "mapper.treeToValue(ActualRated.class)

还有其他种方法--使用creator "ActualRated(boolean)",让实例可以从POJO或标量构建。但我认为上面的方法应该可以工作。


我发誓我之前尝试过这种方法!我已经更新了我的问题,并提供了最终的解决方案。非常感谢。 - Omertron

4

你需要编写自己的反序列化器。它可能看起来像这样:

@SuppressWarnings("unchecked")
class RatingJsonDeserializer extends JsonDeserializer<Rating> {

    @Override
    public Rating deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
        Map<String, Object> map = jp.readValueAs(Map.class);

        Rating rating = new Rating();
        rating.setId(getInt(map, "id"));
        rating.setRated(getRated(map));

        return rating;
    }

    private int getInt(Map<String, Object> map, String propertyName) {
        Object object = map.get(propertyName);

        if (object instanceof Number) {
            return ((Number) object).intValue();
        }

        return 0;
    }

    private int getRated(Map<String, Object> map) {
        Object object = map.get("rated");
        if (object instanceof Boolean) {
            if (((Boolean) object).booleanValue()) {
                return 0; // or throw exception
            }

            return -1;
        }

        if (object instanceof Map) {
            return getInt(((Map<String, Object>) object), "value");
        }

        return 0;
    }
}

现在您需要告诉Jackson使用此反序列化程序来处理Rating类:
@JsonDeserialize(using = RatingJsonDeserializer.class)
class Rating {
...
}

简单的用法:

ObjectMapper objectMapper = new ObjectMapper();
System.out.println(objectMapper.readValue(json, Rating.class));

以上程序输出结果为:
Rating [id=78, rated=10]

JSON 格式:

{
    "id": 78,
    "rated": {
        "value": 10
    }
}

并打印出:
Rating [id=78, rated=-1]

对于JSON:

{
    "id": 78,
    "rated": false
}

谢谢,最初我也这样做了,但在我看来StaxMan的解决方案更简洁,减少了我需要的实际类数。 - Omertron

3
我在这个主题上找到了一篇很好的文章:http://programmerbruce.blogspot.com/2011/05/deserialize-json-with-jackson-into.html 我认为将JSON解析成对象的方法可能存在问题,因为当你发送它时,你发送的是一个字符串。我不确定这是否是一个实际的问题,但听起来可能会有一些意外的行为。示例5和6表明您可以使用继承来解决这个问题。
例如:

Example 6: Simple Deserialization Without Type Element To Container Object With Polymorphic Collection

Some real-world JSON APIs have polymorphic type members, but don't include type elements (unlike the JSON in the previous examples). Deserializing such sources into polymorphic collections is a bit more involved. Following is one relatively simple solution. (This example includes subsequent serialization of the deserialized Java structure back to input JSON, but the serialization is relatively uninteresting.)

// input and output:
//   {
//     "animals":
//     [
//       {"name":"Spike","breed":"mutt","leash_color":"red"},
//       {"name":"Fluffy","favorite_toy":"spider ring"},
//       {"name":"Baldy","wing_span":"6 feet",
//           "preferred_food":"wild salmon"}
//     ]
//   }

import java.io.File;
import java.io.IOException;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;

import org.codehaus.jackson.JsonNode;
import org.codehaus.jackson.JsonParser;
import org.codehaus.jackson.JsonProcessingException;
import org.codehaus.jackson.Version;
import org.codehaus.jackson.map.DeserializationContext;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.deser.StdDeserializer;
import org.codehaus.jackson.map.module.SimpleModule;
import org.codehaus.jackson.node.ObjectNode;

import fubar.CamelCaseNamingStrategy;

public class Foo
{
  public static void main(String[] args) throws Exception
  {
    AnimalDeserializer deserializer = 
        new AnimalDeserializer();
    deserializer.registerAnimal("leash_color", Dog.class);
    deserializer.registerAnimal("favorite_toy", Cat.class);
    deserializer.registerAnimal("wing_span", Bird.class);
    SimpleModule module =
      new SimpleModule("PolymorphicAnimalDeserializerModule",
          new Version(1, 0, 0, null));
    module.addDeserializer(Animal.class, deserializer);
    
    ObjectMapper mapper = new ObjectMapper();
    mapper.setPropertyNamingStrategy(
        new CamelCaseNamingStrategy());
    mapper.registerModule(module);

    Zoo zoo = 
        mapper.readValue(new File("input_6.json"), Zoo.class);
    System.out.println(mapper.writeValueAsString(zoo));
  }
}

class AnimalDeserializer extends StdDeserializer<Animal>
{
  private Map<String, Class<? extends Animal>> registry =
      new HashMap<String, Class<? extends Animal>>();

  AnimalDeserializer()
  {
    super(Animal.class);
  }

  void registerAnimal(String uniqueAttribute,
      Class<? extends Animal> animalClass)
  {
    registry.put(uniqueAttribute, animalClass);
  }

  @Override
  public Animal deserialize(
      JsonParser jp, DeserializationContext ctxt) 
      throws IOException, JsonProcessingException
  {
    ObjectMapper mapper = (ObjectMapper) jp.getCodec();
    ObjectNode root = (ObjectNode) mapper.readTree(jp);
    Class<? extends Animal> animalClass = null;
    Iterator<Entry<String, JsonNode>> elementsIterator = 
        root.getFields();
    while (elementsIterator.hasNext())
    {
      Entry<String, JsonNode> element=elementsIterator.next();
      String name = element.getKey();
      if (registry.containsKey(name))
      {
        animalClass = registry.get(name);
        break;
      }
    }
    if (animalClass == null) return null;
    return mapper.readValue(root, animalClass);
  }
}

class Zoo
{
  public Collection<Animal> animals;
}

abstract class Animal
{
  public String name;
}

class Dog extends Animal
{
  public String breed;
  public String leashColor;
}

class Cat extends Animal
{
  public String favoriteToy;
}

class Bird extends Animal
{
  public String wingSpan;
  public String preferredFood;
}

1
我曾经问过一个类似的问题 - JSON POJO polymorphic对象的消费者 你需要编写自己的deserialiser,在反序列化过程中进行查看并根据数据决定要执行什么操作。
可能还有其他更简单的方法,但这种方法对我来说效果很好。

我无法让那个解决方案对我起作用。在 InfoDeserializer 的末尾,我遇到了一个“由于输入结束而无内容可映射”的错误。 - Omertron
我使用了自定义反序列化程序,但StaxMan的解决方案对我来说更加简洁。 - Omertron

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接