项目源码 https://github.com/hao297531173/jsonExplorer
说在前面的话
之前我读过一本叫做《自制编程语言》 的书,给我的启发真的很大,这本书的源码在https://github.com/hao297531173/DIYProgramLanguage
我这次项目借鉴了书中的很多编码技巧,下面我就详细介绍一下核心代码编写思路
PS:对外接口的写的不多,如果你觉得这个项目很有用的话,可以自己写接口
简单用法实例
#include <cstdio>
#include <cstring>
#include <iostream>
#include "jsonExplorer.h"
using namespace std;
void get(){
explorer e("test.json");
e.dump();
char a[2][100] = {"Tony", "course"};
char b[2][100] = {"Mary", "age"};
Value val;
val = e.findElement(a, 2);
if(val.type != ERROR){
e.traverse(val);
}
val = e.findElement(b, 2);
if(val.type != ERROR){
e.traverse(val);
}
}
int main(){
get();
return 0;
}
只需要引入头文件"jsonExplorer.h"就行了,然后实例化一个explorer类,构造函数中的参数就是你要解析的json文件。
之后调用接口dump()将json文件内容存入数据结构中,使用findElement(char a[][], int length);即可找到你要找的值,
其中二维数组就是你要找的值所对应的键,由外向内寻找。
输出结果
object analysis
object analysis
list analysis
object analysis
list analysis
[
"calculus" ,"linear algebra"
](NUMBER):19
两个主要类的定义
parser
class parser{
const char* file; //文件路径
FILE *fileWrite; //写出的文件指针
FILE *fp; //文件指针
Token result;
int line; //标识行
char ch;
int tokenCount = 0;
//判断token是否是关键字
TokenType isKeyword(char a[], int length);
//跳过空格,运行完后fp指向不为空格或者回车的字符
void skipBlanks();
//获取下一个token
void getNextToken();
//显示token
void showToken();
//初始化result
void initResult();
public:
//解析主函数(用于测试)
void parse();
//发送token给别的程序(给别的程序调用)
Token pullToken();
//构造函数(一个无参数,一个有参数)
parser(){}
parser(char *file){
this->file = file;
this->fp = fopen(this->file, "r"); //打开文件
this->fileWrite = fopen("output.txt", "w");
this->line = 1;
initResult();
ch = fgetc(fp); //先读取第一个字符
//printf("construct : %c\n", ch);
}
void initParser(char *file){
this->file = file;
this->fp = fopen(this->file, "r"); //打开文件
this->fileWrite = fopen("output.txt", "w");
this->line = 1;
initResult();
ch = fgetc(fp); //先读取第一个字符
}
//析构函数,主要用来关闭文件
~parser(){
fclose(this->fileWrite);
fclose(this->fp);
}
};
类中有一个result属性,记录当前的token(有关token的定义会在后面介绍),提供两个构造函数,一个无参数的,一个有参数的,无参数的构造函数需要使用initParser()来初始化parser对象,参数就是待解析的json文件,有参数的构造函数直接将文件名传入就行了。
parse()是测试用的接口,一次将所有的token都输出到文件中(我默认是写入output.txt中,可以在构造函数中修改)
pullToken()时候提供给别的程序的接口,一次解析一个token,返回值为Token类型,返回自身属性result
explorer
class explorer{
Value value; //用于存储json数据
Value error; //用于解析错误时返回
Token token; //用于存储当前token
int num; //用于控制每行缩进个数
int depth; //用于记录查询深度
parser p;
char *file;
//解析花括号
Value analysisBrace();
//解析方括号
Value analysisSquare();
//递归遍历对象
void traverseObject(Value val);
//递归遍历列表
void traverseList(Value val);
/*将遍历结果输出到文件*/
void outputObject(FILE *fp, Value val);
void outputList(FILE *fp, Value val);
/*通过key值递归查询*/
Value findValue(string key,Value val);
Value findValue(Value val, char a[][MAXSIZE], int length);
/*修改value的值*/
Value changeValue(string key, Value ch, Value &val);
public:
//构造函数(a是文件名字符串)
explorer(char *a){
this->file = a;
p.initParser(file);
token = p.pullToken(); //获取第一个token
error.type = ERROR;
this->depth = 0; //从0开始计数
}
~explorer(){}
//提供给外部的接口
void dump();
//写一个遍历函数
void traverse();
void traverse(Value val);
//输出到文件
void output(char *file,Value val);
void output(char *file);
//根据key值查value并且返回一个value变量
Value findElement(string key);
Value findElement(char key[][MAXSIZE], int length);
//根据二维数组找Value值
//Value findElement(char a[][]);
//修改某个key中的value值
//参数分别是key值,要改变的value,从那个value开始查找
//第三个参数缺省的话就是改变类自己的value
Value changeValueOfKey(string key, Value ch, Value &val);
Value changeValueOfKey(string key, Value ch);
};
构造函数的参数是待解析的文件名。
dump()是提供给用户的接口,将json数据存入内部数据结构中,也就是类中value属性中
traverse()是遍历Value数据(关于Value的定义会在后面介绍),无参数的就是遍历类本身的value,带参数的就是遍历传入的value。
output()将Value类型的数据以json格式存入json文件中,文件名就是给定的第一个参数,同样提供写入自身value值和传入value值两个版本。
findElement()就是搜索的接口,二维数组就是要找的值所对应的键(由外向里),length就是键的个数。
主要的数据结构
在介绍算法之前,我们先来看一下我采用的数据结构
首先是Token结构体
typedef enum{
TOKEN_UNKNOWN, //未知类型
TOKEN_LEFT_BRACE, //左花括号
TOKEN_RIGHT_BRACE, //右花括号
TOKEN_LEFT_SQUARE, //左方括号
TOKEN_RIGHT_SQUARE, //右方括号
TOKEN_COMMA, //逗号
TOKEN_QUOTATION, //双引号
TOKEN_NULL, //NULL
TOKEN_FLAG, //斜杠
TOKEN_RE_FLAG, //反斜杠
TOKEN_COLON, //冒号
TOKEN_EOF
}TokenType;
struct Token{
TokenType type; //token类型
int length; //长度
char token[MAXSIZE];
int lineNo; //行号
};
TokenType是一个枚举,用来表示token的类型,需要解析其他的token的时候在这里添加入其他类型即可。
length记录了token的长度
token[]记录了token的字面量
l