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

时间:2016-02-10 13:59:49

标签: java json generics jackson

我尝试编写一个自定义的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;
    }
}

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

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

提前致谢, 本杰明

3 个答案:

答案 0 :(得分:4)

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

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

    确认内容public T val;的私有变量也是内容,因为I18NProperty<T>public T val表示介于&lt;&gt;之间的内容I18NProperty将成为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的信息,并且为了实例化类型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;
  }}

答案 1 :(得分:2)

我认为您甚至不需要自定义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部分,你也可以在I18NProperty上面使用@JsonRootName(value = "name")注释,并且需要为你的ObjectMapper设置mapper.enable(DeserializationFeature.UNWRAP_ROOT_VALUE);。 git repo中的示例。

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

log demo

答案 2 :(得分:0)

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

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

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

实现所需内容的最天真的方法是在JSON表示中添加一个包含类型信息的附加字段,然后将您的Content类更改为非泛型并改为使用继承(例如StringContent extends Content,OtherContent扩展内容等)。杰克逊在polymorphic serialization here上有注释,详情为on how to deal with generics here

编辑: jackson处理此问题的方法是使用DeserializerFactory,当您提供类型引用时,它会为每个参数化类型生成一个新的反序列化器,例如:

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

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

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

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

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