매우 간단한 json 래퍼 클래스

json을 써 볼까 싶은 생각에 찾아 보니 여러가지가 있지만 rapidjson이 괜찮은 듯해서 선택했다. 그런데 문제는 항상 반복하게 될 파일 열기. 그래서 이 부분만 간단히 처리할 클래스를 만들어 보았다. 즉 큰 기대는 금물.

입출력할 파일에 관한 정보를 굳이 멤버로 두지 않은 이유는 다음과 같다. 파일을 열고 읽는 멤버 함수를 만들고 보니 파일에 쓰는 멤버 함수도 있으면 좋을 것 같아 추가했다. 생성자에서 파일 이름을 받도록 하고 각 읽기/쓰기 멤버 함수에서 그대로 쓰면 괜찮겠는데, 생각해 보니 정보를 읽은 파일이 아니라 다른 파일에 기록할 수 있으면 더 좋을 것 같다. 결국 읽기/쓰기 멤버 함수도 파일을 받는 것과 안 받는 것 두 가지를 만들 수 있겠다. 즉 생성자에서 파일을 받으면 read()/write()를 사용해 읽고 쓰면 되고, 생성자에서 받지 않으면 read(file)/write(file)를 사용하면 된다.

그런데 생성자에서 파일을 받고 read()로 읽은 후 write(file)로 기록하거나 그 반대 상황, 또는 생성자와 멤버를 의도와 달리 섞어 쓴다면 클래스에 멤버로 있는 파일 정보는 어떻게 처리하는 게 좋을까. 그래서 내린 결론은 읽고 쓸 때 직접 지정하기로 했다. 헷갈릴 염려도 없고 가장 직관적이지 않을까.


#ifndef JSON_H_
#define JSON_H_


#include "tstring.h"
#include "thirdparty/rapidjson/document.h"


class Json {
public:
  using Document = rapidjson::Document;
  using SizeType = rapidjson::SizeType;
  using Value = rapidjson::Value;

  Json() {}
  ~Json() {}

  /**
   * Read a json file.
   * Read from the file specified in the parameter.
   * @param file a json file included full path
   * @return If succeeded return true, otherwise false
   */
  bool read(std::tstring const& file);

  /**
   * Write a json file.
   * Write to the file specified in the parameter.
   * @param file a json file included full path
   * @return If succeeded return true, otherwise false
   */
  bool write(std::tstring const& file);

  Document& document() {return document_;}

private:
  FILE* fp_ = nullptr;
  rapidjson::Document document_;
};

#endif


#include "json.h"

#include "thirdparty/rapidjson/filereadstream.h"
#include "thirdparty/rapidjson/filewritestream.h"
#include "thirdparty/rapidjson/prettywriter.h"


using std::tstring;
using rapidjson::Document;
using rapidjson::FileReadStream;
using rapidjson::FileWriteStream;
using rapidjson::PrettyWriter;


const int kReadBufferSize = 65536;


bool Json::read(std::tstring const& file) {
  errno_t err = ::_tfopen_s(&fp_, file.c_str(), _T("rb"));
  if (err) {
    return false;
  }

  char readBuffer[kReadBufferSize];
  FileReadStream readStream(fp_, readBuffer, _countof(readBuffer));
  bool result = !document_.ParseStream(readStream).HasParseError();

  ::fclose(fp_);

  return result;
}

bool Json::write(std::tstring const& file) {
  errno_t err = ::_tfopen_s(&fp_, file.c_str(), _T("wb"));
  if (err) {
    return false;
  }

  char writeBuffer[kReadBufferSize];
  FileWriteStream writeStream(fp_, writeBuffer, _countof(writeBuffer));
  PrettyWriter<FileWriteStream> writer(writeStream);
  document_.Accept(writer);

  ::fclose(fp_);
  
  return true;
}


#include <iostream>

#include "json.h"


int _tmain(int argc, _TCHAR* argv[])
{
  std::tstring fileName = _T("sample.json");
  Json json;
  json.read(fileName.c_str());
  Json::Document& doc = json.document();

  Json::Value& gem = doc["a"];
  for (Json::SizeType i = 0; gem.Size() != i; ++i) {
    std::cout << gem[i]["id"].GetInt() << std::endl;
    std::cout << gem[i]["name"].GetString() << std::endl;
    std::cout << gem[i]["max"].GetDouble() << std::endl;

    gem[i]["name"] = "mod";
  }

  json.write(fileName.c_str());

  return 0;
}


{
    "a": [
        {
            "id": 1,
            "name": "abc",
            "max": 324.22
        },
        {
            "id": 2,
            "name": "bcd",
            "max": 43242
        }
    ],
    "b": [
        {
            "id": 1,
            "name": "bbc"
        },
        {
            "id": 2,
            "name": "bac"
        }
    ]
}

rapidjson 관련 파일 경로를 적절히 바꾸고 컴파일하면 std::tstring 때문에 오류가 발생할 텐데 간단히 std::string으로 바꾸면 된다. 실행하면 파일을 읽어 화면 출력 후 배열 a에 대한 각 요소 이름을 mod로 바꿔 다시 저장한다.

그런데 딱히 프로그램 외부에서 정보를 계속 바꿔 가며 사용할 일이 없다 보니 이걸 써야 할까 말아야 할까 아직도 고민 중인 게 함정.

You may also like...