2016-12-07 59 views
2

我們從需要在我們的Java平臺上反序列化的外部C#系統以Json字符串的形式得到響應。由於C#字典序列化爲Json字符串的方式,這並不是直接的。這裏是如何在java和C#中序列化示例映射。通用json反序列化器將c#字典轉換爲java地圖

Java: {"key1":"value1","key2":"value2"} C#: [{"Key":"key1","Value":"value1"},{"Key":"key2","Value":"value2"}]

我們沒有對外部系統的任何控制,序列化格式不能在C#側被改變。我們得到的響應具有許多具有不同鍵值對類型的映射。所以,我試圖編寫一個自定義的泛型反序列化器,它從Json響應字符串構建所需的映射。我正在使用谷歌的Gson API。

這裏是我的嘗試:

private class DictionaryDeserializerGeneric<K,V> implements JsonDeserializer<Map<K, V>> { 

    @Override 
    public Map<K, V> deserialize(JsonElement json, Type type, 
      JsonDeserializationContext context) throws JsonParseException { 
     JsonArray jArray = (JsonArray)json; 
     Map<K, V> map = new HashMap<K, V>(); 
     for(int i=0;i<jArray.size();i++) { 
      JsonElement element = jArray.get(i); 
      Gson gson = new Gson(); 
      PairGeneric<K,V> pair = gson.fromJson(element, new TypeToken<PairGeneric<K,V>>() {}.getType()); 
      K key = pair.getKey(); 
      V value = pair.getValue(); 
      map.put(key, value); 
     } 
     return map; 
    }  
} 

private class PairGeneric<K,V> { 
    private K Key; 
    private V Value; 
    public K getKey() { 
     return Key; 
    } 
    public V getValue() { 
     return Value; 
    } 
} 

在我的解析器代碼,我下面

public static void main(String[] args) { 
    GsonBuilder gsonBuilder = new GsonBuilder(); 
    gsonBuilder.registerTypeAdapter(new TypeToken<ClassA, ClassB>(){}.getType(), new DictionaryDeserializer<ClassA, ClassB>()); 
    gsonBuilder.registerTypeAdapter(new TypeToken<ClassC, ClassD>(){}.getType(), new DictionaryDeserializer<ClassC, ClassD>()); 
    Gson gson = gsonBuilder.create(); 
    String responseStr = getResponse(); 
    ResponseClass responseObj = gson.fromJson(responseStr, ResponseClass.class); 
    doSomeOperation(responseObj); 
} 

public class ResponseClass { 
    Map<ClassA, ClassB> map1; 
    Map<ClassC, ClassC> map2; 
    //Other fields... 
    //Getter and setters... 
} 

的問題是,該類型是要去哪裏丟失,我看到下面的在使用responseObj對象時發生異常。一切似乎都被轉換爲LinkedHashMap類。

java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to ClassA

相同的代碼工作,如果我刪除泛型和創建多個解串器,每一個鍵,值對類型。我讀了關於Java類型擦除,但無法弄清楚它是如何造成這裏的問題。

有無論如何創建一個適用於所有鍵值類型的自定義反序列化器嗎?

回答

0

我無法重現你在談論上述的具體例外,但我想的第一件事就是new TypeToken<PairGeneric<K,V>>() {}.getType()因爲沒有具體類型K這裏V信息 - 他們只是在這個地方佔位符。但即使在這種情況下,類型標記(實際上是匿名類)的通用參數化也不會被擦除,並且可以在運行時讀取。如果你在調試器中看到這種類型的令牌狀態,你會發現它沒有具體的類型(只有橢圓表類型名稱KV),但是如果GSON知道它可以解析的類型的反序列化策略,它似乎忽略它們(如果你的地圖是參數化的,因爲它也可以在運行時讀取)。

考慮以下通用Pair類:

final class Pair<K, V> { 

    @SerializedName("Key") 
    private final K key = null; 

    @SerializedName("Value") 
    private final V value = null; 

    private Pair() { } 

    K getKey() { return key; } 

    V getValue() { return value; } 

} 

GSON支持SerializedName註釋,允許名稱替換,從而讓保持Java的命名約定的字段名。由於這是一個傳入的DTO類,因此它確信沒有代碼會自行實例化它,並且不提供公共構造函數。 GSON可以在沒有構造函數和訪問器的情況下工作,所以這個類可以是純粹只讀的。

常規字典JSON解串器

解串器下面可以不知曉具體類型的反序列化。主要區別在於它可以讓GSON爲Pair課程本身選擇策略。據我瞭解,GSON會根據DTO目標類型嘗試解析適當的解串器(您的響應對象具有參數化映射map1map2)。這些具體類型由GSON進一步處理。

final class GenericDictionaryJsonDeserializer<K, V> 
     implements JsonDeserializer<Map<K, V>> { 

    private static final JsonDeserializer<Map<Object, Object>> dictionaryJsonDeserializer = new GenericDictionaryJsonDeserializer<>(); 

    private GenericDictionaryJsonDeserializer() { 
    } 

    static <K, V> JsonDeserializer<Map<K, V>> getGenericDictionaryJsonDeserializer() { 
     @SuppressWarnings({ "unchecked", "rawtypes" }) 
     final JsonDeserializer<Map<K, V>> castDictionaryJsonDeserializer = (JsonDeserializer) dictionaryJsonDeserializer; 
     return castDictionaryJsonDeserializer; 
    } 

    @Override 
    public Map<K, V> deserialize(final JsonElement json, final Type type, final JsonDeserializationContext context) { 
     final JsonArray array = json.getAsJsonArray(); 
     final Map<K, V> result = new LinkedHashMap<>(); 
     for (int i = 0; i < array.size(); i++) { 
      final Pair<K, V> pair = context.deserialize(array.get(i), Pair.class); 
      result.put(pair.getKey(), pair.getValue()); 
     } 
     return result; 
    } 

} 

所有你在Gson情況下需要的是結合Map.class和通用字典JSON解串器。考慮遷移到另一個類,而不是Map(比方說interface IDictionary<K,V> extends Map<K,V>),以避免衝突,如果你想要一些地圖以「Java風格」反序列化,或者改進反序列化程序類。

有針對性的字典JSON解串器

如果當你細跟目前你要避免有針對性的課程的情況下,你可能想使用一個綁定到特定類型的替代品。

final class TargetedDictionaryJsonDeserializer<K, V> 
     implements JsonDeserializer<Map<K, V>> { 

    private final Type pairType; 

    private TargetedDictionaryJsonDeserializer(final Type pairType) { 
     this.pairType = pairType; 
    } 

    static <K, V> JsonDeserializer<Map<K, V>> getTargetedDictionaryJsonDeserializer(final TypeToken<Pair<K, V>> typeToken) { 
     return new TargetedDictionaryJsonDeserializer<>(typeToken.getType()); 
    } 

    @Override 
    public Map<K, V> deserialize(final JsonElement json, final Type type, final JsonDeserializationContext context) { 
     final JsonArray array = json.getAsJsonArray(); 
     final Map<K, V> result = new LinkedHashMap<>(); 
     for (int i = 0; i < array.size(); i++) { 
      final Pair<K, V> pair = context.deserialize(array.get(i), pairType); 
      result.put(pair.getKey(), pair.getValue()); 
     } 
     return result; 
    } 

} 

這些類型的適配器之間的區別是,後者接受具體類型的令牌(即完全由你的電話網站(見下面的演示)申報),並使用類型令牌類型將它委託給上下文反序列化。它確保在您需要時提供強大的型號安全性。

示範
private static final String JSON = "{" + 
     "'map1':[{'Key':'key1','Value':'value1'},{'Key':'key2','Value':'value2'}]," + 
     "'map2':[{'Key':'1','Value':'1.0'},{'Key':'2','Value':'2.0'}]" + 
     "}"; 

private static final TypeToken<Map<String, String>> stringToStringTypeToken = new TypeToken<Map<String, String>>() { 
}; 

private static final TypeToken<Map<Integer, Float>> integerToFloatTypeToken = new TypeToken<Map<Integer, Float>>() { 
}; 

private static final TypeToken<Pair<String, String>> stringToStringPairTypeToken = new TypeToken<Pair<String, String>>() { 
}; 

private static final TypeToken<Pair<Integer, Float>> integerToFloatPairTypeToken = new TypeToken<Pair<Integer, Float>>() { 
}; 

public static void main(final String... args) { 
    dump("by-targeted", getByTargetedTypeAdapter()); 
    dump("by-generic", getByGenericTypeAdapter()); 
    dump("by-mixed", getByMixTypeAdapter()); 
} 

private static Response getByTargetedTypeAdapter() { 
    return new GsonBuilder() 
      .registerTypeAdapter(stringToStringTypeToken.getType(), getTargetedDictionaryJsonDeserializer(stringToStringPairTypeToken)) 
      .registerTypeAdapter(integerToFloatTypeToken.getType(), getTargetedDictionaryJsonDeserializer(integerToFloatPairTypeToken)) 
      .create() 
      .fromJson(JSON, Response.class); 
} 

private static Response getByGenericTypeAdapter() { 
    return new GsonBuilder() 
      .registerTypeAdapter(Map.class, getGenericDictionaryJsonDeserializer()) 
      .create() 
      .fromJson(JSON, Response.class); 
} 

private static Response getByMixTypeAdapter() { 
    return new GsonBuilder() 
      .registerTypeAdapter(Map.class, getGenericDictionaryJsonDeserializer()) 
      .registerTypeAdapter(stringToStringTypeToken.getType(), getTargetedDictionaryJsonDeserializer(stringToStringPairTypeToken)) 
      .create() 
      .fromJson(JSON, Response.class); 
} 

private static final class Response { 

    private final Map<String, String> map1 = null; 
    private final Map<Integer, Float> map2 = null; 

} 

private static void dump(final String name, final Response response) { 
    out.print(">> "); 
    out.println(name); 
    out.print(" "); 
    out.println(response.map1); 
    out.print(" "); 
    out.println(response.map2); 
} 

的 「靶向」 模式轉到混凝土型適配器(看如何stringToStringPairTypeTokenintegerToFloatPairTypeToken被實例化)。請注意,GsonBuilder允許覆蓋,所以「混合」測試用例在通用測試用例之後指定了目標策略(否則總是會使用通用策略)。

所有測試用例解析JSON和打印以下輸出:

{鍵1 =值,鍵2 =值}
{1 = 1.0,2 = 2.0}

相關問題