본문 바로가기
💻 programming/c++

[c++] RapidJson 사용하기, c++에서 JSON 파싱하기

by 연구원-A 2023. 9. 13.
반응형

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

 

RapidJSON: Tutorial

This tutorial introduces the basics of the Document Object Model(DOM) API. As shown in Usage at a glance, JSON can be parsed into a DOM, and then the DOM can be queried and modified easily, and finally be converted back to JSON. Value & Document Each JSON

rapidjson.org

 

이 페이지에서 사용한 코드는 아래에 업로드 해두었습니다.

https://github.com/taemin-hwang/study-space/tree/master/cpp/99_self/rapidjson

반응형

댓글