Json字段選取器介紹和實現

在這裏插入圖片描述
最近爲了工做方便寫了一個小工具,這個小工具做用很簡單,就是從一個json字符串中篩出你想要的部分。java

介紹

背景是這樣的,咱們爲了線上調試方便,有個工具能夠模擬發起一次數據請求,而後將結果以json的形式展現到頁面上。但問題是這個數據包含的信息很是多,動不動就上千行(如上圖),但每次debug的時候,只想看裏面特定的幾個字段,日常只能依賴於瀏覽器搜索工具一行一行搜,可能想看的字段會間隔好幾屏,一行行看即低效還容易漏。 若是要看JsonArray的數據,我以前是拷貝出來,而後用grep把字段篩出來,但這樣又丟失了層級信息。。。。。若是咱們想把某些字段列一塊兒用於數據分析的話,就更難了,只能人肉篩選記錄。。。正則表達式

我這個工具採用很簡單的語法來標識目標json的層級結構,以及每一層中你想要的字段。語法相似yaml的層級結果,用相同的縮減標識同一層,每一層的關鍵詞是你想要的字段key,不區分大小寫,爲了更方便使用,也支持正則表達式。
固然這裏有幾個特殊規則:
1.若是當前層級是個jsonArray的話字段後面須要加後綴:[]來標識出來(後續我可能會在中括號中支持範圍)。算法

  1. 第一行必須隨便寫個字段,保留這個字段的目的仍是怕一上來就是個JsonArray。
  2. 目前暫時不能加空行,尤爲是多行之間,會致使篩選有問題。

示例以下,也能夠試用demojson

json
  menu
    id
    popup
      menuitem:[]
        value

在這裏插入圖片描述

實現

若是你瞭解json數據格式的話,就知道它是一個層級嵌套的結構,而層級嵌套結構它其實很容易去轉換成一種樹形的結構。事實上如今市面上全部的json解析器,其實都是將這些數據轉換成樹形結構存儲的。知道json是一個樹形結構以後,咱們是否是構造一個同構的子樹,同構子樹的含義樹每一層包含更少的節點,但有的節點和原樹的節點同構。 瀏覽器

如何構造或者說描述這樣一個同構的樹形結構? 這裏我選用了相似yaml的描述,它採用了不一樣縮進來標識層級關係。函數

1
   2
     3
   4
     5
     6

好比這個,2 4 節點爲1的子節點,3是2的子節點,5 6是4的子節點。 有了描述語言,接下來的一步就是將描述語言轉化爲抽象語法樹。這裏我採用編譯原理中的遞歸降低算法,用遞歸的方式構造每一個節點的子節點。工具

爲了方便,我首先將語法描述預處理下,主要是將縮進轉化爲層級深度,而後遞歸解析,解析代碼以下。ui

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字符串,這樣咱們完成了匹配篩選的過程,代碼以下。this

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便可。spa

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);
    }
}
本文來自 https://blog.csdn.net/xindoo
相關文章
相關標籤/搜索