궁금한게 많은 개발자 노트

[ C++ ] jsoncpp - parsing json file 본문

Language

[ C++ ] jsoncpp - parsing json file

궁금한게 많은 개발자 2020. 6. 15. 13:19

간단하게 우선, JSON이란 ?

JavaScript Object Notation라는 의미의 축약어로 데이터를 저장하거나 전송할 때 사용되는 경량의 DATA 교환 형식

JSON은 데이터 포맷일 뿐이며 어떠한 통신 방법도, 프로그래밍 문법도 아닌 단순히 데이터 표현 방법일 뿐이다.

Key-Value형식으로 구성되어 있으며, JSON 각 Key-Value의 Type으로는 

null, number, string, array, object, boolean을 사용

 

 

프로그램 내에서 json file을 작성하거나 json file을 읽어와 parsing한 후 사용해야 할 경우

cpp에서는 jsoncpp library를 사용하여 간편하게 작성 및 파싱을 할 수 있다.

https://github.com/open-source-parsers/jsoncpp

 

open-source-parsers/jsoncpp

A C++ library for interacting with JSON. Contribute to open-source-parsers/jsoncpp development by creating an account on GitHub.

github.com

 

cpp의 경우 주로 Makefile이나 CMakeLists.txt를 사용하여 프로젝트를 빌드할텐데, CMake의 경우 아래와 같이

FindPkgConfig의 pkg_check_modules를 사용하여 jsoncpp를 사용하기 위한 linker flag, cflag를 가져와

CMAKE_CXX_FLAGS에 setting 및 target_link_libraries를 사용하여 executable에 link를 걸어준다.

include(FindPkgConfig)
pkg_check_modules(PKGS REQUIRED jsoncpp)

foreach(flag ${PKGS_CFLAGS})
  set(EXTRA_CFLAGS "${EXTRA_CFLAGS} ${flag}")
endforeach(flag)

set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${EXTRA_CFLAGS}")

set(SRCS ...)
add_executable(${PROJECT_NAME} ${SRCS})
target_link_libraries(diagnostics_test ${PKGS_LDFLAGS})

CMakeLists.txt는 c/c++로 개발을 하다보면 계속해서 사용해야 하는데,

아직까지도 제대로 공부해보지 않아서 따로 정리를 한번 할 필요가 있을것 같다.

 

 

 

jsoncpp로 돌아와서, 아래와 같은 json format으로 파일을 생성하고, 그것을 읽어와 parsing해보려 한다.

{
   "age" : 28,
   "friends" : [
      {
         "age" : 25,
         "name" : "Kim"
      },
      {
         "age" : 30,
         "name" : "Park"
      }
   ],
   "hasCar" : true,
   "items" : [ "nootbook", "iphone", "apple-watch" ],
   "name" : "Lee"
}

 

json document를 구성하는 type으로는 Json::Value를 사용한다. 위의 {"age" : 28} 과 같은 하나의 형태가 Json::Value로 구성되며,

전체 가장 바깥쪽을 구성하는 중괄호도 결국엔 하나의 Json::Value이며, 그 내부에 다른 Object(Json::Value)들이 포함되어 있다.

 

 

Json::Value는 key-value로 구성되어 있으며,

값을 입력하는 방식으로는 인덱스 연산자 []에 key를 입력하고, value를 대입하는 방식으로 사용한다.

위에 보이는 "firends"나 "items"처럼 List형태의 경우에는 append() method를 사용, Object를 추가하는 방식으로 구현.

Object를 중첩하는 경우에는 마찬가지로 상위 Object[Key] = SubObject형태로 넣어주면 된다. 

#include <iostream>
#include <json/json.h>						// using for json format 

...

void print_json() {
	Json::Value root;					// 기본 json document
	root["name"] = "Lee";
	root["age"] = 28;
	root["hasCar"] = true;

	Json::Value items;					// list를 위한 value
	items.append("nootbook");				// append를 이용하여 추가
	items.append("iphone");
	items.append("apple-watch");
	root["items"] = items;

	Json::Value friends;
	Json::Value tom;
	tom["name"] = "Park";
	tom["age"] = 30;
	Json::Value jane;					// 각 Object에 대해서도 append로 추가
	jane["name"] = "Kim";
	jane["age"] = 25;
	friends.append(tom);
	friends.append(jane);
	root["friends"] = friends;

	Json::StyledWriter writer;				// json format으로 출력하기 위해 사용
	str = writer.write(root);
	std::cout << str << std::endl << std::endl;
}

 

 

위의 예제는 단순히 json document를 생성하고, 출력하는 방식의 예제이지만, 

실제로 많이 사용되는 방식은 json file을 읽어오거나 json format으로 file을 작성하는 상황이 많을것이다. 

이때 fstream library를 사용해 ifstream, ofstream으로 file 입출력을 한다.

#include <fstream>						// using for json file write/read
#include <json/json.h>

void parsing_json() {
    std::ifstream json_file(path, std::ifstream::binary);
    // 생성하면서 바로 path와 open mode를 parameter로 전달 가능
    
    std::ifstream json_file;
    json_file.open(path, std::ifstream::in|std::ifstream::binary);
    // 이처럼 open함수를 이용할 수 있으며, open mode는 | 연산자를 이용하여 여러 모드 가능
    
    Json::Value root
    json_file >> root;
    json_file.close();
    // open한 file에서 얻어온 내용을 root에 대입하여 Json::Value로 사용
    
    ...
    
    // 원하는 수정을 거친 후 formating json
    Json::StyledWriter writer;
    str = writer.write(root);

    // output to json file
    std::ofstream output_file("output.json");
    output_file << str;
    output_file.close();
}

위의 예제에서는 ifstream을 사용하여 path에 있는 file을 원하는 open mode를 사용하여 읽어온 후, 

원하는 작업을 거친 후 마지막에 수정내용을 "output.json"으로 출력하는 예제입니다.

 

 

json file을 읽어오는 방식에는 위처럼 ifstream에서 생성자를 호출하거나, open()을 사용하는 방법도 있지만,

jsoncpp에서 제공하는 method를 사용하는 방법도 있다. 아마 이방법이 좀 더 jsoncpp를 잘사용하는 느낌..?

#include <fstream>
#include <json/json.h>
#include <iostream>

void read_json() {
    Json::CharReaderBuilder builder;
    builder["collectComments"] = false;
  
    Json::Value value;
    Json::JSONCPP_STRING errs;
    bool ok = parseFromStream(builder, std::cin, &value, &errs);
  
    if (ok == true) {
        // access json value ...
    }
}

 

 

추가로 Json::Value를 사용하여 위에서 저장한 data들을 어떤방식으로 수정 및 접근하는지에 대해 알아보려한다.

void read_json() {
    // 수정할때와 마찬가지로 가장 바깥쪽의 Json::Value의 Key에 접근시
    // root[Key]로 value를 가져올 수 있다.
    std::cout << "name : " << root["name"].asString() << std::endl;
    std::cout << "age : " << root["age"].asInt() << std::endl;
    
    // "friends", "items"와 같은 list를 접근할 경우에는 iterator를 사용
    // .asString(), .asInt()를 사용하는 이유는, Json::Value에서 출력시 typecasting 필요
    Json::Value friend_list = root["friends"];
    for (Json::ValueIterator it = friend_list.begin(); it != friend_list.end(); it++) {
        std::cout << "friend name : " << (*it)["name"].asString() << std::endl;
        std::cout << "firend age : " << (*it)["age"].asInt() << std::endl;
    }
    
    // Vector와 비슷한 자료구조라고 생각하면 되는게, direct indexing도 가능하다.
    for (int index = 0; indext < (int)friend_list.size(); index++) {
        std::cout << "friend name : " << friend_list[index]["name"].asString() << std::endl;
        std::cout << "firend age : " << friend_list[index]["age"].asInt() << std::endl;
        
        // 아래 처럼 get() method를 사용하면, 만약 해당 Key가 없다면 2번째 인자를 return한다.  
        std::cout << "friend name : " << friend_list[index].get("name", "empty").asString() << std::endl;
        std::cout << "firend age : " << friend_list[index].get("age", -1).asInt() << std::endl;
    }   
}

다음에 json file을 parsing할 필요가 있을때 잘 사용할 수 있을것 같다.

Comments