繁体   English   中英

杰克逊:使用泛型将对象反序列化为数组

[英]Jackson: Deserialize Object to Array with Generics

我尝试编写一个自定义的JsonDeserializer,但我无法弄清楚如何获取我的类的Generic类型信息。

I18NProperty.class:

public class I18NProperty<T> extends ArrayList<T> {
  public static class Content<T> {
    public Locale i18n;     
    public T val;
  }
}

我想要的JSON表示如下所示( nameI18NProperty<Content<String>>的实例):

{
  "name": {
    "en": "foo",
    "jp": "bar"
  }
}

现在我尝试编写一个JsonDeserializer ,我能够读取字段名称和值, 我无法弄清楚如何创建泛型类型的实例(在本例中为String ):

public static class I18NPropertyDeserializer extends StdDeserializer<I18NProperty<? extends Content<?>>> {
    protected I18NPropertyDeserializer() {
        super(I18NProperty.class);
    }

    @Override
    public I18NProperty<? extends Content<?>> deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
        I18NProperty<Content<?>> result = new I18NProperty<>();
        while(p.nextToken() != JsonToken.END_OBJECT) {
            String lang = p.getCurrentName();
            p.nextToken();              
-->         Object val = p.readValueAs(Object.class);

-->         I18NProperty.Content<?> c = new I18NProperty.Content<>();
            c.i18n = new Locale(lang);
-->         c.val = null;
            result.add(c);
        }
        return result;
    }
}

我用-->标记了行,我需要通用类型信息。

这必须以某种方式可能,因为通常我可以给杰克逊一个包含任何通用字段的任意类,它将正确地反序列化它。

先谢谢你,本杰明

你的代码可能无法正常工作,因为

  • public static class I18NPropertyDeserializer extends StdDeserializer<I18NProperty<? extends Content<?>>>

    确认Content public T val;的私有变量public T val; 也是内容,因为I18NProperty<T>public T val意味着I18NProperty<T>之间的任何东西都将是val的类型,并且你不想拥有类型内容的val! 你希望它是一个简单的对象,如String,Integer,Float ......

  • public class I18NProperty<T> extends ArrayList<T>意味着I18NProperty将扩展ArrayList<String> ,以防val是String ArrayList<Integer> ,如果val是Integer ...你明白我的意思。但是你想要什么是I18NProperty扩展Array<Content<T>>

所以我在类I18NProperty<T>中进行了更改,使其扩展了ArrayList<Content>

对于类I18NPropertyDeserialize我所做的是我使类通用,以获得有关public T val;类型T的信息public T val; 并且为了实例化类型T的类,我们应该有传递给构造函数I18NPropertyDeserializer(Class<T> clazz)类型的对象类,现在我们能够实例化将要放入Content.val

I18NPropertyDeserialize:

public class I18NPropertyDeserializer<T> extends
        StdDeserializer<I18NProperty<T>> {

    private Class<T> clazz;

    protected I18NPropertyDeserializer(Class<T> clazz) {
        super(I18NProperty.class);
        this.clazz = clazz;
    }

    @Override
    public I18NProperty<T> deserialize(JsonParser p,
            DeserializationContext ctxt) throws IOException,
            JsonProcessingException {
        I18NProperty<T> result = new I18NProperty<>();
        while (p.nextToken() != JsonToken.END_OBJECT) {
            String lang = p.getCurrentName();
            p.nextToken();
            T val;
            val = p.readValueAs(clazz);
            I18NProperty.Content<T> c = new I18NProperty.Content<>();
            c.i18n = new Locale(lang);
            c.val = val;
            result.add(c);
        }
        return result;
    }
}

I18NProperty

public class I18NProperty<T> extends ArrayList<I18NProperty.Content<T>> {
  public static class Content<T> {
    public Locale i18n;     
    public T val;
  }}

我认为你甚至不需要自定义JsonDeserializer来使它工作。 以下是如何解决这个问题的方法。 使用someMethod(String,T)上方的@JsonAnySetter注释创建对象。 像这样的东西:

//Need shape=JsonFormat.Shape.OBJECT to suppress default array deserialization behavior
@JsonFormat(shape=JsonFormat.Shape.OBJECT) 
public class I18NProperty<T> extends ArrayList<I18NProperty.Content<T>>{

    public static class Content<T> {
        public Locale i18n;
        public T val;
    }

    @JsonAnySetter
    public void set(String key, T value) {
        Content<T> c = new Content<T>();
        c.i18n = new Locale(key);
        c.val = value;
        this.add(c);
    }
}

你可以像这样使用它:

private static final String s = 
    "{\"en\": \"foo\", \"jp\": \"bar\"}";
private static final String s2 = 
    "{\"en\":{\"title\":\"f\",\"id\":1},\"jp\":{\"title\":\"b\",\"id\":2}}";

ObjectMapper mapper = new ObjectMapper();

I18NProperty<String> o1 =
    mapper.readValue(s, new TypeReference<I18NProperty<String>>(){});

I18NProperty<TestObject> o2 = 
    mapper.readValue(s2, new TypeReference<I18NProperty<TestObject>>(){});

简单易行。

如果要从您提供的json中删除name部分,还可以使用@JsonRootName(value = "name")注释,并且需要设置mapper.enable(DeserializationFeature.UNWRAP_ROOT_VALUE); 为您的ObjectMapper。 git repo中的示例。

这里是完整的git演示和其他示例+序列化代码: https//github.com/varren/JacksonDemo/tree/master/demo/src/main/java

日志演示

由于类型擦除,这通常不可能。

因为参数化类型的参数是通配符( ? extends Content<?>而不是T extends Content<T> ),我们所知道的只是它是一个Object,但我们什么都不知道。

即使假设您为类型命名并在创建反序列化器时,也为每种类型创建并注册了一个: new I18NPropertyDeserializer<String>()new I18NPropertyDeserializer<OtherObject>()等。在您的代码中,您只需要一个引用每个中的泛型类型T不是类对象,而是类型引用,你不能像类Object那样操作。

实现所需内容的最简单方法是在JSON表示中添加一个包含类型信息的附加字段,然后将Content类更改为非泛型并改为使用继承(例如StringContent extends Content,OtherContent extends Content,等等)。 杰克逊在这里有关于多态序列化的注释,以及如何在这里处理泛型的细节。

编辑: jackson处理这个的方式是通过使用DeserializerFactory,当你提供类型引用时,它为每个参数化类型生成一个新的反序列化器,例如:

List<String> values = MAPPER.readValue(jsonString, new TypeReference<List<String>>() {});

将动态地为每个集合类型生成一个新的Deserializer。 然后使用一堆反射生成正确类型的对象。 有关此示例,请查看BasicDeserializerFactory

在您的情况下,您可以在命名类型( T )而不是通配符( ? )上参数化StdDeserializer,然后创建自己的DeserializerFactory,它使用传入readValue调用的TypeReference / JavaType为每个给定类型返回正确的反序列化器。 这似乎是很多工作,如果你可以插入你自己的DerserializerFactory甚至不是很清楚(SimpleModule.setDeserializers会让你接近我认为)。

如果你可以枚举可能的不同内容类型的数量(即内容,内容,内容等),那么我只是尝试明确地注册所有这些内容并使用上面提到的继承方法。

TLDR: Jackson使用内置于Jackson核心的大量魔法进行收集,即虽然BasicDeserializerFactory不可插入(并且显式处理参数化的Collection / Map / Array类型但不包括其他类型)。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM