JSON 포맷 사용을 위해 boostproperty_tree를 사용하기로 했습니다. 실제로 사용해본 결과 그 간편함이 XMLtinyXML 조합 때 보다 훨씬 쾌적했습니다.  
<!-- JSON 예제 파일 -->
{
    "String": "테스트",
    "Number": 12345,
    "Boolen": true,
    "StrArray": [ "테스트1", "테스트2" ],
    "NumArray": [ 1, 2, 3, 4, 5 ],
    "SubValue" :
    {
	"SubString": "서브테스트",
	"SubNumber": 67890,
	"SubBoolen": false
    }
}
// JSON 입력 테스트
EXPECT_TRUE( exists( "test.json" ) );

wptree pt;
read_json( "test.json", pt, locale("kor") );

wstring strTest = pt.get<wstring>( L"String" );
EXPECT_TRUE( 0 == strTest.compare( L"테스트" ) );

int numTest = pt.get( L"Number", 0 );
EXPECT_TRUE( 12345 == numTest );

bool boolTest = pt.get( L"Boolen", false );
EXPECT_TRUE( boolTest );

vector<wstring> strArrayTest;
BOOST_FOREACH( wptree::value_type &v, pt.get_child( L"StrArray" ) )
{
	strArrayTest.push_back( v.second.data() );
}
EXPECT_TRUE( 2 == (int)strArrayTest.size() );
	
vector<wstring> numArrayTest;
BOOST_FOREACH( wptree::value_type &v, pt.get_child( L"NumArray" ) )
{
	numArrayTest.push_back( v.second.data() );
}
EXPECT_TRUE( 5 == (int)numArrayTest.size() );

wstring strSubTest = pt.get<wstring>( L"SubValue.SubString" );
EXPECT_TRUE( 0 == strSubTest.compare( L"서브테스트" ) );

int numSubTest = pt.get( L"SubValue.SubNumber", 0 );
EXPECT_TRUE( 67890 == numSubTest );

bool boolSubTest = pt.get( L"SubValue.SubBoolen", true );
EXPECT_TRUE( false == boolSubTest );

위의 샘플 코드를 보시면 아시겠지만 굉장히 직관적입니다. 읽고, 가져다 쓰기만 하면 되죠. 타입도 대부분 알아서 정해줍니다. 다만, 배열이 조금 문제인데 이것이 무조건 문자열로만 읽는 것 같습니다. ( 해결법 아시는 분은 자비 점... )

문제는 좀더 있습니다. 특히 쓰기가 문제입니다. boost 1.46.1 버전 기준으로 property_tree를 이용해 JSON 파일 쓰기를 하면, 모든 값들이 문자열로만 저장이 됩니다. int 형이나 bool 형 상관없이 무조건 문자열로만 저장입니다. 읽기와는 전혀 딴판이죠. 그래서 구글링으로 해결책을 찾아보던 중 property_tree의 소스를 조금 수정 하는 것으로 이 문제를 해결할 수 있었습니다.

json_parser_write.hpp52 줄 부분을 보시면 밑의 스샷과 같은 부분이 있습니다.
// Value or object or array
if (indent > 0 && pt.empty())
{
    // Write value
    Str data = create_escapes(pt.template get_value <Str>());
    //stream << Ch('"') << data << Ch('"');
	stream << data;
}

중간에 제가 주석을 쳐놓은 부분이 있습니다. 이 부분이 모든 값을 " "로 감싸어 문자열로 만들어 버리는 부분입니다. 이 부분을 주석 처리하면 문자열이 아닌 값으로 저장이 되죠. 하지만 이렇게만 두면 문자열로 저장하고 싶은 부분도 " " 감싸짐 없이 그대로 저장되기때문에 파싱 할때 오류가 나게 됩니다.

문자열은 예외를 둬야 하죠. 다행히 propert_tree는 값을 지정할때 Translator를 지정할 수 있습니다. 이 기능을 이용해 문자열에만 " "를 감싸 줄 수 있습니다.
template <typename T>
struct tr
{
	typedef T internal_type;
	typedef T external_type;

	boost::optional<T> get_value(const T &v) { return  v.substr(1, v.size() - 2); }
	boost::optional<T> put_value(const T &v) { return L'"' + v + L'"'; }
};

wptree wPt;
wPt.put( L"StringTest", L"Test", tr<wstring>() );
wPt.put( L"NumTest", 1234 );
wPt.put( L"BoolenTest", false );
write_json( "writeTest.json", wPt );
<!-- 출력 결과물 -->
{
    "StringTest": "Test",
    "NumTest": 1234,
    "BoolenTest": false
}
이제 문자열과 값이 구분되어 잘 나옵니다. 하지만 아직 한가지 문제가 더 남았습니다. 바로 한글!! property_tree는 기본적으로 한글 출력이 안됩니다. 읽는 거는 locale("kor") 옵션을 주고, 읽기가 가능하지만 쓰기에서는 locale("kor") 옵션을 준다고 해도 한글 출력이 안됩니다. json_parser_write.hpp 파일 내부를 보면 그 이유를 알수 있습니다.
// This assumes an ASCII superset. But so does everything in PTree.
// We escape everything outside ASCII, because this code can't
// handle high unicode characters.
if (*b == 0x20 || *b == 0x21 || (*b >= 0x23 && *b <= 0x2E) ||
    (*b >= 0x30 && *b <= 0x5B) || (*b >= 0x5D && *b <= 0xFF))
    result += *b;
보시면 문자열 범위에서 영어권 문자열표를 제외한 특수문자는 모조리 제외되어있습니다. 이 때문에 한글을 써도 제대로 출력이 안되는 것 입니다. 여기에 한글 문자열 범위를 추가해 줍니다.
( 참고 : http://ko.wikipedia.org/wiki/%EC%9C%A0%EB%8B%88%EC%BD%94%EB%93%9C_A000~AFFF )

수정 한 후에는 wraite_json에 locale("kor") 옵션을 주고, 쓰기를 실행 합니다.
template <typename T>
struct tr
{
	typedef T internal_type;
	typedef T external_type;

	boost::optional<T> get_value(const T &v) { return  v.substr(1, v.size() - 2); }
	boost::optional<T> put_value(const T &v) { return L'"' + v + L'"'; }
};

wptree wPt;
wPt.put( L"StringTest", L"한글 테스트", tr<wstring>() );
wPt.put( L"NumTest", 1234 );
wPt.put( L"BoolenTest", false );
write_json( "writeTest.json", wPt, locale("kor") );
<!-- 출력 결과물 -->
{
    "StringTest": "한글 테스트",
    "NumTest": 1234,
    "BoolenTest": false
}
이제 한글도 잘 나오게 되었습니다.
만세~

기쁨의 세레모니 짤


  1. 홍구 2011.06.16 16:15

    조흔 짤이다.... ;;; 조흔 자료감솨 합니당

  2. BlogIcon 구차니 2011.06.18 10:31 신고

    짤에서 얼굴이 반댈세! ㅋㅋ


    JSON은 Firefox plugin 이나 즐겨찾기 export 하는게JSON 이라서 처음 알았는데
    이걸 우찌 읽어야 하나 Jei-sn 이라고 해야하나 고민을 했더랬죠 ^^;

    • BlogIcon 친절한티스 2011.06.20 10:00 신고

      원래 얼굴 보는거 아닙니다. 고갱님.
      전 제이슨(?) 이라고 읽는데...

    • 은령 2011.06.22 20:53

      조선에 한표

  3. BlogIcon ICARTSH 2011.07.07 02:50 신고

    Json에서 배열 읽는 방법은..
    vector<int> numArrayTest;
    BOOST_FOREACH( wptree::value_type &v, pt.get_child( L"NumArray" ) )
    {
    numArrayTest.push_back( v.second.get_value<int>());
    }
    이렇게 접근하면 되요..
    근데 jsoncpp처럼 배열에 직접 접근하는 방법은 없는거 같네요 ㅠ.ㅠ
    배열 접근할때마다 루프돌아야 된다니...
    혹시 루프 안돌고 배열 도는 방법 아시면 알려주세요~

    • BlogIcon 친절한티스 2011.07.07 09:49 신고

      boost는 아무래도 미완성이라는 느낌을 지울 수가 없어서 그냥 jsoncpp를 쓰기로 했네요 ㅠㅜ

  4. yielding 2011.08.16 20:37

    좋은 글 감사합니다.

    한글 저장관련해서요. boost source code를 고치는 것은 좀 그렇죠. template specialization을 이용하는 방법이 있습니다. wchar_t에 대해서 따로 full specialization code를 만들면 됩니다.
    json_write_ext.hpp
    [code]
    #include <boost/property_tree/ptree.hpp>
    #include <boost/property_tree/json_parser.hpp>
    #include <iostream>
    #include <string>

    namespace boost { namespace property_tree { namespace json_parser {
    ////////////////////////////////////////////////////////////////////////////////
    //
    //
    //
    ////////////////////////////////////////////////////////////////////////////////
    template<>
    std::wstring create_escapes(std::wstring const& s)
    {
    std::wstring result;
    std::wstring::const_iterator b = s.begin();
    std::wstring::const_iterator e = s.end();
    while (b != e)
    {
    wchar_t v = *b;

    std::cout << std::hex << v << std::endl;
    if (v == 0x20 || v == 0x21 || (v >= 0x23 && v <= 0x2E) ||
    (v >= 0x30 && v <= 0x5B) || (v >= 0x5D && v <= 0xFF) ||
    (v >= 0xAC00 && v <= 0xD7A3)) // is_korean
    result += v;
    else if (v == wchar_t(L'\b')) result += wchar_t(L'\\'), result += wchar_t(L'b');
    else if (v == wchar_t(L'\f')) result += wchar_t(L'\\'), result += wchar_t(L'f');
    else if (v == wchar_t(L'\n')) result += wchar_t(L'\\'), result += wchar_t(L'n');
    else if (v == wchar_t(L'\r')) result += wchar_t(L'\\'), result += wchar_t(L'r');
    else if (v == wchar_t(L'/')) result += wchar_t(L'\\'), result += wchar_t(L'/');
    else if (v == wchar_t(L'"')) result += wchar_t(L'\\'), result += wchar_t(L'"');
    else if (v == wchar_t(L'\\')) result += wchar_t(L'\\'), result += wchar_t(L'\\');
    else
    {
    const wchar_t *hexdigits = L"0123456789ABCDEF";
    typedef make_unsigned<wchar_t>::type UCh;
    unsigned long u = (std::min)(static_cast<unsigned long>(static_cast<UCh>(v)), 0xFFFFul);
    int d1 = u / 4096; u -= d1 * 4096;
    int d2 = u / 256; u -= d2 * 256;
    int d3 = u / 16; u -= d3 * 16;
    int d4 = u;
    result += wchar_t(L'\\'); result += wchar_t(L'u');
    result += wchar_t(hexdigits[d1]); result += wchar_t(hexdigits[d2]);
    result += wchar_t(hexdigits[d3]); result += wchar_t(hexdigits[d4]);
    }
    ++b;
    }

    return result;
    }

    ////////////////////////////////////////////////////////////////////////////////
    //
    //
    //
    ////////////////////////////////////////////////////////////////////////////////
    } // end of json_parser
    } // end of property_tree
    } // end of boost
    [/code]

+ Recent posts