Java版Endpoints Framework中Map<GR2, Double>的客户端库适配问题
这个问题我之前也踩过坑!根源在于Google Cloud Endpoints底层用Jackson处理JSON序列化,而不是你用来生成键的Gson,Jackson默认不知道怎么把你用Gson生成的GR2 JSON字符串反序列化为GR2对象作为Map的键,所以才会抛出Can not find a (Map) Key deserializer for type [simple type, class GR2]这个错误。
下面给你两种可行的解决方案,推荐第一种,更优雅:
方案一:给GR2类添加Jackson的自定义键序列化/反序列化器
步骤1:完善GR2类的基础方法
首先要确保GR2类有正确的equals()、hashCode()和toString()方法——这不仅是Map键的核心要求,也能让序列化后的字符串唯一标识对象:
import com.google.gson.Gson; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import java.io.Serializable; import java.util.Objects; @JsonSerialize(keyUsing = GR2KeySerializer.class) @JsonDeserialize(keyUsing = GR2KeyDeserializer.class) public class GR2 implements Serializable { private double prop1; private double prop2; private double prop3; private double prop4; // 构造器、getter、setter自行补充 @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; GR2 gr2 = (GR2) o; return Double.compare(gr2.prop1, prop1) == 0 && Double.compare(gr2.prop2, prop2) == 0 && Double.compare(gr2.prop3, prop3) == 0 && Double.compare(gr2.prop4, prop4) == 0; } @Override public int hashCode() { return Objects.hash(prop1, prop2, prop3, prop4); } @Override public String toString() { // 用Gson把对象转成JSON字符串,保证唯一且可反序列化 return new Gson().toJson(this); } }
步骤2:编写自定义键序列化器
这个类负责把GR2对象序列化为Jackson能识别的键字符串:
import com.google.gson.Gson; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.SerializerProvider; import java.io.IOException; public class GR2KeySerializer extends JsonSerializer<GR2> { private final Gson gson = new Gson(); @Override public void serialize(GR2 value, JsonGenerator gen, SerializerProvider serializers) throws IOException { // 把GR2转成JSON字符串作为Map的键 gen.writeFieldName(gson.toJson(value)); } }
步骤3:编写自定义键反序列化器
这个类负责把JSON字符串反序列回GR2对象:
import com.google.gson.Gson; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.KeyDeserializer; import java.io.IOException; public class GR2KeyDeserializer extends KeyDeserializer { private final Gson gson = new Gson(); @Override public Object deserializeKey(String key, DeserializationContext ctxt) throws IOException { // 把JSON字符串转成GR2对象 return gson.fromJson(key, GR2.class); } }
步骤4:关联注解
回到GR2类,添加上面写的@JsonSerialize和@JsonDeserialize注解(已经在步骤1的代码里标注了),这样Jackson处理Map<GR2, Double>时,就会用你自定义的逻辑来处理键的序列化和反序列化。
方案二:改用字符串作为Map键(简易替代)
如果不想写自定义序列化器,也可以把Map<GR2, Double>改成Map<String, Double>,在业务代码里手动处理GR2和字符串的转换:
- 存数据时:用Gson把GR2转成JSON字符串作为键
- 取数据时:把字符串转成GR2对象再操作
这种方法不需要修改Jackson的配置,但需要在代码里额外处理转换逻辑,适合快速临时解决问题,但长期来看不如方案一优雅。
为什么会出现这个错误?
再给你理一下逻辑:你用Gson把GR2转成JSON字符串作为JsonMap的键,但Endpoints用Jackson解析请求/响应时,看到这个字符串键,不知道怎么把它转成GR2对象——因为Jackson默认只支持基本类型、字符串等简单类型作为Map键,自定义对象需要你明确告诉它怎么处理。
内容的提问来源于stack exchange,提问作者sylvain1811

