Json解析器的设计与实现

简介

本文设计并实现了一个简化版的json解析器。可解析json中的对象数组字符串。同时解析器还可把对象编码成json的格式。实现采用C++语言。
需要前置知识:

  • 编译原理
  • C++程序设计

文法

根据旧版json的文法规则,可反推出如下上下文无关文法,并使用EBNF表示:

其中 \uxxxx 表示除了 \ 和 " 以外的字符

<json>    -> <object> | <array> | <string>
<object>  -> '{' {<string> : <json> , } <string> : <json> '}' 
          -> '{'  '}' 
<array>   -> '[' { <json> , } <json> ']'
          -> '[' ']'
<string>  -> "{<char>}"
<char>    -> `\uxxxx` 
          -> \ (\ | ")

为了能够变成递归下降算法可处理的文法,即LL(1)文法。重写后的文法如下:

<json>    -> <object> | <array> | <string>
<object>  -> '{' [ <string> : <json> { , <string> : <json> } ] '}' 
<array>   -> '[' [ <json> { , <json> } ] ']'
<string>  -> "{<char>}"
<char>    -> `\uxxxx` 
          -> \ (\ | ")

词法分析

本文把<string>的识别作为词法分析的内容。因此,词法分析的任务就是从输入字符序列中识别以下符号:

符号对应的识别内容正规式
对象左边界{{
对象右边界}}
数组左边界[[
数组右边界]]
字符串表达式<string>(\\ | \” |\uxxxx)*"
逗号,,
冒号::

由于字符串表达式比较复杂,而其它的符号(又称单词)很容易可以画出其最小的DFA,因此下面重点只介绍字符串表达式的DFA推导过程。
首先根据正规式,画出其NFA,如下:

A B C " " \ \ \ " \uxxxx

根据NFA转换DFA的算法,画出如下用于推导的表格:
其中S表示开始状态,Z表示结束状态。

状态 \ 字符"\\uxxxx
{S}{A}
{A}{Z}{B, C}{A}
{Z}
{B, C}{A}{A}

将以上出现的状态从上至下重新命名为:S、S1、Z、S2,绘制出DFA如下:

S1 S2 " " \ \uxxxx " \

发现并没有多余的状态或等价的状态,因此DFA已经最小。根据此DFA,可编写出相应的词法分析程序。见实现部分。

语法分析

语法分析使用递归下降算法。每一个非终结符都将由一个函数来处理。语法分析部分需要识别以下三个非终结符:

非终结符处理函数备注
<json>inferElement()自顶向下推导json
<object>inferObject()自顶向下推导对象表达式
<array>inferArray()自顶向下推导数组表达式

由于<string>在词法分析阶段已经识别,因此在此阶段视为终结符

语义分析及目标代码生成

语义分析和语法分析同时进行。每当一个非终结符被识别完成,则生成一个对应的C++对象(可看作是目标代码)。当最顶层的非终结符识别完成,则翻译工作完成。
中间代码及优化略去

实现

以下是是完整的实现代码。您可直接拷贝到您的项目中作为一个单独的文件,并以头文件的方式引用。

/**
 * JSON 解析器-简化版
 * 本解析器实现了以下两个功能:
 * 1.把字符串转成编程语言可处理的对象
 * 2.构造对象从而生成json字符串
 * 但因为时间关系,懒得做得太复杂~~,因此存在如下局限性:
 * 1. json语法经过了简化,只支持string,array和object,不支持null、number、boolean,因此不适用于生产环境
 * 2. 不支持中文字符的处理
 * */
#pragma once
#include <vector>
#include <map>
#include <string>
#include <memory>
#include <sstream>

using namespace std;

namespace json{
	/**
	 * 通用基类
	 */
	class Element{
		public:
		virtual shared_ptr<string> toString() = 0;
	};

	/**
	 * 对象
	 */
	class Object : public Element {
		private:
		map<string, shared_ptr<Element>> attributes;

		public:
		/** 根据key获取值 */
		shared_ptr<Element> get(string key){
			auto iter = this->attributes.find(key);
			shared_ptr<Element> element = nullptr;
			if(iter != this->attributes.end()){
				element = iter->second;
			}
			return element;
		}

		/** 设置元素 */
		void set(string key, shared_ptr<Element> value){
			this->attributes[key] = value;  // 暂时不支持null值,所以不用判定
		
		}

		/** 转成字符串 */
		virtual shared_ptr<string> toString(){
			stringstream ss;
			ss << "{";
			for(auto i = this->attributes.begin(); i != this->attributes.end(); ){
				ss << "\"";
				ss << i->first;
				ss << "\"";
				ss << ":";
				ss << *(i->second->toString());
				i ++;
				if(i != this->attributes.end()){
					ss << ",";
				}
			}
			ss << "}";	
			return make_shared<string>(ss.str());
		}
	};

	/**
	 * 数组
	 */
	class Array : public Element{
		private:
		vector<shared_ptr<Element>> elements;


		public:
		/** 数组元素个数 */
		int size(){
			return this->elements.size();
		}

		/** 添加元素到尾 */
		void add(shared_ptr<Element> element){
			this->elements.push_back(element);
		}

		/** 根据索引获取元素 */
		shared_ptr<Element> get(int index){
			shared_ptr<Element> element = nullptr;
			if(index >= 0 && index < size()){
				element = this->elements[index];
			}
			return element;
		}

		/** 转成字符串 */
		virtual shared_ptr<string> toString(){
			stringstream ss;
			ss << "[";
			for(int i = 0; i < size(); i++){
				ss << *(this->elements[i]->toString());
				if (i != size() - 1){
					ss << ",";
				}
			}
			ss << "]";
			return make_shared<string>(ss.str());
		}
	};

	/**
	 * 字符串
	 */
	class String : public Element {
		private:
			shared_ptr<string> str = nullptr;
		public:
		/**
		 * 获取内置字符串
		 */
		shared_ptr<string> get(){
			return this->str;
		}

		/**
		 * 设置内置字符串
		 */
		void set(shared_ptr<string> str){
			this->str = str;
		}

		/**
		 * 转成字符串
		 */
		virtual shared_ptr<string> toString(){
			stringstream ss;
			ss << "\"";
			for(int i = 0; i < this->str->size(); i++){
				char ch = (*(this->str))[i];
				if(ch == '\"' || ch == '\\'){
					ss << '\\';
				}
				ss << ch;
			}
			ss <<  "\"";
			return make_shared<string>(ss.str());	
		}
	};
	


	
	/**
	 * Facade 类
	 * 用于把字符串解析成对象
	 */
	class JSON{
		private:
		static const string LEXICAL_ERROR;
		static const string GRAMMA_ERROR;

		public:

		
		/**
		 * 词法分析的目标。
		 * */
		enum class Symbol {
			OBJECT_L_BOUND, // 对象左边界
			OBJECT_R_BOUND, // 对象右边界
			ARRAY_L_BOUND,  // 数组左边界
			ARRAY_R_BOUND,  // 数组右边界
			STRING,         // 字符串表达式
			COMMA,          // 逗号
			COLON,          // 冒号
			EOS             // 结束标志,即#  
		};

		/**
		 * 符号对象
		 * */
		struct Word{
			Symbol symbol;
			string value;

			Word(Symbol symbol)
			: Word(symbol, "")
			{}

			Word(Symbol symbol, string value)
			: symbol(symbol)
			, value(value)
			{}
		};

		/**
		 * 词法分析器
		 * 把输入的字符序列翻译成为单词串
		 * @param str 源语言
		 * @return 单词列表。(逆序存储)
		 */
		static vector<Word>  lexical_analyze(string str){
			vector<Word> words;

			// 总字符个数
			int count = str.size();

			// 当前字符
			int sym = 0;

			// 开始循环查找单词
			while (sym < count)
			{
				if(str[sym] == ' ' || str[sym] == '\r' || str[sym] == '\n' || str[sym] == '\t' || str[sym] == '\f'){
					sym += 1;
				}else if(str[sym] == '{'){
					words.push_back(Word(Symbol::OBJECT_L_BOUND));
					sym += 1;
				}else if(str[sym] == '}'){
					words.push_back(Word(Symbol::OBJECT_R_BOUND));
					sym += 1;
				}else if(str[sym] == '['){
					words.push_back(Word(Symbol::ARRAY_L_BOUND));
					sym += 1;
				}else if(str[sym] == ']'){
					words.push_back(Word(Symbol::ARRAY_R_BOUND));
					sym += 1;
				}else if(str[sym] == ','){
					words.push_back(Word(Symbol::COMMA));
					sym += 1;
				}else if(str[sym] == ':'){
					words.push_back(Word(Symbol::COLON));
					sym += 1;
				}else if(str[sym] == '\"'){
					sym += 1; 
					if(sym >= count) throw LEXICAL_ERROR;
					stringstream ss;
					while (true)
					{
						if(str[sym] == '\\'){
							sym += 1;
							if(sym >= count) throw LEXICAL_ERROR;
							if(str[sym] == '\\'){
								ss << '\\';
							}else if(str[sym] == '\"'){
								ss << '\"';
							}else {
								throw LEXICAL_ERROR;
							}
							sym += 1;
							if(sym >= count) throw LEXICAL_ERROR;
						}else if(str[sym] == '\"'){
							words.push_back(Word(Symbol::STRING, ss.str()));
							sym += 1;
							break;
						}else{
							ss << str[sym];
							sym += 1;
							if(sym >= count) throw LEXICAL_ERROR;
						}
					}
				}else {
					throw LEXICAL_ERROR;
				}
			}
			

			return words;
		}


		private:
		/**
		 * 推导object
		 */ 
		shared_ptr<Object> inferObject(const vector<Word> & words, int & current){
			shared_ptr<Object> object = make_shared<Object>();
			if(words[current].symbol != Symbol::OBJECT_L_BOUND){
				throw GRAMMA_ERROR;
			}

			current += 1;
			const Word& w = words[current];
			if(w.symbol == Symbol::OBJECT_R_BOUND){
				current += 1;
				return object;
			}else{
				if(w.symbol != Symbol::STRING){
					throw GRAMMA_ERROR;
				}
				current += 1;
				if(words[current].symbol != Symbol::COLON){
					throw GRAMMA_ERROR;
				}
				current += 1;
				shared_ptr<Element> element = inferElement(words, current);
				object->set(w.value, element);

				while (words[current].symbol == Symbol::COMMA)
				{
					current += 1;
					const Word& w2 = words[current];
					if(w2.symbol != Symbol::STRING){
						throw GRAMMA_ERROR;
					}
					current += 1;
					if(words[current].symbol != Symbol::COLON){
						throw GRAMMA_ERROR;
					}
					current += 1;
					shared_ptr<Element> element2 = inferElement(words, current);
					object->set(w2.value, element2);
				}
				
				if(words[current].symbol != Symbol::OBJECT_R_BOUND){
					throw GRAMMA_ERROR;
				}

				current += 1;
				return object;
			}
		}


		/**
		 * 推导array
		 */ 
		shared_ptr<Array> inferArray(const vector<Word> & words, int & current){
			shared_ptr<Array> array = make_shared<Array>();
			if(words[current].symbol != Symbol::ARRAY_L_BOUND){
				throw GRAMMA_ERROR;
			}

			current += 1;
			const Word& word = words[current];
			if(word.symbol == Symbol::ARRAY_R_BOUND){
				current += 1;
				return array;
			}else{
				shared_ptr<Element> element = inferElement(words, current);
				array->add(element);
				while(words[current].symbol == Symbol::COMMA){
					current += 1;
					shared_ptr<Element> element2 = inferElement(words, current);
					array->add(element2);
				}
				if(words[current].symbol != Symbol::ARRAY_R_BOUND){
					throw GRAMMA_ERROR;
				}
				current += 1;
				return array;
			}
		}

		/**
		 * 推导json
		 */ 
		shared_ptr<Element> inferElement(const vector<Word> & words, int & current){
			const Word& word = words[current];
			if(word.symbol == Symbol::STRING){
				shared_ptr<String> str = make_shared<String>();
				str->set(make_shared<string>(word.value));
				current += 1;
				return str;
			}else if(word.symbol == Symbol::OBJECT_L_BOUND){
				return inferObject(words, current);
			}else if(word.symbol == Symbol::ARRAY_L_BOUND){
				return inferArray(words, current);
			}else {
				throw GRAMMA_ERROR;
			}
		}


		/**
		 * 语法分析器 
		 * 根据源语言单词序列转换成对象
		 * @param words 源语言的单词序列
		 * @return 分析完成翻译的json对象
		 */
		shared_ptr<Element> gramma_analyze(const vector<Word> & words){
			int current = 0;
			return inferElement(words, current);
		}

		public:
		/**
		 * 解析器
		 * 把源语言字符串依次经过词法分析、语法分析、语义分析
		 * 最终生成目标语言,即c++对象
		 * */
		shared_ptr<Element> parse(string str){
			vector<Word> words = lexical_analyze(str);
			words.push_back(Word(Symbol::EOS));
			shared_ptr<Element> element = gramma_analyze(words);
			return element;
		}

	
	};

	const string JSON::LEXICAL_ERROR = "词法错误";
	const string JSON::GRAMMA_ERROR = "语法错误";

};

使用示例

解析json字符串

本示例从文件中读取json字符串,并进行解析。文件内容如下:


{
    "code": "00000",
    "message": "operate  successfully! test slash: \\ , test quota: \"",
    "data":   [
        {"id": "1", "name": "zhangsan", "age": "18", "authorities": ["admin", "user"]},
        {"id": "2", "name": "lisi", "age": "18", "authorities": ["admin"]},
        {"id": "3", "name": "wangwu", "age": "18", "authorities": ["user"]}
    ]
}

下面是是使用解析器json::JSON进行解析的代码

#include <iostream>
#include <fstream>
#include <cassert>
#include "json.hpp"    // 引用库文件
using namespace std;

void example_decode(){
	// 读取文件内容
	ifstream is("json.json");
	if(is.fail()){
		cout << "read file failed" << endl;
		return;
	}
	ostringstream stringBuffer;
	string line;
	while(getline(is, line)){
		stringBuffer << line << endl;
	}
	is.close();

	cout << "读取文件结果:" << endl;
	cout << stringBuffer.str() << endl;

	// json解析
	json::JSON parser;
	shared_ptr<json::Element> element = parser.parse(stringBuffer.str());
	cout << "解析结果:"<< *(element->toString()) << endl;
	cout << "读取数据: " << endl;

	// 数据读取
	shared_ptr<json::Object> object = dynamic_pointer_cast<json::Object>(element);
	shared_ptr<json::String> code = dynamic_pointer_cast<json::String>(object->get("code"));
	shared_ptr<json::String> message = dynamic_pointer_cast<json::String>(object->get("message"));
	shared_ptr<json::Array> data = dynamic_pointer_cast<json::Array>(object->get("data"));
	cout << "code = " << *(code->get()) << endl;
	cout << "message = " << *(message->get()) << endl;
	for(int i = 0; i < data->size(); i++){
		shared_ptr<json::Object> obj = dynamic_pointer_cast<json::Object>(data->get(i));
		shared_ptr<json::String> id = dynamic_pointer_cast<json::String>(obj->get("id"));
		shared_ptr<json::String> name = dynamic_pointer_cast<json::String>(obj->get("name"));
		shared_ptr<json::String> age = dynamic_pointer_cast<json::String>(obj->get("age"));
		shared_ptr<json::Array> authorities = dynamic_pointer_cast<json::Array>(obj->get("authorities"));
		cout << "id = " <<  *(id->get()) << ",";
		cout << "name = " <<  *(name->get()) << ",";
		cout << "age = " <<  *(age->get()) << ",";
		cout << "authorities = ";
		for(int j = 0; j < authorities->size(); j++){
			shared_ptr<json::String> authority = dynamic_pointer_cast<json::String>(authorities->get(j));
			cout <<  *(authority->get()) << " ";
		}
		cout << endl;
	}
}


int main(){
	example_decode();
	return 0;
}

运行结果如下。可以发现数据能够正常从json字符串的解析出来。包括转义字符。
在这里插入图片描述

编码json字符串

#include <iostream>
#include <fstream>
#include <cassert>
#include "json.hpp"    // 引用库文件
using namespace std;

void example_encode(){
	shared_ptr<json::Object> object = make_shared<json::Object>();
	shared_ptr<json::Array> settings = make_shared<json::Array>();
	object->set("settings", settings);
	for(int i = 0; i < 5; i++){
		shared_ptr<json::Object> setting = make_shared<json::Object>();
		shared_ptr<json::String> settingId =   make_shared<json::String>();
		shared_ptr<json::String> settingInfo =   make_shared<json::String>();
		settingId->set(make_shared<string>("S00X"));
		settingInfo->set(make_shared<string>("this is setting info..."));
		setting->set("id", settingId);
		setting->set("info", settingInfo);
		settings->add(setting);
	}

	cout << *(object->toString()) << endl;
}

int main(){
	example_encode();
	return 0;
}

运行结果如下。
在这里插入图片描述

总结

本文实现了一个简化版的json解析器,同时支持编码的功能。显然,功能是够完善的。如有意向合作完成完整版的解析器、替换更优的解析算法、实现json5解析等,可通过邮箱 cloudea@163.com 与我联系。

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值