JSON은 key, value 쌍으로 이루어진 데이터의 집합입니다.
웹에 데이터를 전송하거나, 간단한 구성 파일을 작성하는 데 주로 JSON 파일이 사용되곤 합니다.
물론 다양한 JSON 파싱 라이브러리가 있지만, 특히 RapidJson 라이브러리는 아래와 같은 장점이 있습니다.
1. 외부 라이브러리를 사용하지 않고 그냥 header 파일만 include 경로에 포함시키면 빌드할 수 있습니다
2. MIT 라이선스를 따르므로 상업적으로 사용하기 쉽습니다
RapidJson을 사용하기 전에 간단히 JSON에 대해 이해해봅시다.
먼저 JSON 문자열을 구성하는 두 가지 기본 데이터 포맷이 있습니다. (JSON object와 JSON array)
먼저 JSON object는 다음의 특징을 가집니다.
* 중괄호 {}를 이용해 데이터를 묶습니다
* 서로 다른 key - value 값을 콤마(,)를 이용해 연결합니다
* 중괄호로 묶여진 구조 전체를 객체 (JSON object)라고 부릅니다.
{
"name": "John",
"age": 30,
"city": "New York"
}
위 예제에서 "name", "age", "city"는 키이고 "John", 30, "New York"이 각 key에 해당하는 value를 의미합니다.
key를 이용해 JSON object의 value에 접근할 수 있습니다.
다음으로 JSON array는 다음의 특징을 가집니다.
* JSON object와 다르게 key, value 쌍으로 저장되지 않습니다.
* key 대신 index를 이용해 element에 접근할 수 있습니다. (대신 순서에 대한 고려가 반드시 필요합니다)
* 배열 구조를 의미하고, 배열 안에는 string, int, array, object 등을 담을 수 있습니다.
["apple", "banana", "cherry"]
위 예제에서 JSON array는 "apple", "banana", "cherry"로 구성된 문자열을 순서대로 담고 있습니다.
그럼 다음부터 RapidJson을 사용하는 c++ 예제에 대해 설명하겠습니다.
먼저, 앞서 말씀드린 것처럼 rapidjson header 파일을 include 폴더에 추가해야 합니다.
저는 thirdparty 폴더에 rapidjson header 파일을 넣고 아래처럼 CMakeLists.txt를 작성했습니다.
cmake_minimum_required(VERSION 2.4)
PROJECT(rapidjson-sample)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
include_directories(
${CMAKE_CURRENT_SOURCE_DIR}/src
${CMAKE_CURRENT_SOURCE_DIR}/thirdparty
)
FILE(GLOB_RECURSE SRC_FILES ${CMAKE_CURRENT_SOURCE_DIR}/src/*.c*)
FILE(GLOB_RECURSE HDR_FILES ${CMAKE_CURRENT_SOURCE_DIR}/src/*.h*)
add_executable(${PROJECT_NAME} ${HDR_FILES} ${SRC_FILES})
다음으로 실제 코드를 작성해서 json 파일을 파싱해보고 employees에 JSON object를 추가해보겠습니다.
{
"company": "noname",
"founding year": 2010,
"employees": [
{
"name": "elon"
},
{
"name": "tim"
},
{
"name": "mark"
}
]
}
json 파일을 파싱하고 값을 추가하는 기능을 수행하는 간단한 클래스를 작성했습니다.
rapidjson을 이용하기 위해 include를 이용해 필요한 header 파일을 가져옵니다.
#include <iostream>
#include <string>
#include <vector>
#include <fstream>
// include rapidjson, for just reading json file
#include "rapidjson/document.h"
#include "rapidjson/writer.h"
#include "rapidjson/stringbuffer.h"
#include "rapidjson/filereadstream.h"
using namespace std;
using namespace rapidjson;
class JsonParser {
public:
JsonParser(string path);
virtual ~JsonParser() = default;
inline string get_company() { return company_; }
inline int get_founding_year() { return founding_year_; }
inline vector<string> get_employees() { return employees_; }
void append_employee(string name);
private:
Document document_;
string company_ = "";
int founding_year_ = 0;
vector<string> employees_;
};
JsonParser::JsonParser(string path) {
// read json file
FILE* fp = fopen(path.c_str(), "rb");
if(fp == 0) {
std::cout << "file not exist : default param will be used" << std::endl;
return;
}
char readbuffer[65536];
FileReadStream is(fp, readbuffer, sizeof(readbuffer));
document_.ParseStream(is);
// parse company
if(document_.HasMember("company")) {
company_ = document_["company"].GetString();
}
// parse founding year
if(document_.HasMember("founding year")) {
founding_year_ = document_["founding year"].GetInt();
}
// parse employees
if(document_.HasMember("employees")) {
const Value& employees = document_["employees"];
for(SizeType i = 0; i < employees.Size(); i++) {
const Value& employee = employees[i];
if(employee.HasMember("name")) {
employees_.push_back(employee["name"].GetString());
}
}
}
}
void JsonParser::append_employee(string name) {
Value employee(kObjectType);
Value name_value(kStringType);
name_value.SetString(name.c_str(), name.size());
employee.AddMember("name", name_value, document_.GetAllocator());
// append employee
document_["employees"].PushBack(employee, document_.GetAllocator());
// update employees
employees_.clear();
const Value& employees = document_["employees"];
for(SizeType i = 0; i < employees.Size(); i++) {
const Value& employee = employees[i];
if(employee.HasMember("name")) {
employees_.push_back(employee["name"].GetString());
}
}
// write to file
StringBuffer buffer;
Writer<StringBuffer> writer(buffer);
document_.Accept(writer);
std::ofstream output_file("../etc/config.json");
if (output_file.is_open()) {
output_file << buffer.GetString();
output_file.close();
}
}
JsonParser 클래스를 만들었으니 main 함수에서 불러와 사용해보겠습니다.
먼저 JsonParser의 생성자에 json 파일이 저장된 위치를 전달하고 json 파일을 읽은 결과를 출력합니다.
다음으로 "steve"라는 이름을 employee 배열에 추가하였습니다.
int main () {
// read json file
JsonParser parser("../etc/config.json");
cout << "company : " << parser.get_company() << endl;
cout << "founding year : " << parser.get_founding_year() << endl;
cout << "employees : ";
for(auto& employee : parser.get_employees()) {
cout << employee << " ";
}
cout << endl;
// append employee
parser.append_employee("steve");
cout << "employees : ";
for(auto& employee : parser.get_employees()) {
cout << employee << " ";
}
cout << endl;
return 0;
}
빌드를 수행하고 실행하면 터미널에 아래와 같이 출력되는 것을 확인할 수 있습니다.
company : noname
founding year : 2010
employees : elon tim mark
employees : elon tim mark steve
그리고 json 파일을 다시 열어보면 정상적으로 저장된 것도 확인할 수 있습니다.
{
"company": "noname",
"employees": [
{
"name": "elon"
},
{
"name": "tim"
},
{
"name": "mark"
},
{
"name": "steve"
}
],
"founding year": 2010
}
간단한 동작을 알아보았습니다. 보다 자세한 내용은 아래 RapidJson에서 제공하는 튜토리얼에서 확인할 수 있습니다.
https://rapidjson.org/md_doc_tutorial.html
이 페이지에서 사용한 코드는 아래에 업로드 해두었습니다.
https://github.com/taemin-hwang/study-space/tree/master/cpp/99_self/rapidjson
'💻 programming > c++' 카테고리의 다른 글
[c++] TCP/IP 서버 클라이언트 설명 및 예제 코드 (소켓 프로그래밍) (0) | 2023.03.20 |
---|---|
[c++] fold expression (0) | 2021.08.05 |
[c++] logger 클래스 만들기 (0) | 2021.08.04 |
[c++] timer 클래스 만들기 (0) | 2021.08.04 |
[c++] thread vs task (thread 와 async) (0) | 2021.08.04 |
댓글