
最近為了作業方便寫了一個小工具,這個小工具作用很簡單,就是從一個json字串中篩出你想要的部分,
介紹
背景是這樣的,我們為了線上除錯方便,有個工具可以模擬發起一次資料請求,然后將結果以json的形式展示到頁面上,但問題是這個資料包含的資訊非常多,動不動就上千行(如上圖),但每次debug的時候,只想看里面特定的幾個欄位,平常只能依賴于瀏覽器搜索工具一行一行搜,可能想看的欄位會間隔好幾屏,一行行看即低效還容易漏, 如果要看JsonArray的資料,我之前是拷貝出來,然后用grep把欄位篩出來,但這樣又丟失了層級資訊,,,,,如果我們想把某些欄位列一起用于資料分析的話,就更難了,只能人肉篩選記錄,,,
我這個工具采用很簡單的語法來標識目標json的層級結構,以及每一層中你想要的欄位,語法類似yaml的層級結果,用相同的縮減標識同一層,每一層的關鍵詞是你想要的欄位key,不區分大小寫,為了更方便使用,也支持正則運算式,
當然這里有幾個特殊規則:
1.如果當前層級是個jsonArray的話欄位后面需要加后綴:[]來標識出來(后續我可能會在中括號中支持范圍),
2. 第一行必須隨便寫個欄位,保留這個欄位的目的還是怕一上來就是個JsonArray,
3. 目前暫時不能加空行,尤其是多行之間,會導致篩選有問題,
示例如下,也可以試用demo,
json
menu
id
popup
menuitem:[]
value

實作
如果你了解json資料格式的話,就知道它是一個層級嵌套的結構,而層級嵌套結構它其實很容易去轉換成一種樹形的結構,事實上現在市面上所有的json決議器,其實都是將這些資料轉換成樹形結構存盤的,知道json是一個樹形結構之后,我們是不是構造一個同構的子樹,同構子樹的含義樹每一層包含更少的節點,但有的節點和原樹的節點同構,
如何構造或者說描述這樣一個同構的樹形結構? 這里我選用了類似yaml的描述,它采用了不同縮進來標識層級關系,
1
2
3
4
5
6
比如這個,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);
}
}
本文來自https://blog.csdn.net/xindoo
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/158655.html
標籤:Java
上一篇:Spring3——使用注解實作宣告式事務、面向切面編程——AOP
下一篇:JDBC語法總結
