2020년 1월 30일 목요일

RapidJson Traverse Example for MFC

이번에 소개 할것은 JsonCPP 라이브러리만 쓰다 속도의 이슈가 있어서 다른 Json 라이브러리를 찾다가

알게된 "RapidJson" 라이브러리 입니다.

라이브러리는 헤더파일로 구성되어 별도의 라이브러리가 없습니다.
https://rapidjson.org/index.html

위 사이트 가셔서 라이브러리 소스는 다운로드 하시면 됩니다.

공식적인 성능면에서도 우수한 속도를 냅니다.

제가 가지고 있는 json 파일이 20k 정도 하여서 파싱만 처리를 속도를 체크 해봤는데 속도가 5분의 1로 줄었습니다.

성능면에서는 우수합니다.

단 개발자로서 코드를 다루는 입장에서는 접근하는 방법이 좀 까다로운점 말고는 괜찮아 이 라이브러리를 도입하게 되었습니다.


들어가기 전에 개발된 샘플은 Visual Studio 2017 버젼에서 제작 된겁니다.

샘플 구성은 TestJson.json 파일을 읽어서 파싱하고 안에 내용을 순회 하며 결과를 출력하는 샘플입니다.



CString sFilePath;
 sFilePath = "D:\\TestJson.json";

 CFile file;
 Document rapidJsonDoc;

 if (file.Open(sFilePath, CFile::modeRead))
 {
  LPBYTE lpTmp = new BYTE[file.GetLength()];
  memset(lpTmp, 0x00, file.GetLength());

  unsigned long nReadSize = file.Read(lpTmp, file.GetLength());
  if (nReadSize > 0)
  {
   CString sReadCon((LPSTR)lpTmp, file.GetLength());
   OutputDebugString(_T("Start RapidJson Parse\n"));

   rapidJsonDoc.Parse(sReadCon);
   if (rapidJsonDoc.HasParseError())
   {
    OutputDebugString(_T("Read Failed\n"));
   }
   else
   {
    OutputDebugString(_T("Read Success\n"));

    for (rapidjson::Value::ConstMemberIterator itr = rapidJsonDoc.MemberBegin(); itr != rapidJsonDoc.MemberEnd(); ++itr)
    {
     CString sKey((LPSTR)itr->name.GetString(), strlen((LPSTR)itr->name.GetString()));
     doTraversRapidJson(rapidJsonDoc[itr->name.GetString()], sKey);
    }
   }

   OutputDebugString(_T("End RapidJson Parse\n"));
  }
 }



void doTraversRapidJson(const rapidjson::Value& oRoot, CString sKey)
{
 CString sDebugStr;
 switch (oRoot.GetType())
 {
 case kNullType:
 {
  sDebugStr.Format(_T("[%s]=null\n"), (LPCTSTR)sKey);
  OutputDebugString(sDebugStr);
 }break;
 case kFalseType:
 case kTrueType:
 {
  sDebugStr.Format(_T("[%s]=%s\n"), (LPCTSTR)sKey, oRoot.GetBool() ? _T("true") : _T("false"));
  OutputDebugString(sDebugStr);
 }break;
 case kStringType:
 {
  CString sValue;
  sValue = oRoot.GetString();
  sDebugStr.Format(_T("[%s]=%s\n"), (LPCTSTR)sKey, (LPCTSTR)sValue);
  OutputDebugString(sDebugStr);
 }break;
 case kNumberType:
 {
  if (oRoot.IsInt())
  {
   sDebugStr.Format(_T("[%s]=%d\n"), (LPCTSTR)sKey, oRoot.GetInt());
   OutputDebugString(sDebugStr);
  }
  else if (oRoot.IsUint())
  {
   sDebugStr.Format(_T("[%s]=%d\n"), (LPCTSTR)sKey, oRoot.GetUint());
   OutputDebugString(sDebugStr);
  }
  else if (oRoot.IsDouble())
  {
   sDebugStr.Format(_T("[%s]=%f\n"), (LPCTSTR)sKey, oRoot.GetDouble());
   OutputDebugString(sDebugStr);
  }
  else if (oRoot.IsInt64())
  {
  }
  else if (oRoot.IsUint64())
  {
  }
 }break;
 case kObjectType:
 {
  sDebugStr.Format(_T("[%s]=Object\n"), (LPCTSTR)sKey);
  OutputDebugString(sDebugStr);
  CString sPath;
  for (rapidjson::Value::ConstMemberIterator itr = oRoot.MemberBegin(); itr != oRoot.MemberEnd(); ++itr)
  {
   CString sName;
   sName = itr->name.GetString();
   if (sKey != _T(""))
   {
    sPath.Format(_T("%s/%s"), (LPCTSTR)sKey, (LPCTSTR)sName);
   }
   else
   {
    sPath = sName;
   }

   doTraversRapidJson(oRoot[itr->name.GetString()], sPath);
  }
 }break;
 case kArrayType:
 {
  sDebugStr.Format(_T("[%s]=Array\n"), (LPCTSTR)sKey);
  OutputDebugString(sDebugStr);
  unsigned int nArrCnt = oRoot.Size();
  CString sPath;
  for (unsigned int index = 0; index < nArrCnt; ++index)
  {
   sDebugStr.Format(_T("[%s][%d]"), (LPCTSTR)sKey, index);
   if (sKey != _T(""))
   {
    sPath.Format(_T("%s/%d"), (LPCTSTR)sKey, index);
   }
   else
   {
    sPath.Format(_T("%d"), index);
   }

   doTraversRapidJson(oRoot[index], sPath);
  }
 }break;
 }
}

보면 아시겠지만 doTraversRapidJson 함수는 재귀 함수 입니다.

사용을 하시기 위해서는 아래의 것을 선행 하셔야 합니다.

rapidjson\document.h

rapidjson\reader.h

위 두파일을 인쿠르드 하시고


​using namespace rapidjson;

을 선언합니다.


소스에 .GetString() 해서 값을 얻어 오는 부분이 있는데 이값을 UniCode에서 MutiByteCode 로 변환을 하셔야 합니다.

변환 하는 방법은 제 블로그에 내용이 있으니 참조 하시면 됩니다.
https://blog.naver.com/berdo_seok/220931299353

사용법은 여기까지 입니다. 그럼 ^^

JsonCPP Traverse Example for MFC

이번에 소개 할것은 이전부터 써오던 라이브러리인데 JavaScript 가 주여 프로그래밍 언어로 상승 하면서 데이터 저장 형태로 Json 형태를 쓰게 되었습니다.

이것을 파싱하고 객체를 관리 하는 라이브러리중 하나로 JsonCPP 를 사용하여 안에 데이터를 순회 하는 샘플을 만들어 볼겁니다.

들어가기 전에 개발된 샘플은 Visual Studio 2017 버젼에서 제작 된겁니다.

JsonCPP는 별도 라이브러리를 생성할수 있게 소스를 제공합니다.

제가 사용한 라이브러리는 예전에 구한건데 어디서 구한 소스 인지는 기억이 나질 않네요

Visual Studio 2017 에서 쓰기 위해서 2017 맞춰서 별도로 빌드 했습니다. 빌드한 라이브러리는 올려 둘테니 사용하시면 됩니다.


샘플 구성은 TestJson.json 파일을 읽어서 파싱하고 안에 내용을 순회 하며 결과를 출력하는 샘플입니다.


CString sFilePath;
sFilePath = "D:\\TestJson.json";

CFile file;
if (file.Open(sFilePath, CFile::modeRead))
{
    LPBYTE lpTmp = new BYTE[file.GetLength()];
    memset(lpTmp, 0x00, file.GetLength());

    unsigned long nReadSize = file.Read(lpTmp, file.GetLength());
    if (nReadSize > 0)
    {
        CString sReadCon((LPSTR)lpTmp, file.GetLength());
        OutputDebugString(_T("Start JsonCPP Parse\n"));
        Json::Value jJsonData;
        Json::Reader reader;
        std::string sReadBuff = sReadCon;
        if (reader.parse(sReadBuff, jJsonData))
        {
            OutputDebugString(_T("Read Success\n"));
            doTraversJsonCPP(jJsonData, _T(""));
        }
        else
        {
            OutputDebugString(_T("Read Failed\n"));
        }
        OutputDebugString(_T("End JsonCPP Parse\n"));
    }
}



void doTraversJsonCPP(Json::Value oRoot, CString sKey)
{
 CString sDebugStr;
 switch (oRoot.type())
 {
 case Json::nullValue:
 {
  sDebugStr.Format(_T("[%s]=null\n"), (LPCTSTR)sKey);
  OutputDebugString(sDebugStr);
 }break;
 case Json::intValue:
 case Json::uintValue:
 {
  sDebugStr.Format(_T("[%s]=%d\n"), (LPCTSTR)sKey, oRoot.asUInt());
  OutputDebugString(sDebugStr);
 }break;
 case Json::realValue:
 {
  sDebugStr.Format(_T("[%s]=%f\n"), (LPCTSTR)sKey, oRoot.asDouble());
  OutputDebugString(sDebugStr);
 }break;
 case Json::booleanValue:
 {
  sDebugStr.Format(_T("[%s]=%s\n"), (LPCTSTR)sKey, oRoot.asBool() ? _T("true") : _T("false"));
  OutputDebugString(sDebugStr);
 }break;
 case Json::stringValue:
 {
  CString sValue;
  sValue = oRoot.asCString();
  sDebugStr.Format(_T("[%s]=%s\n"), (LPCTSTR)sKey, (LPCTSTR)sValue);
  OutputDebugString(sDebugStr);
 }break;
 case Json::objectValue:
 {
  sDebugStr.Format(_T("[%s]=Object\n"), (LPCTSTR)sKey);
  OutputDebugString(sDebugStr);
  Json::Value::Members members(oRoot.getMemberNames());
  CString sPath;
  for (auto name : members)
  {
   CString sName;
   sName = name.c_str();
   if (sKey != _T(""))
   {
    sPath.Format(_T("%s.%s"), (LPCTSTR)sKey, (LPCTSTR)sName);
   }
   else
   {
    sPath = sName;
   }

   doTraversJsonCPP(oRoot[name], sPath);
  }
 }break;
 case Json::arrayValue:
 {
  sDebugStr.Format(_T("[%s]=Array\n"), (LPCTSTR)sKey);
  OutputDebugString(sDebugStr);
  unsigned int nArrCnt = oRoot.size();
  CString sPath;
  for (unsigned int index = 0; index < nArrCnt; ++index)
  {
   sDebugStr.Format(_T("[%s][%d]\n"), (LPCTSTR)sKey, index);
   OutputDebugString(sDebugStr);
   if (sKey != _T(""))
   {
    sPath.Format(_T("%s[%d]"), (LPCTSTR)sKey, index);
   }
   else
   {
    sPath.Format(_T("[%d]"), index);
   }

   doTraversJsonCPP(oRoot[index], sPath);
  }
 }break;
 }
}

보면 아시겠지만 doTraversJsonCPP 함수는 재귀 함수 입니다.

사용을 하시기 위해서는 두가지를 선행 하셔야 합니다.

Json.h 파일을 인클루드 하셔야 하고요.

json_vc2017_libmtd.lib 파일을 링크 옵션에 추가 하시면 됩니다.
 
 만약에 파싱하시는 Json 파일이 UTF8 인코딩이 되어 있으면 한글이 깨져서 나올겁니다.



소스에 .asCString() 해서 값을 얻어 오는 부분이 있는데 이값을 UniCode에서 MutiByteCode 로 변환을 하셔야 합니다.

변환 하는 방법은 제 블로그에 내용이 있으니 참조 하시면 됩니다.

https://blog.naver.com/berdo_seok/220931299353


제가 빌드한 환경은 MutiByte 환경입니다.

VisualStudio 2017로 기본 프로젝트 생성하시게 되면 UniCode 로 생성이 되실겁니다.

제가 빌드한 JsonCPP 라이브러리는 MultiByte 환경으로 빌드가 되어 있으니

UinCode에서 사용하시는 분이라면 CString 변수에 .asCString() 로 값을 넣으실때에는

MutilByte To UniCode 를 사용 하셔야 합니다.

https://blog.naver.com/berdo_seok/220931302777

설명은 드렸는데 잘 모르시겠으면 댓글을 남겨 주시면 답변 드리겠습니다. ^^



내용은 여기 까지고 라이브러리와 헤더 파일은 별도로 올려 둘테니 쓰실분은 받으시면 됩니다.

Json.zip : 헤더파일, json_vc2017_libmtd.lib (멀티바이트 디버그용), json_vc2017_libmt.lib(멀티바이트 릴리즈용)
파일은 네이버 블로그에 있으니 여기서 받아가시기 바랍니다. ^^

https://blog.naver.com/berdo_seok/221789982110