使用Gson序列化含非基本类型键HashMap的类时遇编译错误求助
问题:Gson序列化含非基本类型键的HashMap时键显示哈希字符串且编译报错
我想用Gson把包含非基本类型键(Tile对象)的HashMap的Java类序列化为JSON,但默认情况下Gson会将HashMap的键转为哈希字符串(比如dir.Tile@6996db8)。参考官方的MapTypeAdapterFactory实现时,编译报错:
java: cannot find symbol symbol: class TypeAdapterRuntimeTypeWrapper location: class TypeAdapters.MapTypeAdapterFactory.Adapter<K,V>
原始代码
public class App { public static void main(String[] args) throws Exception { Gson gson = new GsonBuilder().setPrettyPrinting().create(); GameController gameController = new GameController(); System.out.println(gson.toJson(gameController)); } } class GameController { HashMap<Tile, TileState> map = new HashMap<>(); public GameController() { map.put(new Tile(), TileState.VISIBLE); map.put(new Tile(), TileState.REVEALED); } } class Tile { @Override public int hashCode() { return super.hashCode(); } } enum TileState { REVEALED, VISIBLE }
默认序列化输出
{ "map": { "dir.Tile@6996db8": "REVEALED", "dir.Tile@368102c8": "VISIBLE" } }
解决方案
核心原因
TypeAdapterRuntimeTypeWrapper是Gson内部私有类,不属于公开API,直接复制官方的MapTypeAdapterFactory代码会因无法引用该类导致编译错误。我们需要自定义TypeAdapter来处理非基本类型键的Map序列化。
步骤1:完善Tile类(保证HashMap正确性+可序列化)
你的Tile类目前没有业务属性,且hashCode直接继承自Object,会导致每个Tile对象都是唯一键(即使逻辑上相同)。先完善Tile类:
import java.util.Objects; class Tile { // 添加业务标识属性,比如坐标 private int x; private int y; public Tile() {} public Tile(int x, int y) { this.x = x; this.y = y; } // Getter & Setter public int getX() { return x; } public void setX(int x) { this.x = x; } public int getY() { return y; } public void setY(int y) { this.y = y; } // 重写equals和hashCode,保证HashMap键的正确性 @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Tile tile = (Tile) o; return x == tile.x && y == tile.y; } @Override public int hashCode() { return Objects.hash(x, y); } }
步骤2:自定义Map TypeAdapter
针对Map<Tile, TileState>编写自定义适配器,实现键的正确序列化与反序列化:
import com.google.gson.TypeAdapter; import com.google.gson.Gson; import com.google.gson.stream.JsonReader; import com.google.gson.stream.JsonWriter; import java.io.IOException; import java.util.HashMap; import java.util.Map; public class TileMapTypeAdapter extends TypeAdapter<Map<Tile, TileState>> { private final Gson gson = new Gson(); @Override public void write(JsonWriter out, Map<Tile, TileState> map) throws IOException { out.beginObject(); for (Map.Entry<Tile, TileState> entry : map.entrySet()) { // 将Tile序列化为JSON字符串作为键 String keyJson = gson.toJson(entry.getKey()); out.name(keyJson); gson.toJson(entry.getValue(), TileState.class, out); } out.endObject(); } @Override public Map<Tile, TileState> read(JsonReader in) throws IOException { Map<Tile, TileState> map = new HashMap<>(); in.beginObject(); while (in.hasNext()) { // 将键的JSON字符串反序列化为Tile对象 String keyJson = in.nextName(); Tile tile = gson.fromJson(keyJson, Tile.class); TileState state = gson.fromJson(in, TileState.class); map.put(tile, state); } in.endObject(); return map; } }
步骤3:注册适配器并使用
在创建Gson实例时注册自定义适配器,同时更新GameController的构造方法:
import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.reflect.TypeToken; public class App { public static void main(String[] args) throws Exception { Gson gson = new GsonBuilder() .setPrettyPrinting() // 注册自定义适配器 .registerTypeAdapter(new TypeToken<Map<Tile, TileState>>(){}.getType(), new TileMapTypeAdapter()) .create(); GameController gameController = new GameController(); System.out.println(gson.toJson(gameController)); } } class GameController { HashMap<Tile, TileState> map = new HashMap<>(); public GameController() { // 使用带属性的Tile对象 map.put(new Tile(0, 0), TileState.VISIBLE); map.put(new Tile(1, 1), TileState.REVEALED); } } enum TileState { REVEALED, VISIBLE }
可选优化:将Map序列化为数组结构
如果不希望用JSON字符串作为键(可读性较差),可以修改TypeAdapter的write方法,将Map转为数组结构:
@Override public void write(JsonWriter out, Map<Tile, TileState> map) throws IOException { out.beginArray(); for (Map.Entry<Tile, TileState> entry : map.entrySet()) { out.beginObject(); out.name("tile"); gson.toJson(entry.getKey(), Tile.class, out); out.name("state"); gson.toJson(entry.getValue(), TileState.class, out); out.endObject(); } out.endArray(); }
对应的输出会更直观:
{ "map": [ { "tile": { "x": 0, "y": 0 }, "state": "VISIBLE" }, { "tile": { "x": 1, "y": 1 }, "state": "REVEALED" } ] }
内容的提问来源于stack exchange,提问作者Reza Masoumi
相关产品推荐
相关产品推荐

