最近爲了工做方便寫了一個小工具,這個小工具做用很簡單,就是從一個json字符串中篩出你想要的部分。java
背景是這樣的,咱們爲了線上調試方便,有個工具能夠模擬發起一次數據請求,而後將結果以json的形式展現到頁面上。但問題是這個數據包含的信息很是多,動不動就上千行(如上圖),但每次debug的時候,只想看裏面特定的幾個字段,日常只能依賴於瀏覽器搜索工具一行一行搜,可能想看的字段會間隔好幾屏,一行行看即低效還容易漏。 若是要看JsonArray的數據,我以前是拷貝出來,而後用grep把字段篩出來,但這樣又丟失了層級信息。。。。。若是咱們想把某些字段列一塊兒用於數據分析的話,就更難了,只能人肉篩選記錄。。。正則表達式
我這個工具採用很簡單的語法來標識目標json的層級結構,以及每一層中你想要的字段。語法相似yaml的層級結果,用相同的縮減標識同一層,每一層的關鍵詞是你想要的字段key,不區分大小寫,爲了更方便使用,也支持正則表達式。
固然這裏有幾個特殊規則:
1.若是當前層級是個jsonArray的話字段後面須要加後綴:[]
來標識出來(後續我可能會在中括號中支持範圍)。算法
示例以下,也能夠試用demo。json
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