2015-04-05 50 views
0

这里的String数组的地图是完整的错误我收到:IllegalStateException异常 - 序列化使用GSON

com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Expected BEGIN_ARRAY but was STRING at line 1 column 3 path $. 

我想编码的地图是

HashMap<String[],Boolean>. 

我用这个来序列化地图:

Gson gson = new Gson(); 
gson.toJson(object); 

,这反序列化对象:

json.fromJson(encodedObject, new TypeToken<HashMap<String[], Boolean>>(){}.getType()) 

这是原始的JSON输出:

{ 
    "[Ljava.lang.String;@433fe238":false, 
    "[Ljava.lang.String;@433feb38":false, 
    "[Ljava.lang.String;@433fe018":false, 
    "[Ljava.lang.String;@433fd618":false 
} 

我有我以类似的方式编码和解码等地图,但是这是给我的问题只有一个。有没有办法做到这一点?

+0

我不能帮你解决Gson问题,因为我没有经验,但是我确实看到了一个你可能想要解决的关键问题。一个数组不能很好地用作'HashMap'键。数组没有一个表现良好的'hashCode()'和'equals()'。使用'列表'。 – 2015-04-05 23:32:39

+0

无论如何,我谷歌搜索和你的问题似乎是这样的:http://stackoverflow.com/questions/25522309/converting-json-between-string-and-byte-with-gson – 2015-04-05 23:34:14

+0

谢谢你的抬头。这是我正在尝试修复的旧代码,如果没有某些问题,我无法真正更改数据结构。当我编写它时,我有点新手:) – eBehbahani 2015-04-05 23:35:13

回答

2

这是不幸的,你不能改变的数据结构件的类型。您应该永远不会将数组存储为键,因为它们不会覆盖equals,hashcode和toString。

那就说让我们来谈谈你的错误。这是由于Gson将密钥序列化为字符串,因为JSON语法要求在键值对中密钥必须是字符串。但是你期望一个数组作为关键字,因此解析器会抛出错误。

由于不能改变结构,可以编写自定义串行器和解串来处理这个问题:

class MapSerializer implements JsonSerializer<Map<String[], Boolean>> { 
    @Override 
    public JsonElement serialize(Map<String[], Boolean> src, Type typeOfSrc, JsonSerializationContext context) { 
     JsonObject obj = new JsonObject(); 
     for(Map.Entry<String[], Boolean> entry : src.entrySet()) { 
      obj.addProperty(Arrays.toString(entry.getKey()), entry.getValue()); 
     } 
     return obj; 
    } 
} 

class MapDeserializer implements JsonDeserializer<Map<String[], Boolean>> { 
    @Override 
    public Map<String[], Boolean> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { 
     Map<String[],Boolean> map = new HashMap<>(); 
     JsonObject obj = json.getAsJsonObject(); 
     for(Map.Entry<String, JsonElement> entry : obj.entrySet()) { 
      String s = entry.getKey(); 
      //See how you're dependent of the String representation given by Arrays.toString? 
      //Not very convenient. 
      map.put(s.substring(1, s.length()-1).split(", "), entry.getValue().getAsBoolean()); 
     } 
     return map; 
    } 
} 

基本上键由Arrays.toString,这是阵列中的元件的可读表示现在给出,而不是其toString方法。

但是,正如你所看到的,反序列化过程必须假设这种表示不会改变,这是相当危险的,因为Arrays.toString的结果中的任何修改都会打破这个过程,但是你必须处理它...(虽然这不太可能发生,但这是一个事实,你必须知道)

为了使这项工作,您需要在解析器中注册适配器。

Map<String[],Boolean> map = new HashMap<>(); 
map.put(new String[]{"Hello"},false); 
map.put(new String[]{"Stack", "Overflow"},true); 

Type t = new TypeToken<Map<String[], Boolean>>(){}.getType(); 

Gson gson = new GsonBuilder().registerTypeAdapter(t, new MapSerializer()) 
          .registerTypeAdapter(t, new MapDeserializer()) 
          .setPrettyPrinting() 
          .create(); 

String repr = gson.toJson(map, t); 
Map<String[], Boolean> map2 = gson.fromJson(repr, t); 

如果您尝试打印repr,你会得到:

{ 
    "[Hello]": false, 
    "[Stack, Overflow]": true 
} 

比你原来的输出更好。你回到与fromJson(我引用“相同”,因为按键是等于根据Arrays.equals而不是.equals())相同的地图。

希望它有帮助!

+0

所以我确实玩过这一点,并且实际上最终改变了结构,从地图到列表。这似乎工作,即使列表仍然包含字符串数组。 – eBehbahani 2015-04-06 14:54:07

+0

@rioneye如果使用Map更方便,你可以在数组周围创建一个包装类,用'Arrays.toString'覆盖toString,用'Arrays.hashCode'代替hashcode,用Arrays.equals代替equals。 – 2015-04-06 14:58:58

+0

@rioneye然后你只需编写反序列化器,你就可以控制包装器的'toString'实现。而'map.equals(map2)'当然会给你真实的。如果需要,你可以在那里找到一个实现:https://gist.github.com/alexcrt/3585ca1916a426a816c7 – 2015-04-06 15:05:49

0

也许你应该写一个toString()方法String[],或者你只会得到的每一个地址的String []