最近准备把ES的版本从5.1.2升到6.2.4,将Kafka的数据写入ES的工具类ESPersistor需要进行相应api的调整。在5.1.2的java api中,使用IndexRequest.source(String source)
来设置要写入的json字符串,但在6.2.4中这个函数已经被移除,可选的替代者有以下几种(source的重载函数还有很多,但这里不在讨论范围内)
1 | IndexRequest.source(String source, XContentType xContentType) |
第一种写法没有问题,指定XContentType.JSON就和之前版本的写入效果完全一样
第二种写法就发生了比较诡异的现象,假如json字符串中有值为浮点数,比如{“value”: 0.1},写入ES之后类型并不是float,而是text。假如字段value之前并不存在,那么ES会自动创建类型为text的字段value,后续就没办法对value做数值类型的计算了。那么为什么浮点类型会被认为是字符串呢?看代码
1 | public IndexRequest source(Map source, XContentType contentType) throws ElasticsearchGenerationException { |
参数Map source实际上是会被转换成XContentBuilder来处理,再看builder.map(source);
1 | private XContentBuilder map(Map<String, ?> values, boolean ensureNoSelfReferences) throws IOException { |
先检查json(map)中是否有自我引用,然后遍历所有Entry,将key/value写到XContentBuilder中,再看看值是怎么写入的unknownValue(value.getValue(), false);
1 | private void unknownValue(Object value, boolean ensureNoSelfReferences) throws IOException { |
判断value的类型,如果是ES标准数据类型,直接从WRITERS中获取相应的Writer写入,例如对于Float,调用(builder, value) -> builder.value((Float) value)
写入;对于其他类型,调用相应的value重载函数写入;如果列举的类型都不匹配,则当做字符串来处理
DEBUG一下,发现JSONObject {“value”: 0.1} 执行到这的时候,value.getClass()居然是BigDecimal,跟所有列举的类型都不匹配,所有就当字符串处理了,写入ES时就成了{“value”: “0.1”},那么为什么值的类型会变成BigDecimal呢?测试一下
1 | JSONObject data = new JSONObject(); |
打印结果是class java.lang.Double
,没有问题,这是new一个JSONObject的情况,再测试一下字符串parse成JSONObject的情况
1 | JSONObject data = JSON.parseObject("{\"value\":0.1}"); |
打印结果是class java.math.BigDecimal
,OK破案了,真凶是fastjson,它在parseObject时会把Float识别为BigDecimal,看一下源码,parseObject会调用parse(String text, int features)
函数,features的值是常量JSON.DEFAULT_PARSER_FEATURE,这个常量是由一系列的Feature位或计算出来的
1 | public static int DEFAULT_PARSER_FEATURE; |
其中有个Feature是UseBigDecimal,这个Feature会使得DefaultJSONParser中会把Float转成BigDecimal
1 | case LITERAL_FLOAT: |
问题根源找到了,解决也就不难了,自定义一个features,把Feature.UseBigDecimal从DEFAULT_PARSER_FEATURE中用异或去掉,然后JSON.parse使用自定义的features就可以了
1 | int features = JSON.DEFAULT_PARSER_FEATURE ^ Feature.UseBigDecimal.getMask(); |
打印class java.lang.Double
,解决