往 ElasticSearch 写数据的时候报了个奇怪的错误:
Exception in thread "main" ElasticsearchParseException[field must be either lat/lon or geohash]
查看 ES 索引发现 location
字段是 geo_point
类型的,我在 Java 代码里对应字段使用的是 ES 提供的 GeoPoint 字段,感觉序列化之后写入 ES 不应该报错。
翻了 ES 的文档,geo_point
类型支持的数据格式有四种:
PUT my_index/my_type/1
{
"text": "Geo-point as an object",
"location": {
"lat": 41.12,
"lon": -71.34
}
}
PUT my_index/my_type/2
{
"text": "Geo-point as a string",
"location": "41.12,-71.34"
}
PUT my_index/my_type/3
{
"text": "Geo-point as a geohash",
"location": "drm3btev3e86"
}
PUT my_index/my_type/4
{
"text": "Geo-point as an array",
"location": [ -71.34, 41.12 ]
}
ElasticSearch 的 GeoPoint 类有两个字段 lan
和 lon
,分别是纬度和经度。
加了日志,我发现使用 Jackson 序列化之后结果有三个字段:
{
"lat": 30.286928,
"lon": 120.205459,
"geohash": "wtmkr3dfnhwd"
}
ES 无法解析这种格式,结果报错了。
通过查看 GeoPoint 类的代码,发现它有个方法:
public String getGeohash() {
return stringEncode(lon, lat);
}
Jackson 序列化/反序列化是根据 get
和 set
方法设置字段的,结果序列化的时候多出了 geohash
字段,进而导致 ES 无法解析成 geo_point
类型数据。
解决方案也很简单,把序列化工具换成 Gson 就可以了。
不过 Jackson 也提供了接口兼容这种情况,可以通过自定义 GeoPoint
类的序列化和反序列化方式来实现。
public class GeoPointSerializer extends StdSerializer<GeoPoint> {
private static final long serialVersionUID = -1262685290185555664L;
GeoPointSerializer() {
this(null);
}
GeoPointSerializer(Class<GeoPoint> t) {
super(t);
}
@Override
public void serialize(GeoPoint value, JsonGenerator gen, SerializerProvider provider) throws IOException {
gen.writeStartObject();
gen.writeNumberField("lat", value.getLat());
gen.writeNumberField("lon", value.getLon());
gen.writeEndObject();
}
}
public class GeoPointDeserializer extends StdDeserializer<GeoPoint> {
private static final long serialVersionUID = -5510642578760781580L;
GeoPointDeserializer() {
this(null);
}
GeoPointDeserializer(Class<GeoPoint> t) {
super(t);
}
@Override
public GeoPoint deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
ObjectCodec codec = p.getCodec();
JsonNode node = codec.readTree(p);
JsonNode latNode = node.get("lat");
double lat = latNode.asDouble();
JsonNode lonNode = node.get("lon");
double lon = lonNode.asDouble();
return new GeoPoint(lat, lon);
}
}
然后把新的序列化和反序列化方式注册到 Jackson 的 ObjectMapper 就可以了。
SimpleModule deserializerModule = new SimpleModule("GeoPointDeserializer", new Version(1, 0, 0, null, null, null));
deserializerModule.addDeserializer(GeoPoint.class, new GeoPointDeserializer());
MAPPER.registerModule(deserializerModule);
SimpleModule serializerModule = new SimpleModule("GeoPointSerializer", new Version(1, 0, 0, null, null, null));
serializerModule.addSerializer(GeoPoint.class, new GeoPointSerializer());
MAPPER.registerModule(serializerModule);