2017-01-04 66 views
3

我想使用Gson反序列化一個響應。數據由可以嵌套到任意深度的節點列表組成。 JSON的看起來是這樣的:Gson:如何處理可能有不同類型的字段?

{ 
    "type": "node", 
    "children": [ 
     { 
      "id": "abc123", 
      "name": "Name 1", 
      "subdata": { 
       "type": "node", 
       "children": [ 
        { 
         "id": "def456", 
         "name": "Name 2" 
        } 
       ] 
      } 
     } 
    ] 
} 

現在,沒有任何自定義類型的適配器,我可以使這項工作有以下類別:

public class Data { 
    private String type; 
    private List<Node> nodes; 
} 

public class Node { 
    private String id; 
    private String name; 
    private Data subdata; 
} 

一切正常,很正常,現在。但是,服務器可能會削減一些更深層次的節點,並且只是與自己的ID響應,所以subdata可能是這樣的,而不是:

"subdata": { 
    "type": "extra", 
    "children": ["ghi", "jkl", "mno"] 
} 

這當然可以表示爲一個Java類是這樣的:

public class ExtraData { 
    private String type; 
    private List<String> children; 
} 

問題是,但是:如何處理反序列化,以便subdata可以是DataExtraData

+0

我前段時間和傑克遜做了類似的事情:http://stackoverflow.com/a/12459070/823393 – OldCurmudgeon

回答

1

給定節點的孩子總是看起來像是JSON數組,所以你可以用它們做的第一件事就是將子女聲明爲隱藏實際類型的List<?>。但是,您仍然擁有type屬性/字段,這對於獲取子類的實際類型來說非常合適。最簡單的方法可能就是添加另一個JSON解串器,以反序列化具有一些性能成本的Data實例(因爲它們不是類型適配器),並且據我所知,Data類的字段上缺少@SerializedName

如果你也沒事改變你的DTO的類型,喜歡枚舉而不是原始字符串,因爲他們的工作只是完美的使用枚舉(尤其是與智能的IDE合作):

enum Type { 

    @SerializedName("node") 
    NODE, 

    @SerializedName("extra") 
    EXTRA 

} 

Data類本身則可能看起來像如下:

final class Data { 

    private final Type type; 
    private final List<?> children; // this one is supposed to be: 
            // * either List<String> if type=EXTRA 
            // * or List<Node> if type=NODE 

    Data(final Type type, final List<?> children) { 
     this.type = type; 
     this.children = children; 
    } 

    Type getType() { 
     return type; 
    } 

    List<?> getChildren() { 
     return children; 
    } 

} 

由於extra -typed孩子只是在你的問題的字符串,只需添加節點DTO類:

final class Node { 

    @SerializedName("id") 
    private final String id = null; 

    @SerializedName("name") 
    private final String name = null; 

    @SerializedName("subdata") 
    private final Data subdata = null; 

    String getId() { 
     return id; 
    } 

    String getName() { 
     return name; 
    } 

    Data getSubdata() { 
     return subdata; 
    } 

} 

現在,在反序列化Data類時,可以確定子列表的實際類型,並根據節點類型將其反序列化爲字符串列表或節點列表。請注意,下面的解串器使用java.lang.reflect.Type實例而不是java.lang.Class,因爲後者由於Java通用類型擦除而很弱,並且對於任何列表參數化(字符串,節點等)而言都是List.class。爲類型標記提供期望的類型,只需將JSON鍵/值對委派給指定目標類型的反序列化上下文,從而進行適用於任意嵌套元素級別的遞歸反序列化(但是,GSON的某些內部堆棧限制僅限於32如果我沒錯的話)。

final class DataJsonDeserializer 
     implements JsonDeserializer<Data> { 

    private static final JsonDeserializer<Data> dataJsonDeserializer = new DataJsonDeserializer(); 

    private static final java.lang.reflect.Type nodeListType = new TypeToken<List<Node>>() { 
    }.getType(); 

    private static final java.lang.reflect.Type stringListType = new TypeToken<List<String>>() { 
    }.getType(); 

    private DataJsonDeserializer() { 
    } 

    static JsonDeserializer<Data> getDataJsonDeserializer() { 
     return dataJsonDeserializer; 
    } 

    @Override 
    public Data deserialize(final JsonElement jsonElement, final java.lang.reflect.Type type, final JsonDeserializationContext context) 
      throws JsonParseException { 
     final JsonObject rootJsonObject = jsonElement.getAsJsonObject(); 
     final Type nodeType = context.deserialize(rootJsonObject.get("type"), Type.class); 
     final JsonArray childrenJsonArray = rootJsonObject.get("children").getAsJsonArray(); 
     final List<?> children; 
     switch (nodeType) { 
     case NODE: 
      children = context.deserialize(childrenJsonArray, nodeListType); 
      break; 
     case EXTRA: 
      children = context.deserialize(childrenJsonArray, stringListType); 
      break; 
     default: 
      throw new AssertionError(nodeType); 
     } 
     return new Data(nodeType, children); 
    } 

} 

這遞歸通過孩子走(注意加強for語句,下面投每個項目爲目標類型)演示:

public final class EntryPoint { 

    private static final String JSON_WITH_SUBNODES = "{\"type\":\"node\",\"children\":[{\"id\":\"abc123\",\"name\":\"Name 1\",\"subdata\":{\"type\":\"node\",\"children\":[{\"id\":\"def456\",\"name\":\"Name 2\"}]}}]}"; 
    private static final String JSON_WITH_REFERENCES = "{\"type\":\"node\",\"children\":[{\"id\":\"abc123\",\"name\":\"Name 1\",\"subdata\":{\"type\":\"extra\",\"children\":[\"ghi\",\"jkl\",\"mno\"]}}]}"; 

    private static final Gson gson = new GsonBuilder() 
      .registerTypeAdapter(Data.class, getDataJsonDeserializer()) 
      .create(); 

    public static void main(final String... args) { 
     process(gson.fromJson(JSON_WITH_SUBNODES, Data.class)); 
     process(gson.fromJson(JSON_WITH_REFERENCES, Data.class)); 
    } 

    private static void process(final Data data) { 
     process(data, 0); 
     out.println(); 
    } 

    private static void process(final Data data, final int level) { 
     for (int i = 0; i < level; i++) { 
      out.print('>'); 
     } 
     final List<?> children = data.getChildren(); 
     final Type type = data.getType(); 
     out.println(type); 
     switch (type) { 
     case NODE: 
      @SuppressWarnings("unchecked") 
      final Iterable<Node> nodeChildren = (Iterable<Node>) children; 
      for (final Node node : nodeChildren) { 
       out.printf("\t%s %s\n", node.getId(), node.getName()); 
       final Data subdata = node.getSubdata(); 
       if (subdata != null) { 
        process(subdata, level + 1); 
       } 
      } 
      break; 
     case EXTRA: 
      @SuppressWarnings("unchecked") 
      final Iterable<String> extraChildren = (Iterable<String>) children; 
      for (final String extra : extraChildren) { 
       out.printf("\t%s\n", extra); 
      } 
      break; 
     default: 
      throw new AssertionError(type); 
     } 
    } 

} 

輸出:

NODE 
    abc123 Name 1 
>NODE 
    def456 Name 2 

NODE 
    abc123 Name 1 
>EXTRA 
    ghi 
    jkl 
    mno 
+0

非常感謝!這似乎正是我正在尋找的。 – manabreak

+0

@manabreak不客氣! :) –

相關問題