wstring な JSON 処理系 (その3)

メンバ関数 dump で、 json オブジェクトから JSON 書式を生成します。

  1. json クラスのコンストラクタとデスタラクタ
  2. json クラスのコピー・コンストラクタとムーブ・コンストラクタ
  3. dump (本稿)
  4. load の構文解析
  5. load の字句解析

引数に出力先の文字列 wstring を参照渡しするものと、値として返すものの 2 種類を使えるようにしておきます。

//@<dump メンバ関数を宣言・定義します@>=
    void dump (std::wstring& out) const;

    std::wstring dump () const
    {
        std::wstring out;
        dump (out);
        return out;
    }

dump の定義では、 共用体の有効なメンバから文字列を作っていきます。 なお、 この実装の独自のふるまいとして、 実数と整数を区別するために、 json オブジェクトが double のとき、 小数部か指数部のいずれかを必ずつけるようにしてあります。

//@<dump メンバ関数を定義します@>=
void json::dump (std::wstring& out) const
{
    int count = 0;
    switch (mguard) {
    case JSONNULL:
        out += L"null";
        break;
    case BOOLEAN:
        out += mboolean ? L"true" : L"false";
        break;
    case INTEGER:
        out += std::to_wstring (minteger);
        break;
    case NUMBER:
        {
            wchar_t buf[32];  // %.15g 形式の最大長は L"-0.123456789012345e-1023\0" で 25 文字
            std::swprintf (buf, sizeof(buf)/sizeof(buf[0]), L"%.15g", mnumber);
            std::wstring s (buf);
            if (s.find_first_of (L".e") == std::wstring::npos)
                s += L".0";
            out += s;
        }
        break;
    case STRING:
        out += L"\"";
        for (wchar_t c : mstring) {
            if (L'\\' == c)
                out += L"\\\\";
            else if (L'\"' == c)
                out += L"\\\"";
            else if (L'/' == c)
                out += L"\\/";
            else if (L'\b' == c)
                out += L"\\b";
            else if (L'\r' == c)
                out += L"\\r";
            else if (L'\n' == c)
                out += L"\\n";
            else if (L'\t' == c)
                out += L"\\t";
            else if (c < L' ') {
                wchar_t buf[8];
                unsigned int u = c;
                std::swprintf (buf, sizeof(buf)/sizeof(buf[0]), L"\\u%04x", u); // 修正: %4x -> %04x
                out += buf;
            }
            else
                out += c;
        }
        out += L"\"";
        break;
    case ARRAY:
        out += L"[";
        for (auto& x : marray) {
            if (count++ > 0)
                out += L", ";
            x.dump (out);
        }
        out += L"]";
        break;
    case OBJECT:
        out += L"{";
        for (auto& x : mobject) {
            if (count++ > 0)
                out += L", ";
            json key (x.first);
            key.dump (out);
            out += L": ";
            x.second.dump (out);
        }
        out += L"}";
        break;
    }
}

json オブジェクトの共用体で、どのメンバが有効かを調べるテンプレート・メンバ関数 is は、picojson の同名のテンプレート・メンバ関数から拝借してきました。

//@<is メンバ関数を宣言します@>=
    template<typename T> bool is () const;

//@<is メンバ関数の特化をおこないます@>=
template<> bool json::is<std::nullptr_t> () const { return mguard == JSONNULL; }
template<> bool json::is<bool> () const { return mguard == BOOLEAN; }
template<> bool json::is<int> () const { return mguard == INTEGER; }
template<> bool json::is<double> () const { return mguard == NUMBER; }
template<> bool json::is<std::wstring> () const { return mguard == STRING; }
template<> bool json::is<array> () const { return mguard == ARRAY; }
template<> bool json::is<object> () const { return mguard == OBJECT; }

共用体のメンバにアクセスも picojson のテンプレート・メンバ関数 get から拝借してきたものですが、名前を as に変えました。ここもまた m4 のお世話になって生成した部分です。

//@<as メンバ関数を宣言します@>=
    template<typename T> T const& as () const;
    template<typename T> T& as ();

//@<as メンバ関数の特化をおこないます@>=
template<> bool const& json::as<bool> () const
{
    if (mguard != BOOLEAN) throw std::logic_error ("as: not boolean.");
    return mboolean;
}

template<> bool& json::as<bool> ()
{
    if (mguard != BOOLEAN) throw std::logic_error ("as: not boolean.");
    return mboolean;
}

template<> int const& json::as<int> () const
{
    if (mguard != INTEGER) throw std::logic_error ("as: not integer.");
    return minteger;
}

template<> int& json::as<int> ()
{
    if (mguard != INTEGER) throw std::logic_error ("as: not integer.");
    return minteger;
}

template<> double const& json::as<double> () const
{
    if (mguard != NUMBER) throw std::logic_error ("as: not number.");
    return mnumber;
}

template<> double& json::as<double> ()
{
    if (mguard != NUMBER) throw std::logic_error ("as: not number.");
    return mnumber;
}

template<> std::wstring const& json::as<std::wstring> () const
{
    if (mguard != STRING) throw std::logic_error ("as: not string.");
    return mstring;
}

template<> std::wstring& json::as<std::wstring> ()
{
    if (mguard != STRING) throw std::logic_error ("as: not string.");
    return mstring;
}

template<> array const& json::as<array> () const
{
    if (mguard != ARRAY) throw std::logic_error ("as: not array.");
    return marray;
}

template<> array& json::as<array> ()
{
    if (mguard != ARRAY) throw std::logic_error ("as: not array.");
    return marray;
}

template<> object const& json::as<object> () const
{
    if (mguard != OBJECT) throw std::logic_error ("as: not object.");
    return mobject;
}

template<> object& json::as<object> ()
{
    if (mguard != OBJECT) throw std::logic_error ("as: not object.");
    return mobject;
}

共用体の object か array が有効なとき、 要素を json で返すメンバ関数は picojson では get でしたが、 要素オペレータに変更します。

//@<要素オペレータを定義します@>=
    json const& operator[] (size_t idx) const
    {
        static json nullval;
        if (mguard != ARRAY)
            return nullval;
        return idx < marray.size () ? marray[idx] : nullval;
    }

    json& operator[] (size_t idx)
    {
        static json nullval;
        if (mguard != ARRAY)
            return nullval;
        return idx < marray.size () ? marray[idx] : nullval;
    }

    json const& operator[] (std::wstring const& key) const
    {
        static json nullval;
        if (mguard != OBJECT)
            return nullval;
        object::const_iterator i = mobject.find (key);
        return i != mobject.end () ? i->second : nullval;
    }

    json& operator[] (std::wstring const& key)
    {
        static json nullval;
        if (mguard != OBJECT)
            return nullval;
        object::iterator i = mobject.find (key);
        return i != mobject.end () ? i->second : nullval;
    }

これでようやく JSON のためのデータ構造部分ができあがりました。 dump は使えるようになっているので、 次から load を作ります。