kondoumh のブログ

- とあるソフトウェアエンジニアのめったに更新されないブログ -

C++ REST SDK で null safe な JSON デシリアライズ処理を書く

iEdit では XML 形式のデータエクスポート・インポートをサポートしていますが、今時は XML より JSON だよねってことでこの前対応しました。

blog.kondoumh.com

この対応のために C++ の JSON 処理系ライブラリを探して、Microsoft 製の OSS C++ REST SDK (cpprestsdk) を知りました。

blog.kondoumh.com

iEdit からエクスポートした JSON ファイルはフルセットのデータがあるので何も考えずにインポートして問題ありませんが、ユーザはエディタで作ったり別のプログラムからエクスポートしたりしたデータをインポートしたいもしれません。その場合、全ての値が適切に入ってくると想定するとまずいことになります。例えば、ノードの ID は数値ですが文字列が指定されてしまうかもしれません。ノードの位置を表す座標は何も設定されていないかもしれません。入力データをチェックして不正なデータを弾くようにすれば、ランタイムのエラーは防げますが、インポートデータを作成するのが大変になってしまいます。

ということで、入力されない属性については極力デフォルト値を割り当て、なるべくインポートを成功させる方向で実装しました。JSON の Parse が終わった後、所定のキーで値を取得してノードやリンクの情報を構築していきますが、値が取得できるかを事前に判定し、取得できない場合はデフォルト値を割り当てます。

iEdit で使用するデータ型は、文字列、数値、配列、真偽値 の4つです。cpprestsdk では、JSON value に対して、is_null() / is_string() / is_number() などのチェック用のヘルパーメソッドが用意されています。これを使って HasValue メソッドを書きました。ちょっとダサいですが キーは2つまでとし 値が目的の型で取れるかをチェックします。

bool JsonProcessor::HasValue(json::value v, json::value::value_type type, const wchar_t* key1, const wchar_t* key2)
{
    json::value target = v[key1];
    if (target.is_null()) return false;

    if (key2 != L"") {
        target = v[key1][key2];
        if (target.is_null()) {
            return false;
        }
    }
    switch (type) {
    case json::value::String: return target.is_string();
    case json::value::Number: return target.is_number();
    case json::value::Array: return target.is_array();
    case json::value::Boolean: return target.is_boolean();
    }
    return false;
}

これにより、値の構築コードは以下のように三項演算子でデフォルト値を指定しながら書けます。

auto fillColor = HasValue(v, json::value::String, L"fill_color") ?
                            v[L"fill_color"].as_string() : L"#FFFFFF";
node.SetFillColor(ConvertToRGB(fillColor));

座標など、複数項目にわたる属性もあるので、それらについては個別に値設定ロジックを書きました。