java stream处理 fastJSON反序列化的JSONObject对象

前言

java如此庞大的生态之下,json工具类有很多,我常用的是阿里巴巴的fastJson。在最开始使用的时候,我反序列化,用的是这个方法

public static <T> T parseObject(String text, Class<T> clazz)

需要传两个参数,第一个是json字符串,第二个是需要序列化成的对象的class,这样的好处是能直接转成对象,然后操作对象,对象也能复用。但是有有个缺点:需要预先把对象创建出来,如果json层次很深,那就需要定义很多的类,举个例子:

{
  "username": "张三",
  "age": "25",
  "certificates": [
    {
      "title": "英语4级",
      "date": "2019-12-10"
    },
    {
      "title": "计算机二级",
      "date": "2018-12-10"
    },
    {
      "title": "教师资格证",
      "date": "2019-6-10"
    }
  ],
  "address": {
    "province": "四川省",
    "city": "成都市",
    "county": "天赋新区"
  }
}

这个简简单单的json,就需要定义三个类:information、certificate、address,如果更复杂一点的json,需要定义的类更多(这个时候只有羡慕前端同事哈哈)。有的时候我们可能只是需要拿到或者操作json里面的一点点内容,比如:我只想拿到拿到张三同学获得了多少个certificate,为了拿到一个统计的和,就需要定义三个类,也太不爽了吧。

parseObject(String text)

明明只需要一点东西,却需要定义很多类,让人很不爽,所以引入我们今天的主角之一:

public static JSONObject parseObject(String text)

可以看到,这个方法只有一个参数,String类型的字符串,有的同学就像思考了,那要将这个字符串反序列化成什么对象呢?返回的这个JSONObject 又是什么东西呢?

善于思考是好事,但只思考不实际行动也没用是吧,咱们直接上代码测试

        String jsonStr = "{\"username\":\"张三\",\"age\":\"25\",\"certificate\":[{\"title\":\"英语4级\",\"date\":\"2019-12-10\"},{\"title\":\"计算机二级\",\"date\":\"2018-12-10\"},{\"title\":\"教师资格证\",\"date\":\"2019-6-10\"}],\"address\":{\"province\":\"四川省\",\"city\":\"成都市\",\"county\":\"天赋新区\"}}";
        JSONObject information = JSONObject.parseObject(jsonStr);
        System.out.println(information);

不测试不知道,一测试吓一跳,好家伙,打印出来的是原来那个json字符串,那到底有没有序列化呢?

System.out.println(Object x)是我们早期学习和调试常用的手段之一,但其实有时候这个方法并不好使,因为我们System.out.println(Object x),其实是调用了x.toString这个方法去展示x这个对象的,所以说一个类,我重写了toString,你再去System.out.println,那么只能看到我想让你看到的。那怎么办?看源码撒,去看看JSONObject 内部是怎么设计的。

要看一个类,该怎么下手呢?还记得最开始学习java、学习面向对象的时候吗?类是由什么组成的?属性和方法,属性是什么?属性是描述这个类的静态属性,比如一个人的身高体重。方法是什么?方法是描述这个类的动态属性,比如吃饭,吃饭是动态的,吃什么,怎么吃,还得看吃的是什么,也就是传递的参数……所以,我们也从属性和方法下手,看看JSONObject 有哪些属性和方法。
JSONObject 源码

JSONObject的属性

方法太多,咱们先忽略,先看属性,what?居然只有一个Map<String, Object>属性?我的jsonStr可是有好多属性、好多K-V键值对的,等等,K-V键值对,我好像想到了什么,map不也是以K-V键值对的形式来存储数据的吗?哦,我好像懂了,咱们再结合断点看看
断点
的确就是咱们想的那样,JSONObject 内部就是用Map来存储数据的,把json字符串中的键和值,转成Map中K-V键值对。有的同学又发言了,json字符串中,value还可能是一个对象、一个数组,这怎么转?这也很简单嘛,你看JSONObject 中Map的泛型<String, Object>,value是Object,json串中value是一个对象,那Map中value就是JSONObject 呗,json串中value是数组,那Map中value就是List或数组呗(原理是这样,但其实是一个自己实现的List:JSONArray)

JSONObject的方法

咱们理清了内部结构之后,再来看看JSONObject对外提供了哪些方法来操作map里面K-V,方法我就不看源码了,太多太杂,我这样看方法
JSONObject的方法
用一个JSONObject对象去点,然后根据工具的提示,看方法的名称、参数、返回值和个人经验去推测这些方法的作用,这样效率比较高。比如
entrySet,返回了一个Set<Map.Entry<String, Object>>,应该就是把json串里面所有的k-v用Set装起来,为啥用Set呢?因为key肯定不能重复,其他的方法,应该也能很简单的推测出来。

stream流

这个是java 8推出的新特性,简单理解就是简化循环,结合方法引用,lambda表达式,用起来很爽。但是维护起来不好维护。给大家贴个比较极端的例子
stream使用

stream流用起来是很爽,一行,一直点下去,通畅爽透。维护起来可就是恶魔了,建议大伙儿stream不要写太复杂了。

stream流里内容也很多,一时半会写不完,抽空写个系列,今天咱们就不说了。

实战: stream处理 fastJSON反序列化的JSONObject对象

json串是类似这样的:

{
  "comprehensiveRank": {
    "year": "2021",
    "valueArr": [
      {
        "value": "12"
      }
    ]
  },
  "all_land": "45",
  "land_use": "45",
  "land_use_rate": 100,
  "total_importAndExport_lastyear": "45",
  "total_importAndExport_thisyear": "45",
  "importAndExport_rate": 0,
  "total_company_value_lastyear": "45",
  "total_company_value_thisyear": "45",
  "company_value_rate": 0,
  "importAndExport_unit_area_lastyear": "45",
  "importAndExport_unit_area_thisyear": "45",
  "importAndExport_unit_area_rate": 0,
  "total_import_lastyear": "45",
  "total_import_thisyear": "45",
  "total_import_rate": 0,
  "total_export_lastyear": "45",
  "total_export_thisyear": "45",
  "total_export_rate": 0,
  "high_tech_comprehensive_rate": "45",
  "sichuan_province_rate": 55,
  "total_importAndExport_value": 47,
  "importAndExportValue": {
    "valueArr": [
      {
        "year": "2021",
        "value": "23"
      },
      {
        "year": "2020",
        "value": "29"
      },
      {
        "year": "2030",
        "value": "47"
      },
      {
        "year": "2035",
        "value": "86"
      },
      {
        "year": "2010",
        "value": "29"
      },
      {
        "year": "2034",
        "value": "44"
      },
      {
        "year": "2060",
        "value": "34"
      }
    ]
  },
  "national_average": 23.5,
  "total_unit_area": "3455",
  "note": "12"
}

需求也很简单,把importAndExportValue中的valueArr按照year排序,然后取前五,将和设置到total_importAndExport_value中,将平均值设置到national_average中,其他的不管。

要是把对象定义出来,反序列化成对象,一顿get、set、集合循环,也很简单,但做这么一点事情,我不想定义那么多对象,直接序列化成JSONObject然后用stream操作。

代码如下:

        String s = "{\"comprehensiveRank\":{\"year\":\"2021\",\"valueArr\":[{\"value\":\"12\"}]},\"all_land\":\"45\",\"land_use\":\"45\",\"land_use_rate\":100,\"total_importAndExport_lastyear\":\"45\",\"total_importAndExport_thisyear\":\"45\",\"importAndExport_rate\":0,\"total_company_value_lastyear\":\"45\",\"total_company_value_thisyear\":\"45\",\"company_value_rate\":0,\"importAndExport_unit_area_lastyear\":\"45\",\"importAndExport_unit_area_thisyear\":\"45\",\"importAndExport_unit_area_rate\":0,\"total_import_lastyear\":\"45\",\"total_import_thisyear\":\"45\",\"total_import_rate\":0,\"total_export_lastyear\":\"45\",\"total_export_thisyear\":\"45\",\"total_export_rate\":0,\"high_tech_comprehensive_rate\":\"45\",\"sichuan_province_rate\":55,\"total_importAndExport_value\":47,\"importAndExportValue\":{\"valueArr\":[{\"year\":\"2021\",\"value\":\"23\"},{\"year\":\"2020\",\"value\":\"29\"},{\"year\":\"2030\",\"value\":\"47\"},{\"year\":\"2035\",\"value\":\"86\"},{\"year\":\"2010\",\"value\":\"29\"},{\"year\":\"2034\",\"value\":\"44\"},{\"year\":\"2060\",\"value\":\"34\"}]},\"national_average\":23.5,\"total_unit_area\":\"3455\",\"note\":\"12\"}";
        // 解析成对象
        JSONObject jsonCockpit = JSONObject.parseObject(s);
        // 拿到valueArr,这是个数组
        List<JSONArray> jsonArrays = jsonCockpit.entrySet().stream().filter(x -> "importAndExportValue".equals(x.getKey()))
                .map(x -> (JSONObject) x.getValue()).collect(Collectors.toList()).get(0).entrySet().stream()
                .filter(x -> "valueArr".equals(x.getKey()))
                .map(x -> (JSONArray) x.getValue()).collect(Collectors.toList());
        // 按照year降序
        List<JSONObject> sort = jsonArrays.get(0).stream().map(x -> (JSONObject) x).sorted((x, y) -> {
            List<Map.Entry<String, Object>> collectX = x.entrySet().stream()
                    .filter(x1 -> "year".equals(x1.getKey())).collect(Collectors.toList());
            List<Map.Entry<String, Object>> collectY = y.entrySet().stream()
                    .filter(y1 -> "year".equals(y1.getKey())).collect(Collectors.toList());
            return collectY.get(0).getValue().toString().compareTo(collectX.get(0).getValue().toString());
        }).collect(Collectors.toList());
        // 求前五个之和
        long sum = sort.stream().limit(Math.min(sort.size(), 5L))
                .mapToLong(x -> Long.parseLong(x.entrySet().stream().filter(y -> "value".equals(y.getKey()))
                        .collect(Collectors.toList()).get(0).getValue().toString())).sum();
        // 设置到原本反序列化之后的对象中
        jsonCockpit.entrySet().forEach(x -> {
            if ("total_importAndExport_value".equals(x.getKey())) {
                x.setValue(sum);
            }
            if ("national_average".equals(x.getKey())) {
                x.setValue((sum + 0F) / Math.min(sort.size(), 5F));
            }
        });
        System.out.println(jsonCockpit);

看起来有点头大哈,我可是写了注释的,用到的东西也没多少。就stream里面的filter、map、sorted、limit、mapToLong

有问题还请指出
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值