C++ JSON parser example
본 json_parser 라이브러리는 rapidjson을 활용한 wrapper이다.
이미 그 자체로 훌륭한 JSON parser 인 rapidjson은 소스코드가 아닌 헤더로만 구성되어있고 사용자의 역량에 따라 코드 표현이 천차만별이라 같은 선언, 같은 메서드, 같은 출력을 보장하기 위해(공용화) wrapper class를 만들게 되었다.
* rapidjson 홈페이지, tutorial이 무척 잘되어있다.
본인도 본인이 만든 json_parser class를 주로 사용하지만 가끔 세부적인 handling이 필요할땐 rapidjson의 기능을 직접 사용한다.
소스코드와 라이브러리 빌드, 샘플 파일은 아래 경로에 업로드 되어 있으니 참고하자.
* github 공유
https://github.com/muabow/home/tree/main/library/cpp/lib_json_parser
구성
1. include/json_parser.h (* rapidjson 은 위 github 경로 또는 rapidjson 공식 홈페이지에서 받으면 된다.)
2. src/json_parser.cpp
3. sample.cpp
소스코드
1. include/json_parser.h
#ifndef __JSON_PARSER_H__
#define __JSON_PARSER_H__
#include <stdarg.h>
#include <string>
#include <vector>
#include "rapidjson/document.h"
#include "rapidjson/pointer.h"
#include "rapidjson/writer.h"
#include "rapidjson/prettywriter.h"
#include "rapidjson/stringbuffer.h"
#include "rapidjson/error/en.h"
#include "rapidjson/error/error.h"
using namespace std;
using namespace rapidjson;
class JsonParser {
private :
bool is_debug_print = false;
void print_debug_info(const char *_format, ...);
Document t_document;
public :
JsonParser(bool _is_debug_print = false);
~JsonParser(void);
void set_debug_print(void);
string read_file(string _file_path);
bool write_file(string _file_path, string _json_string);
bool write_json(string _file_path);
bool parse(const string _data);
string to_string(void);
string get_s(string _node_path); // deprecated
string select(string _node_path);
bool update(string _node_path, int _value);
bool update(string _node_path, double _value);
bool update(string _node_path, string _value);
bool add(string _node_path, int _value);
bool add(string _node_path, double _value);
bool add(string _node_path, string _value);
bool remove(string _node_path);
vector<string> split(string _string, string _delimiter);
string replace_all(string &_str, const string& _from, const string& _to);
};
#endif
2. src/json_parser.cpp
#include <stdio.h>
#include <sys/stat.h>
#include <iostream>
#include <sstream>
#include <fstream>
#include "json_parser.h"
JsonParser::JsonParser(bool _is_debug_print) {
if( this->is_debug_print ) {
this->set_debug_print();
}
this->t_document = NULL;
this->print_debug_info("JsonParser() create instance\n");
return;
}
JsonParser::~JsonParser(void) {
this->print_debug_info("JsonParser() instance destructed\n");
return ;
}
void JsonParser::print_debug_info(const char *_format, ...) {
if( !this->is_debug_print ) return ;
fprintf(stdout, "JsonParser::");
va_list arg;
va_start(arg, _format);
vprintf(_format, arg);
va_end(arg);
return ;
}
void JsonParser::set_debug_print(void) {
this->is_debug_print = true;
this->print_debug_info("set_debug_print() is set on\n");
return ;
}
string JsonParser::read_file(string _file_path) {
struct stat buffer;
if( stat(_file_path.c_str(), &buffer) != 0 ) {
this->print_debug_info("read_file() not found file path [%s]\n", _file_path.c_str());
return "";
}
ostringstream read_data;
ifstream fp(_file_path);
read_data << fp.rdbuf();
fp.close();
this->print_debug_info("read_file() read from [%s] - [%s]\n", _file_path.c_str(), read_data.str().c_str());
return read_data.str();
}
bool JsonParser::write_file(string _file_path, string _json_string) {
ofstream out(_file_path);
if( out.is_open() ) {
out << _json_string;
out.close();
this->print_debug_info("write_file() write to [%s] - [%s]\n", _file_path.c_str(), _json_string.c_str());
return true;
} else {
this->print_debug_info("write_file() open failed [%s]\n", _file_path.c_str());
return false;
}
}
bool JsonParser::parse(const string _json_string) {
this->t_document = NULL;
if( _json_string.compare("") == 0 ) {
this->print_debug_info("parse() JSON parse error : [%02d] %s (offset: %u)\n",
kParseErrorDocumentEmpty, GetParseError_En(kParseErrorDocumentEmpty), 0);
return false;
}
ParseResult is_ok = this->t_document.Parse(_json_string.c_str());
if( !is_ok ) {
this->print_debug_info("parse() JSON parse error : [%02d] %s (offset: %u)\n",
is_ok.Code(), GetParseError_En(is_ok.Code()), is_ok.Offset());
this->t_document = NULL;
return false;
}
this->print_debug_info("parse() convert to json object [%s]\n", _json_string.c_str());
return true;
}
string JsonParser::to_string(void) {
StringBuffer buffer;
buffer.Clear();
Writer<StringBuffer> writer(buffer);
this->t_document.Accept(writer);
string result = buffer.GetString();
this->print_debug_info("to_string() convert to string [%s]\n", result.c_str());
return result;
}
// deprecated
string JsonParser::get_s(string _node_path) {
int cnt_vector_name;
bool is_last_vector = false;
Value obj_value;
StringBuffer buffer;
buffer.Clear();
Writer<StringBuffer> writer(buffer);
vector<string> vector_json_name = this->split(_node_path, "/");
cnt_vector_name = vector_json_name.size();
Document::AllocatorType& allocator = this->t_document.GetAllocator();
obj_value.CopyFrom(this->t_document, allocator);
while( !vector_json_name.empty() ) {
if( --cnt_vector_name == 0 ) {
is_last_vector = true;
}
for( Value::ConstMemberIterator itr = obj_value.MemberBegin(); itr != obj_value.MemberEnd() ; ++itr ) {
if( strcmp(itr->name.GetString(), vector_json_name.front().c_str()) == 0 ) {
switch( itr->value.GetType() ) {
case 3 : // object
if( !is_last_vector ) {
obj_value = obj_value[itr->name.GetString()].GetObject();
break;
}
obj_value[itr->name.GetString()].Accept(writer);
return buffer.GetString();
case 4 : // array
if( is_last_vector && obj_value[itr->name.GetString()].IsArray() ) {
// array
obj_value[itr->name.GetString()].Accept(writer);
return buffer.GetString();
} else {
vector_json_name.erase(vector_json_name.begin());
int idx = atoi(vector_json_name.front().c_str());
if( idx >= (int)obj_value[itr->name.GetString()].Size() ) {
return "";
}
if( --cnt_vector_name == 0 ) {
is_last_vector = true;
}
if( is_last_vector ) {
if( obj_value[itr->name.GetString()][idx].IsObject() ) {
// array
obj_value = obj_value[itr->name.GetString()][idx].GetObject();
obj_value.Accept(writer);
} else {
// array/index
obj_value[itr->name.GetString()][idx].Accept(writer);
}
return buffer.GetString();
} else {
// array/index/value
obj_value = obj_value[itr->name.GetString()][idx].GetObject();
}
}
break;
case 5 : // string
if( obj_value[itr->name.GetString()].IsString() ) {
return obj_value[itr->name.GetString()].GetString();
}
break;
case 0 : // null
case 1 : // false
case 2 : // true
case 6 : // number
obj_value[itr->name.GetString()].Accept(writer);
return buffer.GetString();
break;
}
break;
}
}
vector_json_name.erase(vector_json_name.begin());
}
return "";
}
string JsonParser::select(string _node_path) {
string str_value = "";
Value *value;
if( (value = Pointer(_node_path.c_str()).Get(this->t_document)) == 0 ) {
return str_value;
}
StringBuffer buffer;
Writer<StringBuffer> writer(buffer);
value->Accept(writer);
str_value = buffer.GetString();
// trim begin/end double quotes
if ( str_value.front() == '"' ) {
str_value.erase(str_value.begin());
}
if( str_value.back() == '"' ) {
str_value.pop_back();
}
return str_value;
}
bool JsonParser::update(string _node_path, int _value) {
Value *value;
if( (value = Pointer(_node_path.c_str()).Get(this->t_document)) == 0 ) {
return false;
}
Pointer(_node_path.c_str()).Set(this->t_document, _value);
return true;
}
bool JsonParser::update(string _node_path, double _value) {
Value *value;
if( (value = Pointer(_node_path.c_str()).Get(this->t_document)) == 0 ) {
return false;
}
Pointer(_node_path.c_str()).Set(this->t_document, _value);
return true;
}
bool JsonParser::update(string _node_path, string _value) {
Value *value;
if( (value = Pointer(_node_path.c_str()).Get(this->t_document)) == 0 ) {
return false;
}
Pointer(_node_path.c_str()).Set(this->t_document, _value.c_str());
return true;
}
bool JsonParser::add(string _node_path, int _value) {
Value *value;
if( (value = Pointer(_node_path.c_str()).Get(this->t_document)) == 0 ) {
Pointer(_node_path.c_str()).Set(this->t_document, _value);
return true;
}
return false;
}
bool JsonParser::add(string _node_path, double _value) {
Value *value;
if( (value = Pointer(_node_path.c_str()).Get(this->t_document)) == 0 ) {
Pointer(_node_path.c_str()).Set(this->t_document, _value);
return true;
}
return false;
}
bool JsonParser::add(string _node_path, string _value) {
Value *value;
if( (value = Pointer(_node_path.c_str()).Get(this->t_document)) == 0 ) {
Pointer(_node_path.c_str()).Set(this->t_document, _value.c_str());
return true;
}
return false;
}
bool JsonParser::remove(string _node_path) {
Value *value;
if( (value = Pointer(_node_path.c_str()).Get(this->t_document)) == 0 ) {
return false;
}
Pointer(_node_path.c_str()).Erase(this->t_document);
return true;
}
bool JsonParser::write_json(string _file_path) {
StringBuffer buffer;
PrettyWriter<StringBuffer> writer(buffer);
if( this->t_document.Accept(writer) == false ){
this->print_debug_info("write_json() open failed [%s]\n", _file_path.c_str());
return false;
}
string temp = buffer.GetString();
ofstream out(_file_path.c_str(),std::ofstream::trunc);
out << temp;
this->print_debug_info("write_json() write to [%s] - [%s]\n", _file_path.c_str(), temp.c_str());
return true;
}
vector<string> JsonParser::split(string _string, string _delimiter) {
size_t pos_start = 0, pos_end, delim_len = _delimiter.length();
string token;
vector<string> res;
while ((pos_end = _string.find (_delimiter, pos_start)) != std::string::npos) {
token = _string.substr (pos_start, pos_end - pos_start);
pos_start = pos_end + delim_len;
res.push_back(token);
}
res.push_back(_string.substr(pos_start));
return res;
}
string JsonParser::replace_all(string &_str, const string& _from, const string& _to) {
size_t start_pos = 0;
while( (start_pos = _str.find(_from, start_pos)) != std::string::npos ) {
_str.replace(start_pos, _from.length(), _to);
start_pos += _to.length();
}
return _str;
}
3. sample.cpp
#include <stdio.h>
#include <unistd.h>
#include <vector>
#include <iostream>
#include "json_parser.h"
using namespace std;
/* test.json
{
"number": { "int": 1, "double": 1.1 },
"string": "hello world",
"bool": true,
"array": [0, 2, 4, 8, 10],
"null": null,
"json_array": [{ "int": 2, "double": 2.2 }, { "int": 3, "double": 3.3 }, { "int": 4, "double": 4.4 }]
}
*/
int main(void) {
// json parser instance 생성
// instance 생성 시 debug print set 가능
// args([bool _is_debug_print])
JsonParser json_parser;
// debug print 출력
json_parser.set_debug_print();
// JSON 파일로 부터 JSON string 읽어오기
// args(<string _file_path>)
// return - success: string, fail: ""
string str_json = json_parser.read_file("./test.json");
// JSON string 을 JSON document object로 변환
// args(<string _json_string>)
// return - true: success, false: failed
json_parser.parse(str_json);
// JSON object로 부터 JSON node value 호출
// node value 는 단일/하위 path 까지 입력 가능
// args(<string _node_path>)
// return - success: string, fail: ""
cout << "\n# Parse JSON data" << endl;
cout << "/number" << "\t\t\t: " << json_parser.select("/number") << endl;
cout << "/number/int" << "\t\t: " << json_parser.select("/number/int") << endl;
cout << "/number/double" << "\t\t: " << json_parser.select("/number/double") << endl;
cout << "/string" << "\t\t\t: " << json_parser.select("/string") << endl;
cout << "/bool" << "\t\t\t: " << json_parser.select("/bool") << endl;
cout << "/array" << "\t\t\t: " << json_parser.select("/array") << endl;
cout << "/array/0" << "\t\t: " << json_parser.select("/array/0") << endl;
cout << "/array/1" << "\t\t: " << json_parser.select("/array/1") << endl;
cout << "/array/2" << "\t\t: " << json_parser.select("/array/2") << endl;
cout << "/array/3" << "\t\t: " << json_parser.select("/array/3") << endl;
cout << "/array/4" << "\t\t: " << json_parser.select("/array/4") << endl;
cout << "/null" << "\t\t\t: " << json_parser.select("/null") << endl;
cout << "/json_array" << "\t\t: " << json_parser.select("/json_array") << endl;
cout << "/json_array/0" << "\t\t: " << json_parser.select("/json_array/0") << endl;
cout << "/json_array/0/int" << "\t: " << json_parser.select("/json_array/0/int") << endl;
cout << "/json_array/0/double" << "\t: " << json_parser.select("/json_array/0/double") << endl;
cout << "/json_array/1" << "\t\t: " << json_parser.select("/json_array/1") << endl;
cout << "/json_array/1/int" << "\t: " << json_parser.select("/json_array/1/int") << endl;
cout << "/json_array/1/double" << "\t: " << json_parser.select("/json_array/1/double") << endl;
cout << "/json_array/2" << "\t\t: " << json_parser.select("/json_array/2") << endl;
cout << "/json_array/2/int" << "\t: " << json_parser.select("/json_array/2/int") << endl;
cout << "/json_array/2/double" << "\t: " << json_parser.select("/json_array/2/double") << endl;
cout << "/json_array/2/none" << "\t: " << json_parser.select("/json_array/2/none") << endl;
printf("\n# casting data type\n");
// parse 된 모든 결과물은 string이기 때문에 데이터 타입에 맞게 casting 하여 사용
int num_int_value = stoi(json_parser.select("/number/int"));
double num_double_value = stod(json_parser.select("/number/double"));
printf(" - int[%d] double[%lf] \n", num_int_value, num_double_value);
printf("\n# split: vector<string> array\n");
// string에서 지정한 delimiter로 분할하여 vector<string> array 로 생성
// return - vector array
vector<string> v_arr_values = json_parser.split("1,3,5,7,9", ",");
for( int idx = 0 ; idx < v_arr_values.size() ; idx++ ) {
printf(" - v_arr_values[%d] = %d\n", idx, stoi(v_arr_values.at(idx)));
}
printf("\n# JSON object to string\n");
// instance에 저장된 JSON object를 string으로 변환
// return - success: string, fail: ""
string to_string = json_parser.to_string();
printf("\n# replace string\n");
// string에서 해당하는 문자열을 모두 치환
// args(<string _string, string _from, string _to>)
// return - true: success, false: failed
json_parser.replace_all(to_string, "hello world", "new world");
printf(" -[%s]\n", to_string.c_str());
printf("\n# write file\n");
// json string을 file에 저장, pretty-printing 적용 안됨
// args(<string _file_path, string _json_string>)
// return - true: success, false: failed
json_parser.write_file("test2.json", to_string);
printf("\n# update json data\n");
json_parser.update("/number/int", 5);
json_parser.update("/number/test", 5);
to_string = json_parser.to_string();
printf(" -[%s]\n", to_string.c_str());
printf("\n# add json data\n");
json_parser.add("/number/int", 6);
json_parser.add("/number/test", 6);
to_string = json_parser.to_string();
printf(" -[%s]\n", to_string.c_str());
printf("\n# remove json data\n");
json_parser.remove("/number/test");
to_string = json_parser.to_string();
printf(" -[%s]\n", to_string.c_str());
printf("\n# write json data with pretty printing\n");
json_parser.write_json("./test3.json");
return 0;
}
* 예제파일 : test.json
{
"number": { "int": -1, "double": 1.1 },
"string": "hello world",
"bool": true,
"array": [0, 2, 4, 8, 10],
"null": null,
"json_array": [{ "int": 2, "double": 2.2 }, { "int": 3, "double": 3.3 }, { "int": 4, "double": 4.4 }]
}
컴파일 및 실행 결과
- 라이브러리 빌드, 샘플의 컴파일, LD_LIBRARY_PATH 환경설정 그리고 실행의 과정과 결과이다.
자세한 설명은 위 소스코드 내에 모두 들어있으니 참고 바란다.
muabow@muabow-WorkSpace:~/github/library/cpp/lib_json_parser$ make clean ; make
rm -Rf ./src/json_parser.o libjson_parser.so
g++ -fPIC -I./include -c src/json_parser.cpp -o src/json_parser.o --std=c++11
g++ -shared -o libjson_parser.so ./src/json_parser.o
muabow@muabow-WorkSpace:~/github/library/cpp/lib_json_parser$ g++ -o sample sample.cpp -I./include -L./ -ljson_parser -std=c++11
muabow@muabow-WorkSpace:~/github/library/cpp/lib_json_parser$ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$(pwd)
muabow@muabow-WorkSpace:~/github/library/cpp/lib_json_parser$
muabow@muabow-WorkSpace:~/github/library/cpp/lib_json_parser$ ./sample
JsonParser::set_debug_print() is set on
JsonParser::read_file() read from [./test.json] - [{
"number": { "int": -1, "double": 1.1 },
"string": "hello world",
"bool": true,
"array": [0, 2, 4, 8, 10],
"null": null,
"json_array": [{ "int": 2, "double": 2.2 }, { "int": 3, "double": 3.3 }, { "int": 4, "double": 4.4 }]
}
]
JsonParser::parse() convert to json object [{
"number": { "int": -1, "double": 1.1 },
"string": "hello world",
"bool": true,
"array": [0, 2, 4, 8, 10],
"null": null,
"json_array": [{ "int": 2, "double": 2.2 }, { "int": 3, "double": 3.3 }, { "int": 4, "double": 4.4 }]
}
]
# Parse JSON data
/number : {"int":-1,"double":1.1}
/number/int : -1
/number/double : 1.1
/string : hello world
/bool : true
/array : [0,2,4,8,10]
/array/0 : 0
/array/1 : 2
/array/2 : 4
/array/3 : 8
/array/4 : 10
/null : null
/json_array : [{"int":2,"double":2.2},{"int":3,"double":3.3},{"int":4,"double":4.4}]
/json_array/0 : {"int":2,"double":2.2}
/json_array/0/int : 2
/json_array/0/double : 2.2
/json_array/1 : {"int":3,"double":3.3}
/json_array/1/int : 3
/json_array/1/double : 3.3
/json_array/2 : {"int":4,"double":4.4}
/json_array/2/int : 4
/json_array/2/double : 4.4
/json_array/2/none :
# casting data type
- int[-1] double[1.100000]
# split: vector<string> array
- v_arr_values[0] = 1
- v_arr_values[1] = 3
- v_arr_values[2] = 5
- v_arr_values[3] = 7
- v_arr_values[4] = 9
# JSON object to string
JsonParser::to_string() convert to string [{"number":{"int":-1,"double":1.1},"string":"hello world","bool":true,"array":[0,2,4,8,10],"null":null,"json_array":[{"int":2,"double":2.2},{"int":3,"double":3.3},{"int":4,"double":4.4}]}]
# replace string
-[{"number":{"int":-1,"double":1.1},"string":"new world","bool":true,"array":[0,2,4,8,10],"null":null,"json_array":[{"int":2,"double":2.2},{"int":3,"double":3.3},{"int":4,"double":4.4}]}]
# write file
JsonParser::write_file() write to [test2.json] - [{"number":{"int":-1,"double":1.1},"string":"new world","bool":true,"array":[0,2,4,8,10],"null":null,"json_array":[{"int":2,"double":2.2},{"int":3,"double":3.3},{"int":4,"double":4.4}]}]
# update json data
JsonParser::to_string() convert to string [{"number":{"int":5,"double":1.1},"string":"hello world","bool":true,"array":[0,2,4,8,10],"null":null,"json_array":[{"int":2,"double":2.2},{"int":3,"double":3.3},{"int":4,"double":4.4}]}]
-[{"number":{"int":5,"double":1.1},"string":"hello world","bool":true,"array":[0,2,4,8,10],"null":null,"json_array":[{"int":2,"double":2.2},{"int":3,"double":3.3},{"int":4,"double":4.4}]}]
# add json data
JsonParser::to_string() convert to string [{"number":{"int":5,"double":1.1,"test":6},"string":"hello world","bool":true,"array":[0,2,4,8,10],"null":null,"json_array":[{"int":2,"double":2.2},{"int":3,"double":3.3},{"int":4,"double":4.4}]}]
-[{"number":{"int":5,"double":1.1,"test":6},"string":"hello world","bool":true,"array":[0,2,4,8,10],"null":null,"json_array":[{"int":2,"double":2.2},{"int":3,"double":3.3},{"int":4,"double":4.4}]}]
# remove json data
JsonParser::to_string() convert to string [{"number":{"int":5,"double":1.1},"string":"hello world","bool":true,"array":[0,2,4,8,10],"null":null,"json_array":[{"int":2,"double":2.2},{"int":3,"double":3.3},{"int":4,"double":4.4}]}]
-[{"number":{"int":5,"double":1.1},"string":"hello world","bool":true,"array":[0,2,4,8,10],"null":null,"json_array":[{"int":2,"double":2.2},{"int":3,"double":3.3},{"int":4,"double":4.4}]}]
# write json data with pretty printing
JsonParser::write_json() write to [./test3.json] - [{
"number": {
"int": 5,
"double": 1.1
},
"string": "hello world",
"bool": true,
"array": [
0,
2,
4,
8,
10
],
"null": null,
"json_array": [
{
"int": 2,
"double": 2.2
},
{
"int": 3,
"double": 3.3
},
{
"int": 4,
"double": 4.4
}
]
}]
JsonParser::JsonParser() instance destructed
개인적으로 제일 자주 사용하는 라이브러리이다. 아무래도 JSON format을 사용 하는 곳이 많다보니..
C/C++에서는 string parse가 무척 귀찮고 어려운 경우가 많아서 그렇지 string이든 JSON이든 패턴만 잘 잡아두면 두고두고 유용하다.
끝.
'IT > programming' 카테고리의 다른 글
[C/C++] C++ 프로그램/함수 실행 시간 측정, ms 밀리초 기준 (0) | 2022.01.18 |
---|---|
[JAVA] 클래스와 메서드 설명 (0) | 2022.01.18 |
Rest API 요청 언어별 정리, CURL/PHP/PYTHON/C/C++/QT/JAVA/Node.js (6) | 2022.01.17 |
[C/C++] C언어 MAC 주소 읽기 구현, get mac address (0) | 2022.01.16 |
[C/C++] select 를 활용한 non-block I/O, FD_SET, FD_ISSET (0) | 2022.01.14 |
댓글