5

Json字段选取器介绍和实现

 3 years ago
source link: https://zxs.io/article/1724
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Json字段选取器介绍和实现

2020-06-27 分类:Java 阅读(3377) 评论(0)

在这里插入图片描述
最近为了工作方便写了一个小工具,这个小工具作用很简单,就是从一个json字符串中筛出你想要的部分。

背景是这样的,我们为了线上调试方便,有个工具可以模拟发起一次数据请求,然后将结果以json的形式展示到页面上。但问题是这个数据包含的信息非常多,动不动就上千行(如上图),但每次debug的时候,只想看里面特定的几个字段,平常只能依赖于浏览器搜索工具一行一行搜,可能想看的字段会间隔好几屏,一行行看即低效还容易漏。 如果要看JsonArray的数据,我之前是拷贝出来,然后用grep把字段筛出来,但这样又丢失了层级信息。。。。。如果我们想把某些字段列一起用于数据分析的话,就更难了,只能人肉筛选记录。。。

我这个工具采用很简单的语法来标识目标json的层级结构,以及每一层中你想要的字段。语法类似yaml的层级结果,用相同的缩减标识同一层,每一层的关键词是你想要的字段key,不区分大小写,为了更方便使用,也支持正则表达式。
当然这里有几个特殊规则:
1.如果当前层级是个jsonArray的话字段后面需要加后缀:[]来标识出来(后续我可能会在中括号中支持范围)。

  1. 第一行必须随便写个字段,保留这个字段的目的还是怕一上来就是个JsonArray。
  2. 目前暂时不能加空行,尤其是多行之间,会导致筛选有问题。

示例如下,也可以试用demo

json
  menu
    id
    popup
      menuitem:[]
        value

在这里插入图片描述

如果你了解json数据格式的话,就知道它是一个层级嵌套的结构,而层级嵌套结构它其实很容易去转换成一种树形的结构。事实上现在市面上所有的json解析器,其实都是将这些数据转换成树形结构存储的。知道json是一个树形结构之后,我们是不是构造一个同构的子树,同构子树的含义树每一层包含更少的节点,但有的节点和原树的节点同构。

如何构造或者说描述这样一个同构的树形结构? 这里我选用了类似yaml的描述,它采用了不同缩进来标识层级关系。

比如这个,2 4 节点为1的子节点,3是2的子节点,5 6是4的子节点。 有了描述语言,接下来的一步就是将描述语言转化为抽象语法树。这里我采用编译原理中的递归下降算法,用递归的方式构造每个节点的子节点。

为了方便,我首先将语法描述预处理下,主要是将缩进转化为层级深度,然后递归解析,解析代码如下。

public class Node {
    public int type = 0; //jsonObject or jsonArray 
    Map<String, Node> children = new HashMap<>();

    public Node(String[] keys, int[] deeps, int cur) {  //解析逻辑直接放在构造函数中
        // 无子节点
        if (cur == keys.length - 1 || deeps[cur] >= deeps[cur+1]) {
            this.type = 0; //无子节点
            return;
        }
        int childDeep = deeps[cur+1];
        for (int i = cur+1; i < keys.length; i++) {
            if (deeps[i] < childDeep) {
                break;
            } else if (deeps[i] > childDeep) {
                continue;
            }
            String key = keys[i];
            Node child = new Node(keys, deeps, i);  // 递归解析子节点 
            if (key.contains(":")) {
                key = key.split(":")[0];
                child.type = 1;  // ArrayList;
            }
            children.put(key, child);
        }
    }
}

整个解析完之后就是一颗抽象语法树。json字符串我用fastjson解析后也是树形层级结构,因为我们新生成的语法树和json语法树是同构的关系,所以我们可以同时递归遍历新语法树和抽象语法树,并同时生成一个筛选后的json字符串,这样我们完成了匹配筛选的过程,代码如下。

   public Object getSelected(Object object) {
        // 无子节点
        if (children.size() == 0) {
            return object;
        }

        JSONObject res = new JSONObject(true);
        JSONObject json = (JSONObject)object;
        for (Map.Entry<String, Object> entry : json.entrySet()) {
            Node child = getChild(entry.getKey());
            if (child == null) {
                continue;
            }
            // json
            if (child.type == 0) {
                res.put(entry.getKey(), child.getSelected(json.get(entry.getKey())));
            }
            // jsonArray
            if (child.type == 1) {
                JSONArray arr = (JSONArray)entry.getValue();
                JSONArray newArr = new JSONArray();
                for (int i = 0; i < arr.size(); i++) {
                    newArr.add(child.getSelected(arr.getJSONObject(i)));
                }
                res.put(entry.getKey(), newArr);
            }
        }
        return res;
    }

    public Node getChild(String content) {
        for (Map.Entry<String, Node> child : children.entrySet()) {
            // 这里我额外加入了正则表达式匹配,可以让选择器的功能更灵活  
            if (content.equalsIgnoreCase(child.getKey()) || Pattern.matches(child.getKey(), content)) {
                return child.getValue();
            }
        }
        return null;
    }

最后写个类封装下所有API即可。

public class JsonSelector {
    private Node startNode;
    private JsonSelector() {};
    // 编译生成语法树 
    public static JsonSelector compile(String txt) {
        // 预处理  
        txt = txt.replace("\t", "    ");
        String[] arr = txt.split("\n");
        int[] deeps = new int[arr.length];
        String[] keys = new String[arr.length];
        for (int i = 0; i < arr.length; i++) {
            String str = arr[i];
            deeps[i] = getSpaceCnt(str);
            keys[i] = rmSpace(str);
        }
        JsonSelector selector = new JsonSelector();
        selector.startNode = new Node(keys, deeps, 0);
        return selector;
    }

    public String getSelectedString(String jsonStr) {
        JSONObject json = JSONObject.parseObject(jsonStr, Feature.OrderedField);
        JSONObject res = (JSONObject) startNode.getSelected(json);
        return res.toJSONString();
    }

    private static int getSpaceCnt(String str) {
        int cnt = 0;
        for (cnt = 0; cnt < str.length(); cnt++) {
            if (str.charAt(cnt) != ' ') {
                break;
            }
        }
        return cnt;
    }

    private static String rmSpace(String str) {
        String res = str.trim();
        int end = res.length();
        while(end > 0 && res.charAt(end - 1) == ' ') {
            end--;
        }
        return res.substring(0, end);
    }
}

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK