下载地址:https://files.cnblogs.com/files/heyang78/JsonAnalyzer20200518-01.zip

测试用例:https://www.cnblogs.com/heyang78/p/12911174.html

为什么创建此工程?

笔者在开发中曾遇到一个Restful接口变更后,将新Json文本和旧有文档的对比矫正工作,当时最大的问题是两边都不是按字母序排列的,比较时只能上下翻找而不可一一比对,于是我产生了将Json文本同级节点按字母序排列的想法,继而将其实现之.https://www.cnblogs.com/heyang78/p/11973129.html 是第一个版本,当时实现了需求但未完善,今天(2020年5月18日)修正了原有数组解析不完全的bug并调整简化了代码.

 解析效果展示:

原有文本:

{"data":[{"deliveryListId":"","shipperCode":"","shortShipperName":"RB","orderNo":"","deliveryOrder":,"receiverName":"吉田XXX","receiverTelNo":"","receiverAddress1":"東京都足立区足立1-1","receiverAddress2":"東京都足立区足立1-2","isCod":true,"billAmount":,"geocodingScore":,"latitudeJP":"56789.33","longitudeJP":"123456.33","latitude":"20180001.22","longitude":"20180001.33","vehicleId":"","orderDetails":[{"trackingNo":"","quantity":,"lapCount":null,"statusCode":null,"statusNameMobile":null},{"trackingNo":"","quantity":,"lapCount":,"statusCode":"","statusNameMobile":"配送準備中"},{"trackingNo":"","quantity":,"lapCount":,"statusCode":"","statusNameMobile":"持出し"},{"trackingNo":"","quantity":,"lapCount":,"statusCode":"","statusNameMobile":"配送準備中"},{"trackingNo":"","quantity":,"lapCount":,"statusCode":"","statusNameMobile":"配送準備中"}]}]}

解析后文本:

{
"data":[
{
"billAmount":,
"deliveryListId":"",
"deliveryOrder":,
"geocodingScore":,
"isCod":true,
"latitude":"20180001.22",
"latitudeJP":"56789.33",
"longitude":"20180001.33",
"longitudeJP":"123456.33",
"orderDetails":[
{
"lapCount":null,
"quantity":,
"statusCode":null,
"statusNameMobile":null,
"trackingNo":""
},
{
"lapCount":,
"quantity":,
"statusCode":"",
"statusNameMobile":"配送準備中",
"trackingNo":""
},
{
"lapCount":,
"quantity":,
"statusCode":"",
"statusNameMobile":"持出し",
"trackingNo":""
},
{
"lapCount":,
"quantity":,
"statusCode":"",
"statusNameMobile":"配送準備中",
"trackingNo":""
},
{
"lapCount":,
"quantity":,
"statusCode":"",
"statusNameMobile":"配送準備中",
"trackingNo":""
}
],
"orderNo":"",
"receiverAddress1":"東京都足立区足立1-1",
"receiverAddress2":"東京都足立区足立1-2",
"receiverName":"吉田XXX",
"receiverTelNo":"",
"shipperCode":"",
"shortShipperName":"RB",
"vehicleId":""
}
]
}

此工程的扩展意义:

做编译的分词,语法分析,构建语法树在此工程中均有体现,此工程也为后继编译项目打下了基础.

核心类说明:

记号类,此类用于给Json文本中出现的七种文本分类:

package com.heyang;

/**
* Tokens in json format
* @author Heyang
*
*/
public class Token {
public static final int TYPE_OPEN_BRACE=0; // {
public static final int TYPE_CLOSE_BRACE=1; // }
public static final int TYPE_TEXT=2; // text
public static final int TYPE_COMMA=3; // ,
public static final int TYPE_COLON=4; // :
public static final int TYPE_OPEN_BRACKET=5; // [
public static final int TYPE_CLOSE_BRACKET=6; // ] private int type;
private String text; public Token(char c,int type) {
this.text=String.valueOf(c);
this.type=type;
} public Token(String word,int type) {
this.text=word;
this.type=type;
} public int getType() {
return type;
} public void setType(int type) {
this.type = type;
} public String getText() {
return text;
} public void setText(String text) {
this.text = text;
}
}

分词器类,此类用于将json变成记号:

package com.heyang;

import java.util.ArrayList;
import java.util.List; import org.apache.commons.lang.StringUtils; /**
* Parse json string to tokens
* @author Heyang
*
*/
public class Lexer {
private List<Token> tokens; public Lexer(String jsonTxt) {
tokens = new ArrayList<Token>(); String bundle = "";
for (int i = 0; i < jsonTxt.length(); i++) {
char c = jsonTxt.charAt(i); if (Character.isWhitespace(c)) {
continue;
} else if (c == '{') {
tokens.add(new Token(c, Token.TYPE_OPEN_BRACE));
} else if (c == '}') {
if (StringUtils.isNotEmpty(bundle)) {
tokens.add(new Token(bundle, Token.TYPE_TEXT));
bundle = "";
} tokens.add(new Token(c, Token.TYPE_CLOSE_BRACE));
} else if (c == '[') {
tokens.add(new Token(c, Token.TYPE_OPEN_BRACKET));
} else if (c == ']') {
if (StringUtils.isNotEmpty(bundle)) {
tokens.add(new Token(bundle, Token.TYPE_TEXT));
bundle = "";
} tokens.add( new Token(c, Token.TYPE_CLOSE_BRACKET));
} else if (c == ',') {
if (StringUtils.isNotEmpty(bundle)) {
tokens.add(new Token(bundle, Token.TYPE_TEXT));
bundle = "";
} tokens.add(new Token(c, Token.TYPE_COMMA));
} else if (c == ':') {
if (StringUtils.isNotEmpty(bundle)) {
tokens.add(new Token(bundle, Token.TYPE_TEXT));
bundle = "";
} tokens.add(new Token(c, Token.TYPE_COLON));
} else {
bundle += c;
}
}
} public List<Token> getTokenList() {
return tokens;
} // Just for test
public void printTokens() {
int idx = 0;
for (Token t : tokens) {
idx++;
System.out.println("#" + idx + " " + t.getText());
}
} public String getCompactJsonTxt() {
StringBuilder sb=new StringBuilder(); for (Token t : tokens) {
sb.append(t.getText());
} return sb.toString();
}
}

节点类,这个类构成了语法树的节点:

package com.heyang;

import java.util.Collections;
import java.util.LinkedList;
import java.util.List; /**
* Json Node
* @author Heyang
*
*/
public class Node implements Comparable<Node>{ // There are value types
public static final int Type_String=1;
public static final int Type_Array=2;
public static final int Type_List=3; // Key always is String
private String key;
private Node parent; // There are three types of value
private int valueType;
private String valueString;
private List<Node> valueList; // indent depth
private int depth; public Node() { } public Node(String key,String value) {
this.key=key;
this.valueType=Type_String;
this.valueString=value;
this.depth=0;
} public Node(String key,int type) {
this.key=key;
this.valueType=type;
this.valueList=new LinkedList<Node>();
} public void addChild(Node child) {
if(valueList!=null) {
valueList.add(child);
child.parent=this; adjustDepth();
}
} private void adjustDepth() {
if(valueType==Type_List || valueType==Type_Array) {
for(Node json:valueList) {
json.depth=this.depth+1;
json.adjustDepth();
}
}
} public String toString() {
StringBuilder sb=new StringBuilder(); // key
String tabs=getIndentSpace();
sb.append(tabs); if(key!=null) {
sb.append(key);
sb.append(":");
} // value
if(valueType==Type_String) {
sb.append(valueString);
}else if(valueType==Type_Array) {
sb.append("[\n"); int n=valueList.size();
for(int i=0;i<n;i++) {
Node json=valueList.get(i);
if(i!=n-1) {
sb.append(json.toString()+",\n");
}else {
sb.append(json.toString()+"\n");
}
} sb.append(tabs+"]");
}else if(valueType==Type_List) {
sb.append("{\n"); Collections.sort(valueList); int n=valueList.size();
for(int i=0;i<n;i++) {
Node json=valueList.get(i);
if(i!=n-1) {
sb.append(json.toString()+",\n");
}else {
sb.append(json.toString()+"\n");
}
} sb.append(tabs+"}");
} return sb.toString();
} public int compareTo(Node other) {
return this.key.compareTo(other.key);
} private String getIndentSpace() {
return String.join("", Collections.nCopies(this.depth, " "));
} public String getKey() {
return key;
} public void setKey(String key) {
this.key = key;
} public Node getParent() {
return parent;
} public void setParent(Node parent) {
this.parent = parent;
} public List<Node> getValueList() {
return valueList;
}
}

节点树构建类,顾名思义,此类就是用类构建Node树的:

package com.heyang;

import java.util.List;
import java.util.Stack;
import java.util.regex.Matcher;
import java.util.regex.Pattern; /**
* JSOn tree builder
* @author heyang
*
* 2020年5月18日
*/
public class Builder {
private Node root;
private int index;
private List<Token> tokens; public Builder(List<Token> tokens) {
this.tokens=tokens;
this.index=1; this.root=new Node(null,Node.Type_List);
addSubNode2(this.root);
} /**
* Add branch/leaf to parent node
* @param parent
*/
private void addSubNode2(Node parent) {
if(parent==null) {
return;
} Stack<Token> stack=new Stack<Token>(); while(index<this.tokens.size()) {
Token token=tokens.get(index); if(token.getType()==Token.TYPE_OPEN_BRACE) {// {
Node newBraceNode=new Node(null,Node.Type_List); if(stack.size()>=2) {
Token colonToken=stack.pop();
Token keyToken=stack.pop(); if(colonToken.getType()==Token.TYPE_COLON && keyToken.getType()==Token.TYPE_TEXT) {
newBraceNode.setKey(keyToken.getText());
}
} parent.addChild(newBraceNode); index++;
addSubNode2(newBraceNode);
}else if(token.getType()==Token.TYPE_CLOSE_BRACE) { // }
String text=getTextInStack(stack); if(text.length()>0) {
final String keyValuePattern="(\"([_a-zA-Z]+[_a-zA-Z0-9]*)\")[:]([^,}]+)"; if(Pattern.matches(keyValuePattern,text)) {
java.util.regex.Pattern pattern=Pattern.compile(keyValuePattern);
Matcher matcher=pattern.matcher(text);
while(matcher.find()) {
Node txt=new Node(matcher.group(1),matcher.group(3));
parent.addChild(txt);
}
}
} stack.clear();
index++; addSubNode2(parent.getParent());
}else if(token.getType()==Token.TYPE_OPEN_BRACKET) { // [
Node newBracketNode=new Node(null,Node.Type_Array); if(stack.size()>=2) {
Token left1=stack.pop();
Token left2=stack.pop(); if(left1.getType()==Token.TYPE_COLON && left2.getType()==Token.TYPE_TEXT) {
newBracketNode.setKey(left2.getText());
}
} parent.addChild(newBracketNode); index++;
addSubNode2(newBracketNode);
}else if(token.getType()==Token.TYPE_CLOSE_BRACKET) { // ]
String text=getTextInStack(stack); if(text.length()>0) {
final String keyValuePattern="(\"([_a-zA-Z]+[_a-zA-Z0-9]*)\")[:]([^,}]+)"; if(Pattern.matches(keyValuePattern,text)) {
java.util.regex.Pattern pattern=Pattern.compile(keyValuePattern);
Matcher matcher=pattern.matcher(text);
while(matcher.find()) {
Node txt=new Node(matcher.group(1),matcher.group(3));
parent.addChild(txt);
}
}else { java.util.regex.Pattern pattern=Pattern.compile("([^,]+)");
Matcher matcher=pattern.matcher(text);
while(matcher.find()) {
Node txt=new Node(null,matcher.group(1));
parent.addChild(txt);
}
}
} stack.clear();
index++;
addSubNode2(parent.getParent());
}else if(token.getType()==Token.TYPE_COMMA) {
String text=getTextInStack(stack); if(text.length()>0) {
final String keyValuePattern="(\"([_a-zA-Z]+[_a-zA-Z0-9]*)\")[:]([^,}]+)"; if(Pattern.matches(keyValuePattern,text)) {
java.util.regex.Pattern pattern=Pattern.compile(keyValuePattern);
Matcher matcher=pattern.matcher(text);
while(matcher.find()) {
Node txt=new Node(matcher.group(1),matcher.group(3));
parent.addChild(txt);
}
}else { java.util.regex.Pattern pattern=Pattern.compile("([^,]+)");
Matcher matcher=pattern.matcher(text);
while(matcher.find()) {
Node txt=new Node(null,matcher.group(1));
parent.addChild(txt);
}
}
} stack.clear();
index++;
}else {
stack.push(token);
index++;
}
}
} private String getTextInStack(Stack<Token> stack) {
StringBuilder sb=new StringBuilder();
for(int i=0;i<stack.size();i++) {
Token t=stack.elementAt(i);
sb.append(t.getText());
} return sb.toString();
} public Node getRoot() {
return root;
}
}

入口类,这个类将以上四个类串联起来调用:

package com.heyang;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import com.heyang.util.BracketsBalanceChecker;
import com.heyang.util.CommonUtil; public class EntryPoint {
private final static Logger log = LoggerFactory.getLogger(EntryPoint.class); public static void main(String[] args){
log.info("JsonAnalyzer started."); if(args.length<1) {
log.error("Please set json file path in arguments.");
} String filePath=args[0];
log.info("Begin to read file:'{}'",filePath); try {
// Read context from file
String jsonTxt=CommonUtil.readTextFromFile(filePath);
log.info("Raw json text=\n{}",jsonTxt); // Check brackets balance
BracketsBalanceChecker bbc=new BracketsBalanceChecker();
boolean balanced=bbc.isBracketsBalanced(jsonTxt);
if(balanced) {
log.info("The brackets in read content are balanced.");
} // Parse json to tokens
Lexer l=new Lexer(jsonTxt);
log.info("Compact json text=\n{}",l.getCompactJsonTxt()); // Build json node tree
Builder b=new Builder(l.getTokenList());
Node root=b.getRoot();
log.info("Formatted json=\n{}",root.toString()); }catch(Exception ex) {
log.error(ex.getMessage());
}finally {
log.info("JsonAnalyzer end.");
}
}
}

最后的感悟:

我在开发生涯中不止一次的遇到复杂的文本解析任务,做文本解析,第一反应往往是<编译原理>的那些东西,但笔者不是计算机科班出身,把网上推荐的龙书虎书鲸书买来一看头都要炸了,开发更是在一番斗争后搁置或是简化了. 多次后我终于想到,做文本解析并非一定要先啃下那些大部头书,用递归向下一样能完成任务,适合我的才是最好的.我先用递归向下的方式解析文本,由易到难,再辅以看书,编译原理和任务就可以并行的.这比啃不动书而止步不前要强得多.编程就是这样,能动手才算真正掌握一门技能, 学东西就该学明白,钻透!纸上得来一知半解,最终还是要重新夯实!

--2020年5月18日--

JsonAnalyzer 源码下载的更多相关文章

  1. C# ini文件操作【源码下载】

    介绍C#如何对ini文件进行读写操作,C#可以通过调用[kernel32.dll]文件中的 WritePrivateProfileString()和GetPrivateProfileString()函 ...

  2. C# Excel导入、导出【源码下载】

    本篇主要介绍C#的Excel导入.导出. 目录 1. 介绍:描述第三方类库NPOI以及Excel结构 2. Excel导入:介绍C#如何调用NPOI进行Excel导入,包含:流程图.NOPI以及C#代 ...

  3. C# 条形码操作【源码下载】

    本篇介绍通过C#生成和读取一维码.二维码的操作. 目录 1. 介绍:介绍条形码.条形码的分类以及ZXing.Net类库. 2. 一维码操作:包含对一维码的生成.读取操作. 3. 二维码操作:包含对二维 ...

  4. DataGridView绑定源码下载

    效果图: 源码下载:http://hovertree.com/h/bjaf/bbot18bj.htm 上面源码不包含数据库的查询,需要获取数据库数据的话,请看这个的源码: http://hovertr ...

  5. Web 开发中很实用的10个效果【附源码下载】

    在工作中,我们可能会用到各种交互效果.而这些效果在平常翻看文章的时候碰到很多,但是一时半会又想不起来在哪,所以养成知识整理的习惯是很有必要的.这篇文章给大家推荐10个在 Web 开发中很有用的效果,记 ...

  6. Android 源码下载方法(Git 方式clone)

    Android源码对于Android开发者来说,迟早有一天你会用到的,所以就记录一下,分享给读者,希望对读者有用 这里需要使用到Git相关知识,不清楚的可以先阅读,了解的可以跳过 Git-Tortoi ...

  7. yate: windows下源码下载,配置,编译

    源码下载:使用svn下载checkout:http://voip.null.ro/svn/yate/trunk 配置:(本人使用的是vs2008,故下载的qt工具都是对应2008) 1. 下载并安装q ...

  8. Android斗地主棋牌游戏牌桌实现源码下载

    本次给大家分享下Android斗地主棋牌游戏牌桌实现源码下载如下: 为了节约内存资源,每张扑克牌都是剪切形成的,当然这也是当前编程的主流方法. 1.主Activity package com.biso ...

  9. Spring1:Spring简介、环境搭建、源码下载及导入MyEclipse

    框架学习前言 这个模块是面向Spring的,Spring的学习我是这么想的: 1.简单介绍Spring,主要是从网上借鉴一些重点 2.尽量说明清楚Spring的使用方法以及细节点 3.尽量以自己的理解 ...

随机推荐

  1. 19、State 状态模式

    “人有悲欢离合,月有阴晴圆缺”,包括人在内,很多事物都具有多种状态,而且在不同状态下会具有不同的行为,这些状态在特定条件下还将发生相互转换.就像水,它可以凝固成冰,也可以受热蒸发后变成水蒸汽,水可以流 ...

  2. Java查表法实现十进制转化成其它进制

    首先了解十进制转化成二级制的原理 156的二进制为: 156 % 2 = 78 …… 0 83 % 2 = 39 …… 0 39 % 2 = 19 …… 1 19 % 2 = 9 …… 1 9 % 2 ...

  3. 【JavaScript】windows.open用法详解

    windows.open("URL","窗口名称","窗口外观设定");的用法详解 function onNewWindows(redire ...

  4. Quartz.Net的基础使用方法,多任务执行

    接着上面单任务执行的代码做一下简单的扩展 主要看下面这段代码,这是Quartz多任务调度的方法,主要就是围绕这个方法去扩展: // // 摘要: // Schedule all of the give ...

  5. Vuex mapAction的基本使用

    mapAction-store中的异步方法 import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex) export default new ...

  6. Uni-app从入门到实战

    前言 uni-app是一个使用vue.js开发跨平台应用的前端框架,开发者只需要编写一套代码,便可以发布到IOS.Android和微信小程序等多个平台.所以我打算学习下这个框架,快速浏览了一遍官网之后 ...

  7. Vue.js中传值给子部件及触发动作的问题

    最近研究一个用vue.js做的程序并修改增加功能.其中用到传值给子部件等问题. template中有个子部件: <template> ...... <child-form  v-if ...

  8. PyTorch学习笔记及问题处理

    1.torch.nn.state_dict(): 返回一个字典,保存着module的所有状态(state). parameters和persistent_buffers都会包含在字典中,字典的key就 ...

  9. Vue的数据响应式

    getter和setter怎么用 示例代码 getter ,关键词为 get ,用于获取一个值.定义时为函数,但是使用时不用加括号. setter 用于对数据的改写 Object.defineProp ...

  10. Kubernetes K8S之资源控制器RC、RS、Deployment详解

    Kubernetes的资源控制器ReplicationController(RC).ReplicaSet(RS).Deployment(Deploy)详解与示例 主机配置规划 服务器名称(hostna ...