有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

java将多态对象列表反序列化为对象字段

我的REST端点返回包含对象字段的响应对象。序列化一切正常,但当我开始为这个API编写客户端时,我遇到了反序列化问题。我根据一些关于Jackson多态序列化的问题/文章编写了这个示例代码。这说明了这个问题

@Data
abstract class Animal {

    String name;
}

@Data
class Dog extends Animal {

    boolean canBark;
}

@Data
class Cat extends Animal {

    boolean canMeow;
}

@Data
public class Zoo {

    private Object animals;
}


@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "name", visible = true)
@JsonSubTypes({
        @JsonSubTypes.Type(name = "dog", value = Dog.class),
        @JsonSubTypes.Type(name = "cat", value = Cat.class)
})
public class Mixin {

}

public class Main {

    private static final String JSON_STRING = "{\n"
            + "  \"animals\": [\n"
            + "    {\"name\": \"dog\"},\n"
            + "    {\"name\": \"cat\"}\n"
            + "  ]\n"
            + "}";

    public static void main(String[] args) throws IOException {
        ObjectMapper objectMapper = Jackson.newObjectMapper()
                .setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE)
                .setDefaultPropertyInclusion(Include.NON_NULL);
        objectMapper.addMixIn(Animal.class, Mixin.class);
        Zoo zoo = objectMapper.readValue(JSON_STRING, Zoo.class);
        for (Object animal : (Collection) zoo.getAnimals()) {
            System.out.println(animal.getClass());
        }
    }
}

我对输出的期望(以及我对List<Animals>作为动物园#动物类型的期望):

class jackson.poly.Dog
class jackson.poly.Cat

我现在对Object的看法是:

class java.util.LinkedHashMap
class java.util.LinkedHashMap

但我需要反序列化除动物列表之外的其他类型的对象。救命啊


共 (2) 个答案

  1. # 1 楼答案

    我向所有参与的人表示诚挚的感谢。我决定采用一种有点内省的方法,并编写了自定义反序列化程序,查看列表中第一个对象的内部,以确定是否需要将其反序列化为列表

    public class AnimalsDeserializer extends JsonDeserializer<Object> {
    
        private Set<String> subtypes = Set.of("dog", "cat");
    
        @Override
        public Object deserialize(JsonParser parser, DeserializationContext ctx)
                throws IOException, JsonProcessingException {
            if (parser.getCurrentToken() == JsonToken.START_ARRAY) {
                TreeNode node = parser.getCodec().readTree(parser);
                TreeNode objectNode = node.get(0);
                if (objectNode == null) {
                    return List.of();
                }
                TreeNode type = objectNode.get("name");
                if (type != null
                        && type.isValueNode()
                        && type instanceof TextNode
                        && subtypes.contains(((TextNode) type).asText())) {
                    return parser.getCodec().treeAsTokens(node).readValueAs(new TypeReference<ArrayList<Animal>>() {
                    });
                }
            }
            return parser.readValueAs(Object.class);
        }
    }
    

    也许有些支票是多余的。它是有效的,这才是最重要的:)。 该奖项授予斯蒂芬施莱希特《最鼓舞人心的答案》一书的作者。再次感谢

  2. # 2 楼答案

    如果知道要转换为的类型,可以使用ObjectMapper的方法convertValue

    可以转换单个值或整个列表

    考虑下面的例子:

    public static void main(String[] args) throws IOException {
      ObjectMapper objectMapper = Jackson.newObjectMapper()
        .setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE)
        .setDefaultPropertyInclusion(Include.NON_NULL);
      objectMapper.addMixIn(Animal.class, Mixin.class);
      Zoo zoo = objectMapper.readValue(JSON_STRING, Zoo.class);
      List<Animal> animals = objectMapper.convertValue(zoo.getAnimals(), new TypeReference<List<Animal>>(){});
      for (Object animal : animals) {
        System.out.println(animal.getClass());
      }
    }
    

    对于您的评论,如果您不确定需要处理的实际类型,一个有用的选项是启用Jackson的默认类型(使用required precautions

    你可以这样做:

    PolymorphicTypeValidator validator = BasicPolymorphicTypeValidator
      .builder()
      .allowIfBaseType(Animal.class)
      .build()
    ;
    
    ObjectMapper mapper = new ObjectMapper();
    mapper.activateDefaultTyping(DefaultTyping.NON_FINAL, validator);
    

    您可以做的另一件事是为包含任意信息的字段定义一个自定义Deserializer

    您可以重用一些代码,而不是从头开始创建一个

    当您将属性定义为Object时,Jackson会将类com.fasterxml.jackson.databind.deser.std.UntypedObjectDeserializer的一个实例指定为它的Deserializer

    在您的例子中,这个类将首先处理数组,因此我们获得一个List作为主要的反序列化结果

    这个List将由类LinkedHashMap的实例组成。为什么?因为在它们的实现中,对于数组中的每个JSON对象,它们都会生成一个带有键的LinkedHashMap,JSON对象属性名称按它们出现的顺序,以及一个带有JSON对象属性值的String[]

    这个过程由mapObject方法处理。如果您有某种类型的字段,允许您标识正在处理的Object的类型,那么您可以定义一个新的Deserializer,它扩展UntypedObjectDeserializer并覆盖mapObject方法,从其父级返回的Map创建实际的、具体的对象,只需应用JavaBean内省和属性设置器

    最后一种方法也可以用Converter实现。它们实际上非常适合这两个阶段的过程。其背后的想法与上一段中描述的相同:您将收到ListMap,如果您能够识别具体的类型,您只需要根据收到的信息构建相应的实例。您甚至可以在最后一个转换步骤中使用ObjectMapper