现象及原因
通常来讲,在使用json数据格式时一般不需要要求数据有序。但凡事都有例外,针对查询时序数据这样一个场景,就必须要求服务器端返回的数据是按时间有序的,否则前端在进行数据展示时就会有问题。
项目架构如下:
数据从OpenTSDB中查询出来的时候是有序的:
[{
"metrc":"cpu.usage",
"dps": {
"123456": 12,
"123457": 13,
"123458": 23,
"123459": 32
}
}]
执行如下操作:
JSONObject.parseArray(json)
结果查看对应的JSON数组中的map数据是乱序的,可能的结果如下:
[{
"metrc":"cpu.usage",
"dps": {
"123457": 13,
"123456": 12,
"123459": 32,
"123458": 23
}
}]
原本希望时序数据是按时间Key有序的,但是经过fastjson解析之后就会出现Key乱序。实际上,这个问题是fastjson本身的bug,详见:https://github.com/alibaba/fastjson/issues/660 。
解决办法
如下以解析从OpenTSDB中查询返回的时序数据为例。
1.升级fastjson版本
fastjson从1.2.3
版本开始,在解析json对象时可以指定Feature.OrderedField参数,这样解析的结果就不会乱序。
public static void main(String[] args) {
// 模拟从OpenTSDB中查询返回的时序数据
String str = "[{\\"metric\\":\\"temperature\\",\\"tags\\":{\\"device_id\\":\\"device-12312-14\\",\\"dt_name\\":\\"dsdsdsd\\"},\\"aggregateTags\\":[],\\"dps\\":{\\"1538210186542\\":30,\\"1538210191574\\":83,\\"1538210196597\\":41,\\"1538210201624\\":56,\\"1538210206654\\":20,\\"1538210211677\\":25,\\"1538210216700\\":54,\\"1538210221740\\":36,\\"1538210226773\\":89,\\"1538210231813\\":8,\\"1538210236847\\":34,\\"1538210241882\\":83,\\"1538210246916\\":96,\\"1538210251952\\":42,\\"1538210257002\\":6,\\"1538210262038\\":87,\\"1538210267076\\":19,\\"1538210272108\\":44,\\"1538210277139\\":84,\\"1538210282176\\":41,\\"1538210287216\\":57,\\"1538210292254\\":26,\\"1538210297283\\":64}}]";
// 直接使用fastjson的接口实现有序解析
JSONArray array = JSONArray.parseObject(str.getBytes(), JSONArray.class, Feature.OrderedField);
System.out.println(array.getJSONObject(0).getJSONObject("dps").toJSONString());
}
实际上,追踪一下fastjson的实现源码发现,当传递参数Feature.OrderedField
时,底层正是使用LinkedHashMap来实现Key有序的(LinkedHashMap是按插入顺序排序):
public JSONObject(int initialCapacity, boolean ordered){
if (ordered) {
// 使用LinkedHashMap保证json对象的key是按照插入顺序有序的
map = new LinkedHashMap<String, Object>(initialCapacity);
} else {
map = new HashMap<String, Object>(initialCapacity);
}
}
2.手动排序
除了可以直接通过fastjson的接口在解析时就实现有序,还可以对解析结果进行手动排序。
public static void main(String[] args) {
// 模拟从OpenTSDB中查询返回的时序数据
String str = "[{\\"metric\\":\\"temperature\\",\\"tags\\":{\\"device_id\\":\\"device-12312-14\\",\\"dt_name\\":\\"dsdsdsd\\"},\\"aggregateTags\\":[],\\"dps\\":{\\"1538210186542\\":30,\\"1538210191574\\":83,\\"1538210196597\\":41,\\"1538210201624\\":56,\\"1538210206654\\":20,\\"1538210211677\\":25,\\"1538210216700\\":54,\\"1538210221740\\":36,\\"1538210226773\\":89,\\"1538210231813\\":8,\\"1538210236847\\":34,\\"1538210241882\\":83,\\"1538210246916\\":96,\\"1538210251952\\":42,\\"1538210257002\\":6,\\"1538210262038\\":87,\\"1538210267076\\":19,\\"1538210272108\\":44,\\"1538210277139\\":84,\\"1538210282176\\":41,\\"1538210287216\\":57,\\"1538210292254\\":26,\\"1538210297283\\":64}}]";
// 解析之后手动排序
JSONArray array = JSONArray.parseArray(str);
System.out.println(array.toJSONString());
JSONObject json = array.getJSONObject(0);
// 不传递参数Feature.OrderedField时解析得到的json对象key是无序的,本质上是一个HashMap结构
Map<String, Object> map = json.getJSONObject("dps").getInnerMap();
// 通过TreeMap对Key进行排序
map = sortMapByKey(map);
JSONObject dps = new JSONObject();
dps.put("dps", map);
System.out.println(dps.toJSONString());
}
private static Map<String, Object> sortMapByKey(Map<String, Object> map) {
if (map == null || map.isEmpty()) {
return null;
}
Map<String, Object> sortMap = new TreeMap<String, Object>(new MapKeyComparator());
sortMap.putAll(map);
return sortMap;
}
private static class MapKeyComparator implements Comparator<String> {
@Override
public int compare(String str1, String str2) {
return str1.compareTo(str2);
}
}
【参考】
https://dzone.com/articles/hashmap-vs-treemap-vs HashMap vs. TreeMap vs. HashTable vs. LinkedHashMap