简体   繁体   English

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

[英]Jackson: Deserialize Object to Array with Generics

I try to write a custom JsonDeserializer, but I can't figure out how to get the Generic type information of my class. 我尝试编写一个自定义的JsonDeserializer,但我无法弄清楚如何获取我的类的Generic类型信息。

I18NProperty.class: I18NProperty.class:

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

My desired JSON representation looks like this ( name is an instance of I18NProperty<Content<String>> ) : 我想要的JSON表示如下所示( nameI18NProperty<Content<String>>的实例):

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

Now I tried to write a JsonDeserializer and I'm able to read the field names and values, but I can't figure out how to create an instance of the generic type (in this example String ): 现在我尝试编写一个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;
    }
}

I marked the lines with --> where I need the Generic Type information. 我用-->标记了行,我需要通用类型信息。

This must be possible somehow, because normally I can give Jackson a arbitrary class which contains any generic fields and it will correctly deserialize it. 这必须以某种方式可能,因为通常我可以给杰克逊一个包含任何通用字段的任意类,它将正确地反序列化它。

Thanks in advance, Benjamin 先谢谢你,本杰明

your code probably won't work because 你的代码可能无法正常工作,因为

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

    affirm that the private variable of Content public T val; 确认Content public T val;的私有变量public T val; is also Content because I18NProperty<T> and public T val means that what ever goes between <> of I18NProperty is going to be the type of val, and you don't want to have val of type Content right!! 也是内容,因为I18NProperty<T>public T val意味着I18NProperty<T>之间的任何东西都将是val的类型,并且你不想拥有类型内容的val! you want it to be a simple object like String, Integer, Float ... 你希望它是一个简单的对象,如String,Integer,Float ......

  • public class I18NProperty<T> extends ArrayList<T> means that I18NProperty is going to extend ArrayList<String> in case val is String ArrayList<Integer> in case val is Integer ... you get it what i mean .But what you want is that I18NProperty extends Array<Content<T>> public class I18NProperty<T> extends ArrayList<T>意味着I18NProperty将扩展ArrayList<String> ,以防val是String ArrayList<Integer> ,如果val是Integer ...你明白我的意思。但是你想要什么是I18NProperty扩展Array<Content<T>>

So i made a change in the class I18NProperty<T> to make it extends ArrayList<Content> . 所以我在类I18NProperty<T>中进行了更改,使其扩展了ArrayList<Content>

For the class I18NPropertyDeserialize what i have done is i made the class generic to have information about type T of public T val; 对于类I18NPropertyDeserialize我所做的是我使类通用,以获得有关public T val;类型T的信息public T val; and in order to instantiate the class of type T we should have object class for that type passed to the constructor I18NPropertyDeserializer(Class<T> clazz) now we are able to instantiate the object wich is going to be put in Content.val 并且为了实例化类型T的类,我们应该有传递给构造函数I18NPropertyDeserializer(Class<T> clazz)类型的对象类,现在我们能够实例化将要放入Content.val

I18NPropertyDeserialize : 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 I18NProperty

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

I think you don't even need custom JsonDeserializer to make it work. 我认为你甚至不需要自定义JsonDeserializer来使它工作。 Here is how you can solve this. 以下是如何解决这个问题的方法。 Create your objects with @JsonAnySetter annotation above someMethod(String, T). 使用someMethod(String,T)上方的@JsonAnySetter注释创建对象。 Something like this: 像这样的东西:

//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);
    }
}

And you can use it like this: 你可以像这样使用它:

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>>(){});

Simple and easy. 简单易行。

If you want to remove name part from json you provided, you can also use @JsonRootName(value = "name") annotation above I18NProperty and will need to set mapper.enable(DeserializationFeature.UNWRAP_ROOT_VALUE); 如果要从您提供的json中删除name部分,还可以使用@JsonRootName(value = "name")注释,并且需要设置mapper.enable(DeserializationFeature.UNWRAP_ROOT_VALUE); for your ObjectMapper. 为您的ObjectMapper。 Example in git repo. git repo中的示例。

And here is full working git demo and additional examples + serialisation code: https://github.com/varren/JacksonDemo/tree/master/demo/src/main/java 这里是完整的git演示和其他示例+序列化代码: https//github.com/varren/JacksonDemo/tree/master/demo/src/main/java

日志演示

This is not generally possible due to type erasure. 由于类型擦除,这通常不可能。

Because the parameters of your parameterized types are wild carded ( ? extends Content<?> instead of T extends Content<T> ), all we know is that it is an Object, but we know nothing else. 因为参数化类型的参数是通配符( ? extends Content<?>而不是T extends Content<T> ),我们所知道的只是它是一个Object,但我们什么都不知道。

Even assuming that you named your types and when you created your deserializer, you created and registered one for each type: new I18NPropertyDeserializer<String>() , new I18NPropertyDeserializer<OtherObject>() , etc. In your code you would just have a reference to the generic type T in each of these which is not a class object but a type reference which you cannot manipulate like an class Object. 即使假设您为类型命名并在创建反序列化器时,也为每种类型创建并注册了一个: new I18NPropertyDeserializer<String>()new I18NPropertyDeserializer<OtherObject>()等。在您的代码中,您只需要一个引用每个中的泛型类型T不是类对象,而是类型引用,你不能像类Object那样操作。

The most naive way to accomplish what you want is to add an additional field into your JSON representation which includes type information, and then change your Content class to be non-generic and use inheritance instead (eg. StringContent extends Content, OtherContent extends Content, etc). 实现所需内容的最简单方法是在JSON表示中添加一个包含类型信息的附加字段,然后将Content类更改为非泛型并改为使用继承(例如StringContent extends Content,OtherContent extends Content,等等)。 Jackson has notes on polymorphic serialization here , and details on how to deal with generics here . 杰克逊在这里有关于多态序列化的注释,以及如何在这里处理泛型的细节。

EDIT: The way that jackson handles this is through the use of a DeserializerFactory which generates a new deserializer for each parameterized type when you provide a type reference, for example: 编辑: jackson处理这个的方式是通过使用DeserializerFactory,当你提供类型引用时,它为每个参数化类型生成一个新的反序列化器,例如:

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

Will generate a new Deserializer specifically for each collection type on the fly. 将动态地为每个集合类型生成一个新的Deserializer。 Then using a bunch of reflection it generates objects of the correct type. 然后使用一堆反射生成正确类型的对象。 For an example of this take a look at BasicDeserializerFactory . 有关此示例,请查看BasicDeserializerFactory

In your case, you could parameterize your StdDeserializer on a named type ( T ) rather than wildcards ( ? ) and then create your own DeserializerFactory which uses the TypeReference/JavaType passed in to the readValue call to return the proper deserializer for each given type. 在您的情况下,您可以在命名类型( T )而不是通配符( ? )上参数化StdDeserializer,然后创建自己的DeserializerFactory,它使用传入readValue调用的TypeReference / JavaType为每个给定类型返回正确的反序列化器。 This seems like it would be a lot of work and it's not even really clear if you can plug in your own DerserializerFactory (SimpleModule.setDeserializers would get you close I think). 这似乎是很多工作,如果你可以插入你自己的DerserializerFactory甚至不是很清楚(SimpleModule.setDeserializers会让你接近我认为)。

If you can enumerate the number of different content types that are possible (ie. Content, Content, Content, etc) then I would just go the route of trying register all of them explicitly and use the inheritance method mentioned above. 如果你可以枚举可能的不同内容类型的数量(即内容,内容,内容等),那么我只是尝试明确地注册所有这些内容并使用上面提到的继承方法。

TLDR: Jackson does it for collections using lots of magic which is built into Jackson core, namely though BasicDeserializerFactory which is not pluggable (and explicitly handles parameterized Collection/Map/Array types but not other types). TLDR: Jackson使用内置于Jackson核心的大量魔法进行收集,即虽然BasicDeserializerFactory不可插入(并且显式处理参数化的Collection / Map / Array类型但不包括其他类型)。

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

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