2013-10-10 39 views
3

這是一個狹義的例子,如此荒謬,但卻證明了問題。GSON無法反序列化它之前已經序列化的對象:期望的BEGIN_OBJECT,但是STRING

以下代碼:

import java.util.*; 
import com.google.gson.*; 

class X {} 

class SomeType { 
    private Map <X, String> map; 
    public SomeType() { 
     this.map = new HashMap<X, String>(); 
     map.put(new X(), "b"); 
    } 
} 

public class FooMain { 

    private static Gson gson = new GsonBuilder().serializeNulls().create(); 

    public static void main(String args[]) throws Exception { 
     String foo = gson.toJson(new SomeType(), SomeType.class); 
     System.out.println(foo);       // line 20 
     SomeType st = gson.fromJson(foo, SomeType.class); // line 21 
    } 
} 

失敗:

[java] Exception in thread "main" com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Expected BEGIN_OBJECT but was STRING at line 1 column 20 
[java]  at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:176) 
[java]  at com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.read(TypeAdapterRuntimeTypeWrapper.java:40) 
[java]  at com.google.gson.internal.bind.MapTypeAdapterFactory$Adapter.read(MapTypeAdapterFactory.java:187) 
[java]  at com.google.gson.internal.bind.MapTypeAdapterFactory$Adapter.read(MapTypeAdapterFactory.java:146) 
[java]  at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.read(ReflectiveTypeAdapterFactory.java:93) 
[java]  at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:172) 
[java]  at com.google.gson.Gson.fromJson(Gson.java:795) 
[java]  at com.google.gson.Gson.fromJson(Gson.java:761) 
[java]  at com.google.gson.Gson.fromJson(Gson.java:710) 
[java]  at com.google.gson.Gson.fromJson(Gson.java:682) 
[java]  at FooMain.main(FooMain.java:21) 
[java] Caused by: java.lang.IllegalStateException: Expected BEGIN_OBJECT but was STRING at line 1 column 20 
[java]  at com.google.gson.stream.JsonReader.expect(JsonReader.java:339) 
[java]  at com.google.gson.stream.JsonReader.beginObject(JsonReader.java:322) 
[java]  at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:165) 
[java]  ... 10 more 

而線20打印:

{"map":{"[email protected]":"b"}} 

回答

2

Gson串行化一個Map通過使用該密鑰作爲JSON鍵和地圖元素的值作爲值。因爲您的X類沒有自定義覆蓋toString()方法,所以它使用Object#toString()並將其序列化爲[email protected],但無法對其進行反序列化。即使您提供了toString(),它實際上也無法將其反序列化。

我想你已經找到了一個邊緣情況,你不能正確序列化一切。 JSON對象的關鍵字必須是String

+0

那麼,什麼是一個在這種情況下怎麼辦? –

+0

@MarcusJuniusBrutus改變你的設計,一個鍵(名字)必須是一個字符串。你需要實現一個自定義的反序列化器,它可以理解一個對象的String格式並從中創建一個'X'對象。 –

+0

我能夠使用自定義反序列化器實現該功能,但是當我嘗試使用完整適配器('JsonSerializer'和'JsonDeserializer')以便爲其他目的「釋放」toString()'方法時,GSON始終使用' toString()'並忽略了'Map'鍵類的自定義序列化程序。這是你的理解嗎?即您必須使用'toString()'而不是使用自定義序列化器? –

0

好了,感謝索蒂里奧斯Delimanolis答案,我發現:

  • 你可以提供一個自定義解串器只爲Map重點班。在這種情況下該鍵類的toString方法提供的「系列化」的格式

  • 你可以爲Map類本身的通用適配器(串行+解串器),再加上通用適配器(串行器+解串器)用於Map密鑰類。此方法的唯一優點是密鑰類的toString方法可以用於其他目的,並且不必與解串器一起玩。

以下是第二種情況的完整代碼:

import java.util.*; 
import com.google.gson.*; 
import java.lang.reflect.Type; 

import org.apache.commons.lang3.StringUtils; 

class X { 
    public int x; 
    public X(int x) { 
     this.x = x; 
    } 
    public String toString() { 
     return String.format("boo ha ha %d", x); 
    } 
} 

class SomeType { 
    private Map <X, String> map; 
    public SomeType() { 
     this.map = new HashMap<X, String>(); 
     map.put(new X(2), "b"); 
    } 
    public String toString() { 
     List<String> rv = new ArrayList<>(); 
     for (X x : map.keySet()) 
      rv.add(String.format("%s -> %s\n", x.toString(), map.get(x))); 
     return StringUtils.join(rv, "\n"); 
    } 
} 

public class FooMain { 

    public static void main(String args[]) throws Exception { 
     GsonBuilder gsonBuilder = new GsonBuilder(); 
     gsonBuilder.registerTypeAdapter(X.class, new XAdapter()); 
     gsonBuilder.registerTypeAdapter(Map.class, new MapAdapter()); 
     Gson gson = gsonBuilder.serializeNulls().create(); 
     SomeType original = new SomeType(); 
     System.out.println("original is: "+original); 
     String foo = gson.toJson(original, SomeType.class); 
     System.out.println("JSON form is: "+foo);      
     SomeType reconstructed = gson.fromJson(foo, SomeType.class); 
     System.out.println("reconstructed is: "+reconstructed); 
    } 
} 


class MapAdapter implements JsonSerializer<Map<?, ?>>, JsonDeserializer<Map<?, ?>> { 

    @Override 
    public JsonElement serialize(Map<?, ?> m, Type typeOfT, JsonSerializationContext context) { 
     JsonArray rv = new JsonArray(); 
     for (Object k : m.keySet()) { 
      JsonObject kv = new JsonObject(); 
      kv.add  ("k"  , context.serialize(k)); 
      kv.addProperty("ktype" , k.getClass().getName()); 
      kv.add  ("v"  , context.serialize(m.get(k))); 
      kv.addProperty("vtype" , m.get(k).getClass().getName()); 
      rv.add(kv); 
     } 
     return rv; 
    } 

    @Override 
    public Map<?, ?> deserialize(JsonElement _json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { 
     JsonArray json = (JsonArray) _json; 
     Map<Object, Object> rv = new HashMap<>(); 
     for (int i = 0 ; i < json.size() ; i++) { 
      JsonObject o = (JsonObject) json.get(i); 
      String ktype = o.getAsJsonPrimitive("ktype").getAsString(); 
      String vtype = o.getAsJsonPrimitive("vtype").getAsString(); 
      Class<?> kklass = null; 
      Class<?> vklass = null; 
      try { 
       kklass = Class.forName(ktype); 
       vklass = Class.forName(vtype); 
      } catch (ClassNotFoundException e) { 
       e.printStackTrace(); 
       throw new JsonParseException(e.getMessage()); 
      } 
      Object k = context.deserialize(o.get("k"), kklass); 
      Object v = context.deserialize(o.get("v"), vklass); 
      rv.put(k, v); 
     } 
     return rv; 
    } 

} 


class XAdapter implements JsonSerializer<X>, JsonDeserializer<X> { 

    @Override 
    public JsonElement serialize(X x, Type typeOfT, JsonSerializationContext context) { 
     return context.serialize(x.x); 
    } 

    @Override 
    public X deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { 
     String s = json.getAsString(); 
     int x = Integer.valueOf(s); 
     return new X(x); 
    } 
}