• JsonPath:从多层嵌套Json中解析所需要的值


    问题###

    应用中,常常要从嵌套的JSON串中解析出所需要的数据。通常的做法是,先将JSON转换成Map, 然后一层层地判空和解析。可使用 JsonPath 来解决这个问题。

    给定一个 JSON 串如下所示

    {"code":200,"msg":"ok","list":[{"id":20,"no":"1000020","items":[{"name":"n1","price":21,"infos":{"feature":""}}]}],"metainfo":{"total":20,"info":{"owner":"qinshu","parts":[{"count":13,"time":{"start":1230002456,"end":234001234}}]}}}
    

    从中解析出 code, total, count 的值。

    基本方案###

    基本方案就是自己手动将JSON转为Map,然后一层层判空和解析,如下代码所示:

    public class JsonUtil {
    
      private static final ObjectMapper MAPPER = new ObjectMapper();
    
      static {
        // 为保持对象版本兼容性,忽略未知的属性
        MAPPER.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        // 序列化的时候,跳过null值
        MAPPER.getSerializationConfig().setSerializationInclusion(JsonSerialize.Inclusion.NON_NULL);
        // date类型转化
        SimpleDateFormat fmt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        MAPPER.setDateFormat(fmt);
      }
    
      /**
       * 将一个json字符串解码为java对象
       *
       * 注意:如果传入的字符串为null,那么返回的对象也为null
       *
       * @param json json字符串
       * @param cls  对象类型
       * @return 解析后的java对象
       * @throws RuntimeException 若解析json过程中发生了异常
       */
      public static <T> T toObject(String json, Class<T> cls) {
        if (json == null) {
          return null;
        }
        try {
          return MAPPER.readValue(json, cls);
        } catch (Exception e) {
          return null;
        }
      }
    
      /**
       * 读取JSON字符串为MAP
       */
      @SuppressWarnings("unchecked")
      public static Map<String, Object> readMap(String json) {
        return toObject(json, HashMap.class);
      }
    
      /**
       * 对于正确JSON及存在的Path下获取到最终指定值并转成字符串,其他情况一律返回 null
       * @param json JSON串
       * @param path 点分隔的字段路径
       * @return 相应字段的字符串值
       */
      public static String readVal(String json, String path) {
        if (json == null || path == null) {
          return null;
        }
        Map<String,Object> map = readMap(json);
        if (map == null) {
          // log.warn("parse json failed: " + json);
          return null;
        }
        String[] subpaths = path.split("\.");
        return readVal(map, subpaths);
      }
    
      private static String readVal(Map<String, Object> map, String path) {
        return readVal(map, path.split("\."));
      }
    
      private static String readVal(Map<String, Object> map, String[] subpaths) {
        Object val = map;
        try {
          for (String subpath: subpaths) {
            if (val != null && val instanceof Map) {
              val = ((Map)val).get(subpath);
            }
            else {
              // log.warn("subpath may not exists in " + map);
              return null;
            }
          }
          return val == null ? null: val.toString();
        } catch (Exception ex) {
          return null;
        }
    
      }
    
    

    realVal 的目标就是:对于正常情况下获取到最终指定值并转成字符串,其他情况一律返回 null. readVal 上层函数接受一个JSON串和一个点分割的Path,进行参数校验后交给下层 readVal 函数;下层 readVal 函数对每次取出的值进行判空和取值,如果OK就一直进行到取出最终值;否则要么抛出异常,要么直接返回 null.

    对于只需要从嵌套Map中取值的需求,基本是满足了,不过若要从List,Map混合的JSON串中取值,就不够用了。此时,可采用成熟库 JsonPath 来完成这件事。

    JsonPath###

    在网上搜索 jsonpath maven 即可在 mvnrepository 找到 jsonpath 的最新版本。工程中引入

    <!-- https://mvnrepository.com/artifact/com.jayway.jsonpath/json-path -->
    <dependency>
        <groupId>com.jayway.jsonpath</groupId>
        <artifactId>json-path</artifactId>
        <version>2.4.0</version>
    </dependency>
    

    现在就可以使用JsonPath 了。 基本语法可参考这篇文章: https://www.cnblogs.com/weilunhui/p/3857366.html

    可以为 JsonPath 包装一层函数, 与现有客户端使用相同。

      public static String readValUsingJsonPath(String json, String path) {
        if (json == null || path == null) {
          return null;
        }
        try {
          Object val = JsonPath.read(json, "$." + path);
          return val == null ? null : val.toString();
        } catch (Exception ex) {
          return null;
        }
      }
    

    编写单测如下:

    public class JsonUtilTest extends CommonForTest {
    
      String json = "{"code":200,"msg":"ok","list":[{"id":20,"no":"1000020","items":[{"name":"n1","price":21,"infos":{"feature":""}}]}],"metainfo":{"total":20,"info":{"owner":"qinshu","parts":[{"count":13,"time":{"start":1230002456,"end":234001234}}]}}}";
    
      // 常用的 Json Path 可以缓存起来重用,类似正则里的 Pattern p = Pattern.compile('regexString')
      JsonPath codePath = JsonPath.compile("$.code");
      JsonPath totalPath = JsonPath.compile("$.metainfo.total");
    
      @Test
      public void testReadVal() {
    
        eq(null, JsonUtil.readVal(null, "code"));
        eq(null, JsonUtil.readVal(json, null));
        eq("200", JsonUtil.readVal(json, "code"));
        eq("20", JsonUtil.readVal(json, "metainfo.total"));
        eq("qinshu", JsonUtil.readVal(json, "metainfo.info.owner"));
        eq(null, JsonUtil.readVal("invalid json", "code"));
        eq(null,JsonUtil.readVal(json, "metainfo.extra.feature"));
    
        eq(null, JsonUtil.readValUsingJsonPath(null, "code"));
        eq(null, JsonUtil.readValUsingJsonPath(json, null));
        eq("200", JsonUtil.readValUsingJsonPath(json, "code"));
        eq("20", JsonUtil.readValUsingJsonPath(json, "metainfo.total"));
        eq("qinshu", JsonUtil.readValUsingJsonPath(json, "metainfo.info.owner"));
        eq(null, JsonUtil.readValUsingJsonPath("invalid json", "code"));
        eq(null,JsonUtil.readValUsingJsonPath(json, "metainfo.extra.feature"));
    
        eq(200, codePath.read(json));
        eq(20, totalPath.read(json));
        eq("qinshu", JsonPath.read(json, "$.metainfo.info.owner"));
        eq("n1", JsonPath.read(json, "$.list[0].items[0].name"));
        eq(13, JsonPath.read(json, "$.metainfo.info.parts[0].count"));
    
      }
    
    }
    

    性能测试如下。

    @Test
      public void testPerformance() {
        for (int i=1; i<=1000; i=i*10) {
          double avg = 0.0;
          double total = 0;
          long num = 100*i;
          for(int j=0; j<num; j++) {
            long start = System.nanoTime() / 1000;
            Assert.assertEquals("true", JsonUtils.extractString(json, "IS_MEMBER"));
            long endTime = System.nanoTime() / 1000;
            long cost = (endTime-start);
            //System.out.println("cost: " + cost);
            total += cost;
          }
          System.out.println("avg: " + total / num);
        }
    
      }
    

    平均耗时基本在 0 - 100 微妙之间;大部分在 50 微妙以下。

    可见 jsonPath 的功能更加强大,也更加健壮。

    小结###

    多熟悉现有库,不轻易造重复轮子。

  • 相关阅读:
    身份证号验证
    QML学习笔记(七)— 实现可拖拽、编辑、选中的ListView
    通过WebChannel/WebSockets与QML中的HTML交互
    OpenLayers学习笔记(七)— 类似比例尺的距离环(一)
    OpenLayers学习笔记(六)— 拖拽叠加层overlayer
    OpenLayers学习笔记(五)— 拖拽Feature图层
    QML学习笔记(六)- 简单计时器和定时器
    QML学习笔记(五)— 做一个简单的待做事项列表
    OpenLayers学习笔记(四)— QML显示html中openlayers地图的坐标
    OpenLayers学习笔记(三)— QML与HTML通信之 地图上点击添加自由文本
  • 原文地址:https://www.cnblogs.com/lovesqcc/p/7821641.html
Copyright © 2020-2023  润新知