簡體   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