Json数据格式解析·编译器观点
现在很多软件项目都选择Json为配置文件或数据交换格式,有众多的Json解析库可以选择,有的接口简单易用,有的效率高,然而我仍觉得不够简单,前一段时间一直在编写编译器前端,Json文件的解析其实很类似于编程语言的解析,所以我考虑自己编写一个Json的解析引擎。
说做就做。
在开始之前,我们需要做一些准备工作,首先我们需要了解Json的格式定义,通过Json的官方网站http://www.json.org/我们拿到了Json数据格式的文法定义,看来Json是一种很简单的数据格式,但是我们仍需要仔细研究、认真理解,找出其中的规律,找到我们的“模式”。
我们先来看看网站的介绍,Json网站上都是英文的介绍,我们来简单地翻译一下,再自己总结一下:
JSON is built on two structures:
JSON 由两种数据结构构成
- A collection of name/value pairs. In various languages, this is realized as an object, record, struct, dictionary, hash table, keyed list, or associative array.
- 一个名字/值 对的集合。在不同的语言中,这被理解为一个对象,记录,结构体,表,哈希表,有键的列表,或者关联数组。
- An ordered list of values. In most languages, this is realized as an array, vector, list, or sequence.
- 一个有序的值列表。在大多数语言中,这被理解为一个数组,向量,列表,或序列
These are universal data structures. Virtually all modern programminglanguages support them in one form or another. It makes sense that a dataformat that is interchangeable with programming languages also be based onthese structures.
这些都是通用数据结构。现代的编程语言以某种格式支持它们。它使得基于这些结构的一种数据格式可以在编程语言中进行交互。
In JSON, they take on these forms:
在JSON中,这些表现为以下格式:
An object is an unordered set of name/value pairs. An object begins with { (left brace) and ends with } (right brace). Each name is followed by: (colon) and the name/value pairs are separated by , (comma).
一个对象是一个无序的名字/值 对。一个对象以{左大括弧开始并以}右大括弧结束。每一个名字后面跟着:冒号名字/值 对 以,逗号分隔。
An array is an ordered collection of values. An array begins with [ (left bracket) and ends with ] (right bracket). Values are separated by, (comma).
一个数组是一个有序的值集合,一个数组以[左方括弧开始并以]右方括弧结束。值以,逗号分隔。
A value can be a string in double quotes, or a number, or true or false or null, or an object or an array. These structures can be nested.
一个值可以是一个双引号括起来的字符串,或者一个数值,或者true或false或null,或者一个对象或一个数组。这些数据结构可以嵌套。
A string is a sequence of zero or more Unicode characters, wrapped in doublequotes, using backslash escapes. A character is represented as a singlecharacter string. A string is very much like a C or Java string.
一个字符串是一个零个或多个Unicode编码的字符,以双引号包裹,使用反斜杠转义。一个字符表示一个单字符的字符串。一个字符串非常类似于C语言或Java语言的字符串。
A number is very much like a C or Java number, except that the octal andhexadecimal formats are not used.
一个数值是非常类似于C语言或Java语言的字符,除了不使用八进制和十六进制格式。
Whitespace can be inserted between any pair of tokens. Excepting a fewencoding details, that completely describes the language.
空白可以插入到任何关键字对之间,除了很少的一些编码的细节,这些完整地描述了Json语言。
下面是Json的文法,通过这样的文法定义,我们可以利用lex/yaccflex/bison这些编译解析器工具来开发Json的解析器,但实际上我们用不到,Json相对比较简单完全可以手工解析。
object
{}
{ members }
members
pair
pair , members
pair
string : value
array
[]
[ elements ]
elements
value
value , elements
value
string
number
object
array
true
false
null
string
""
" chars "
chars
char
char chars
char
any-Unicode-character-
except-"-or-\-or-
control-character
\"
\\
\/
\b
\f
\n
\r
\t
\u four-hex-digits
number
int
int frac
int exp
int frac exp
int
digit
digit1-9 digits
- digit
- digit1-9 digits
frac
. digits
exp
e digits
digits
digit
digit digits
e
e
e+
e-
E
E+
E-
单单从这些说明和图示好像还不够直观,我们再结合一些例子来说明。
我们先来看第一个例子:
{
}
这个例子只有一对大括弧,但是这是一个合法的JSON,它表示一个对象,这个对象的成员为空
第二个例子
{
"key":0
}
这个例子表示一个对象,它包含一个成员,这个成员是一个名字/值对 名字为字符串key值为数值0
第三个例子
{
"key":"x"
}
这个例子表示一个对象,它包含一个成员,这个成员是一个名字/值对 名字为字符串key值为字符串x
第四个例子
[
{
"object1-key1":123,
"object1-key2":"abc"
},
{
"test":"x",
"test2":"x2"
},
{
"key":"y"
}
]
这个例子表示一个数组,这个数组包括三个成员,这三个成员表示三个对象,第一个对象包含两个成员,分别是两个名字/值对 ,第二个对象也包含两个成员,也分别是两个名字/值对,第三个对象包含一个成员,是一个名字/值对
通过这几个例子,我们对JSON有了大体上的理解,但这还不足以编写Json的解析引擎,不过我们没有必要一次把所有情况都考虑到,先从最简单的开始,再逐步加深理解,逐步实现更复杂的。
我们在开发解析器的时候一定是先把输入文件分解为关键字,然后再根据上下文把关键字组织起来解释为有意义的对象或数据结构,那么我们的手工解析器其实就是一个大循环,它逐个读入字符,当读入的字符能够成为一个关键字时就停下来,交给解析器进行判断如何进行下一步的处理,然后再继续读入字符,循环这个过程直到达到文件结尾。
在这里我们选用C++来编写JSON的解析器。
首先我们考虑先把JSON文件当做一个字符串来处理,省略掉读取文件这一步,之后再把它加上。那么,整个JSON文件都可以存储于一个字符串中。我们在这里选用STL的字符串,使用STL的迭代器来访问每一个字符,以迭代器自增的方式来实现整个串的访问。
好了,根据我们对JSON文法的观察,我们发现一个JSON格式的文件必然包含一个“根”,这个“根”要么是一个对象,要么是一个数组,必是其一。所以{左大括弧或[左方括弧必然是JSON的起点,}右大括弧或]右方括弧必然是JSON的终点。
而一个JSON对象或一个JSON数组仅仅只是JSON 值的容器,我们可以认为JSON是树形结构,树的根或是一个对象或是一个数组,一个容器中可以包含多个值的列表。这样的一棵树是一个B+树,和文件系统很相似。所以我们的解析工作实际上就是如何构建这样一颗语法树,而访问JSON其实就是找到这棵树的节点。所以我们获取JSON值的时候可以以文件系统路径的方式,通过给出路径和名字/值对的名字 或者值在容器中的索引就可以了。
我们现在需要设计几个类来完成Json文件的解析工作
第一个类,表示我们的解析器对象
第二个类,表示语法解析的内部工作类
第三个类,表示一个JSON的容器节点
第四个类,表示一个JSON值
我还定义了两个枚举类型JsonNodeType,JsonValueType分别表示JSON容器节点的类型和JSON值的类型
这是我的JSON解析器的头文件:
源代码我放在github上托管,有兴趣的读者可以自由下载
https://github.com/zhaoliangcn/CppEasyJson.git
https://github.com/zhaoliangcn/JavaEasyJson.git
class JsonLex;
class JsonNode;
class JsonValue;
typedef enumJsonNodeType
{
NODE_OBJECT,
NODE_ARRAY,
};
typedef enumJsonValueType
{
VALUE_NUM_INT,
VALUE_NUM_FLOAT,
VALUE_STRING,
VALUE_BOOL,
VALUE_ARRAY,
VALUE_OBJECT,
VALUE_NULL,
};
typedef std::vector<JsonValue *> JsonValues;
const staticcharJsonLeftBrace = '{';
const staticcharJsonRightBrace = '}';
const staticcharJsonLeftBracket = '[';
const staticcharJsonRightBracket = ']';
const staticcharJsonEscapeCharacter = '\\';
const staticcharJsonColon = ':';
const staticcharJsonDoubleQuote = '"';
const staticcharJsonComma = ',';
const staticcharJsonNodeRefrence = '.';
const staticcharJsonStar = '*';
const staticcharJsonHash = '#';
const staticcharJsonSlash = '/';
class JsonLex
{
public:
JsonLex();
~JsonLex();
boolParseString(const char* jsonstring, JsonNode **root);
JsonValue * BuildJsonValue(std::string::iterator&it,JsonNode * parentnode);
JsonNode * BulidJsonNode(std::string::iterator& it,JsonNode *parentnode, JsonNodeType nodetype);
voidGoCommentEnd(std::string::iterator&it,std::string commentstyle);
boolTokenIsComment(std::string token);
std::string GetNextToken(std::string::iterator& it,booltonextJsonDoubleQuote);
boolAssingStringToJsonValue(JsonValue *value, std::string & text);
std::string json;
std::string currenttoken;
std::string prevtoken;
};
class CppEasyJson
{
public:
CppEasyJson();
~CppEasyJson();
boolParseString(const char* jsonstring);
boolParseFile(const char*jsonfile);
boolGetValue(const char*nodepath, char * value, size_tvaluesize);
boolGetValue(const char*nodepath, int & value);
boolGetValue(const char*nodepath, double & value);
boolGetValue(const char*nodepath, bool & value);
boolGetValue(const char*nodepath, JsonValue ** jsvalue);
voidRelease();
JsonNode * FindNodeInternal(std::stringpath, JsonNode * parentnode,int&index, std::string &keyname);
std::string jsoncontent;
JsonLex jsonlex;
JsonNode *jsonroot;
};
class JsonValue
{
public:
JsonValue();
~JsonValue();
JsonValueType type;
std::string name;
std::string str;
intvi;
doublevd;
boolvbl;
JsonNode * node;
};
class JsonNode
{
public:
JsonNode();
~JsonNode();
JsonValues values;
JsonNodeType type;
};