当前位置: 首页 > 知识库问答 >
问题:

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

商骞仕
2023-03-14

我的RESTendpoint返回包含对象字段的响应对象。序列化一切正常,但当我开始为此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());
        }
    }
}

我的期望(以及我对列表的期望

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

我现在拥有的Object:

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

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

共有3个答案

郑茂勋
2023-03-14

要求将Object类型的属性反序列化到正确的类中。

可以考虑对不同类型使用不同的路由。如果这不是一个选项,并且由于您可以控制序列化数据并尽可能接近现有代码,那么可以使用JsonDeserialize注释。

解决方案可以是向JSON数据添加“response_type”。

由于除了动物列表之外,您还需要反序列化其他类型的对象,因此也许使用更通用的名称而不是动物是有意义的。让我们将其从动物重命名为响应

因此

private Object animals;

您将拥有:

private Object response;

然后您的代码稍微修改为上述几点可能如下所示:

package jackson.poly;

import com.fasterxml.jackson.databind.annotation.JsonDeserialize;

public class Zoo {

    private String responseType;

    @JsonDeserialize(using = ResponseDeserializer.class)
    private Object response;

    public String getResponseType() { return responseType; }
    public Object getResponse() { return response; }
}

ResponseDeserializer可能如下所示:

package jackson.poly;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;

import java.io.IOException;
import java.util.Arrays;

public class ResponseDeserializer extends JsonDeserializer<Object> {
    @Override
    public Object deserialize(JsonParser jsonParser,
                                  DeserializationContext deserializationContext) throws
            IOException, JsonProcessingException {
        Zoo parent = (Zoo)deserializationContext.getParser().getParsingContext().getParent().getCurrentValue();

        String responseType = parent.getResponseType();
        if(responseType.equals("Animals[]")) {
            Animal[] animals = jsonParser.readValueAs(Animal[].class);
            return Arrays.asList(animals);
        }
        else if(responseType.equals("Facilities[]")) {
            Facility[] facilities = jsonParser.readValueAs(Facility[].class);
            return Arrays.asList(facilities);
        }
        throw new RuntimeException("unknown subtype " + responseType);
    }

}

使用如下输入数据(请注意response\u type属性):

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

上述程序的测试运行将在调试控制台上产生以下输出:

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

如果您将使用这样的内容作为输入(假设相应的类以类似于动物类的形式存在):

private static final String JSON_STRING = "{\n"
        + "  \"response_type\": \"Facilities[]\",\n"
        + "  \"response\": [\n"
        + "    {\"name\": \"house\"},\n"
        + "    {\"name\": \"boat\"}\n"
        + "  ]\n"
        + "}";

你会得到

class jackson.poly.House
class jackson.poly.Boat

作为输出。

韩烈
2023-03-14

如果知道要转换的类型,可以使用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的默认键入(带有所需的预防措施)。

你可以这样做:

PolymorphicTypeValidator validator = BasicPolymorphicTypeValidator
  .builder()
  .allowIfBaseType(Animal.class)
  .build()
;

ObjectMapper mapper = new ObjectMapper();
mapper.activateDefaultTyping(DefaultTyping.NON_FINAL, validator);

您可以做的另一件事是为包含任意信息的字段定义自定义反序列化器。

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

当您将属性定义为Object时,Jackson将分配类com的一个实例。fasterxml。杰克逊。数据绑定。德塞。std.UntypedObjectDeserializer作为其反序列化程序。

在您的情况下,此类将首先处理数组,因此我们获得一个List作为主要的反序列化结果。

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

此过程由mapObject方法处理。如果您有某种类型的字段,允许您识别正在处理的对象的类型,那么您可以定义一个新的反序列化器,该反序列化器扩展非类型对象反序列化器,并覆盖映射对象方法,以便从其父对象返回的映射创建实际的具体对象,只需应用JavaBean内省和属性设置器。

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

咸利
2023-03-14

不幸的是,反序列化程序从声明的字段类型中接受类型。所以,如果字段是对象,则它不会反序列化为任何内容,并将其保留为映射。没有简单的解决方案,因为反序列化没有关于它应该是什么类型的信息。一种解决方案是定制映射器,就像其中一个答案一样,或者您可以使用定制的标记对象类,而不是我用于类似用例的对象:

public class TaggedObject {

    @Expose
    private String type;
    @Expose
    private Object value;
    @Expose
    private Pair<TaggedObject, TaggedObject> pairValue;
    
    @SuppressWarnings("unchecked")
    public TaggedObject(Object v) {
        this.type = getTypeOf(v);
        if (v instanceof Pair) {
            this.pairValue = tagPair((Pair<Object, Object>) v);
            this.value = null;
        } else if ("long".equals(type)){
            this.value = "" + v;
            this.pairValue = null;
        } else {
            this.value = v;
            this.pairValue = null;
        }
    }

    private static Pair<TaggedObject, TaggedObject> tagPair(Pair<Object, Object> v) {
        return new Pair<TaggedObject, TaggedObject>(TaggedObject.tag(v.first), TaggedObject.tag(v.second));
    }

    private static String getTypeOf(Object v) {
        Class<?> cls = v.getClass();
        if (cls.equals(Double.class))
            return "double";
        if (cls.equals(Float.class))
            return "float";
        if (cls.equals(Integer.class))
            return "integer";
        if (cls.equals(Long.class))
            return "long";
        if (cls.equals(Byte.class))
            return "byte";
        if (cls.equals(Boolean.class))
            return "boolean";
        if (cls.equals(Short.class))
            return "short";
        if (cls.equals(String.class))
            return "string";
        if (cls.equals(Pair.class))
            return "pair";
        return "";
    }

    public static TaggedObject tag(Object v) {
        if (v == null)
            return null;
        return new TaggedObject(v);
    }

    public Object get() {
        if (type.equals("pair"))
            return new Pair<Object, Object>(
                    untag(pairValue.first),
                    untag(pairValue.second)
                    );
        return getAsType(value, type);
    }

    private static Object getAsType(Object value, String type) {
        switch (type) {
        case "string" : return value.toString();
        case "double" : return value;
        case "float"  : return ((Double)value).doubleValue();
        case "integer": return ((Double)value).intValue();
        case "long"   : {
            if (value instanceof Double)
                return ((Double)value).longValue();
            else
                return Long.parseLong((String) value);
        }
        case "byte"   : return ((Double)value).byteValue();
        case "short"  : return ((Double)value).shortValue();
        case "boolean": return value;
        }
        return null;
    }

    public static Object untag(TaggedObject object) {
        if (object != null)
            return object.get();
        return null;
    }
}

这是针对google gson的(这就是为什么会有Expose注释的原因),但应该可以与jackson配合使用。我没有包括Pair类,但您可以根据签名创建自己的类,也可以省略它(我需要序列化Pair)。

 类似资料:
  • 我认为最好用一个例子来解释。 我有一个想要反序列化的JSON对象,它包含一个接口类型列表以及列表中的类型,但是我不确定如何让反序列化器确定列表中的具体类型: 要反序列化的类型 界面 儿童 我知道如果类型不是<code>List</code>,我可以将类型与<code>JsonSubTypes</code>一起使用,例如: 如果在父类型内,也是如此。但是当类型在类之外时,是否有一种方法可以帮助反序列

  • 问题内容: 我正在尝试使用Gson将涉及多态的对象序列化/反序列化为JSON。 这是我的序列化代码: 结果如下: 序列化大多的作品,除了它缺少继承成员的内容(尤其是和字符串丢失)。这是我的基类: 这是我的继承类(ObixOp)的样子: 我意识到我可以为此使用适配器,但是问题是我正在序列化基类type的集合。从中继承大约25个类。我该如何优雅地进行这项工作? 问题答案: 我认为自定义序列化器/反序列

  • 我正在尝试使用Gson将一个涉及多态性的对象序列化/反序列化为JSON。 这是我的序列化代码: 结果是这样的: 序列化主要工作,除了它缺少继承成员的内容(特别是和字符串丢失)。这是我的基类: 下面是我继承的类(ObixOp)的样子: 我意识到我可以使用适配器来实现这一点,但问题是我正在序列化一个基类类型的集合。大约有25个类继承自此。我怎样才能优雅地完成这项工作?

  • 我有一个杰克逊多态性问题。我想将JSON数据反序列化为多态类型。通过阅读Jackson文档,我可以将JSON数据反序列化为多态类型。不过,我有一个特例。我有一个类结构如下: 注意:类栏除了继承的“类型”字段之外没有任何其他成员变量。 如果我传入上面的json数据,如: 我得到了类似“无法从end_token中反序列化类条”的信息。我相信这是因为JsonTypeInfo和JsonSubTypes注释

  • 问题内容: 我在使用AJAX访问的Java服务器应用程序中有一个字符串。它看起来像以下内容: 当从服务器提取字符串时,是否有一种简单的方法可以将其转换为活动的JavaScript对象(或数组)?还是我必须手动拆分字符串并手动构建对象? 问题答案: 现代浏览器支持。 在不浏览器,您可以包括在库中。

  • 问题内容: 你好亲爱的同事们, 我有一个Garden类,在其中我可以序列化和反序列化多个Plant类对象。如果想将其分配给mein静态方法中的调用变量,则可以进行序列化,但是不能进行反序列化。 反序列化代码: 我的调用代码: 编辑 我有一个空的Pointer异常 @NilsH的解决方案工作正常,谢谢! 问题答案: 如何序列化整个列表呢?无需序列化列表中的每个对象。 如果那不能解决您的问题,请发布有