jackson 当type是原始JSON的值时,多态反序列化JSON

lnvxswe2  于 2023-01-13  发布在  其他
关注(0)|答案(2)|浏览(96)

我有以下JSON结构:

{name:type, properties...}

例如:

{"bulli":"dog","barkVolume":10}
{"dogi":"dog", "barkVolume":7}
{"kitty":"cat", "likesCream":true, "lives":3}
{"milkey":"cat", "likesCream":false, "lives":9}

现在,我想把它反序列化成一个多态的dog/cat对象来扩展animal,我试过这样做,但是我知道我需要做的有点不同,因为这里我的type键不是常量:

public class Zoo {
    public Animal animal;

    @JsonTypeInfo(
      use = JsonTypeInfo.Id.NAME, 
      include = As.PROPERTY, 
      property = "type")
    @JsonSubTypes({
        @JsonSubTypes.Type(value = Dog.class, name = "dog"),
        @JsonSubTypes.Type(value = Cat.class, name = "cat")
    })
    public static class Animal {
        public String name;
    }

    @JsonTypeName("dog")
    public static class Dog extends Animal {
        public double barkVolume;
    }

    @JsonTypeName("cat")
    public static class Cat extends Animal {
        boolean likesCream;
        public int lives;
    }

有办法做到吗?

7rfyedvj

7rfyedvj1#

似乎不支持以多态方式反序列化您提供的JSON。自定义JsonDeserializer可以给予您所需的结果。此代码不能直接解决您的问题,但可以用于派生一些有用的内容:

@JsonDeserialize(using = AnimalDeserializer.class)
@JsonIgnoreProperties(ignoreUnknown=true)
public abstract class Animal {
   public String name;
}

@JsonDeserialize
public class Dog extends Animal {
   @JsonProperty
   public int barkVolume;
}

@JsonDeserialize
public class Cat extends Animal {
   @JsonProperty
   public boolean likesCream;
   @JsonProperty
   public int lives;
}

和反序列化类:

public class AnimalDeserializer extends JsonDeserializer<Animal> {
   @Override
   public Animal deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
      ObjectMapper mapper = (ObjectMapper) jsonParser.getCodec();
      ObjectNode node = mapper.readTree(jsonParser);

      Iterator<String> fieldIterator = node.fieldNames();
      while (fieldIterator.hasNext()) {
         String fieldName = fieldIterator.next();
         if (node.get(fieldName).asText().equalsIgnoreCase("dog")) {
            Dog dog = mapper.treeToValue(node, Dog.class);
            dog.name = fieldName;
            return dog;
         } else if (node.get(fieldName).asText().equalsIgnoreCase("cat")) {
            Cat cat = mapper.treeToValue(node, Cat.class);
            cat.name = fieldName;
            return cat;
         }
      }
      throw new IllegalArgumentException("Not an animal");
   }
}

正如您所看到的,需要对节点树进行一些筛选,但存在一些问题:

  • 如果另一个字段碰巧具有值“cat”或“dog”,则可能会欺骗反序列化程序,使其试图将其Map到错误的类
  • 必须手动设置名称
  • 如果实现了许多派生类型,则管理起来很困难

但是如果你使用的JSON不是很好的格式,这些就是需要的技巧。作为参考,下面是我的测试方法:

public class Farm {

   private static final String json = "[" +
       "{\"bulli\":\"dog\",\"barkVolume\":10},\n" +
       "{\"dogi\":\"dog\", \"barkVolume\":7},\n" +
       "{\"kitty\":\"cat\", \"likesCream\":true, \"lives\":3},\n" +
       "{\"milkey\":\"cat\", \"likesCream\":false, \"lives\":9}" +
       "]";

   public static void main(String[] args) throws JsonProcessingException {
      ObjectMapper mapper = new ObjectMapper();
      List<Animal> animals = mapper.readValue(json, new TypeReference<>() {
      });
      animals.forEach(a -> {
         System.out.println(a.name);
         if (a instanceof Dog) {
            System.out.println(((Dog) a).barkVolume);
         } else if (a instanceof Cat) {
            System.out.println(((Cat) a).lives);
         }
      });
   }
}
qyzbxkaa

qyzbxkaa2#

您可以操作{"bulli":"dog","barkVolume":10} json对象,获取一个可以反序列化为Animal对象的{"barkVolume":10,"type":"dog","name":"bulli"}新json对象。在这种情况下,您可以将json对象转换为ObjectNode,然后在第一个json属性始终为 “name”的条件下对其进行如下修改:“type”,如示例中所示:

//json is {"bulli":"dog","barkVolume":10}
ObjectNode node = (ObjectNode) mapper.readTree(json);
//name contains the "bulli" value
String name = node.fieldNames().next();
//type contains the "dog" value
String type = node.get(name).asText();
//remove the "bulli" property from the json
node.remove(name);
//add the new "type" property from the json with "dog" value
node.put("type", type);
//add the new "name" property from the json with "bulli" value
node.put("name", name);
//ok, deserialization to a Dog value with name "bulli" and barkVolume 10.0
Animal animal = mapper.treeToValue(node, Animal.class);

相关问题