2013年10月20日日曜日

json-c

設定ファイル等のデータ形式として色々と考えられますが、JSONがなかなか良いのではないかと思います。ECMA標準にもなったようです。独断と偏見でデータ形式の利点、欠点をまとめてみました。

データ形式 利点と欠点
バイナリファイル 構造体をそのまま入力すれば読み込み速度は速いし、プログラミングは楽。内容をあまり見せたくない場合には、見にくく編集もしにくいので良い。エンディアンやアライメントの違いなどCPUやコンパイラによって互換性に問題が生じることがある。またデータのバージョンアップ等で間にデータを挿入すると互換性がなくなる。
csv Microsoft Excelで簡単に作ることができる。読み込みのプログラミングも比較的楽。ただしフォーマットが曖昧。通常は小数点がドット(.)で、区切りがカンマ(,)だが、例えばドイツ語版Excelだと小数点がカンマ(,)になり、区切りがセミコロン(;)になるので注意。その他、ダブルコーテーション(")の扱いなど不明点が多い。
INI Windowsの場合、Win32APIを使用すればプログラミングが楽。階層構造が2弾(セクションとキー)しかなく、また配列の表現が出来ないため、複雑な表現はできない。
XML 汎用性が高く、表現力が高い。XPathやXSLやXML SchemaなどXMLを元にした仕様も充実している。仕様が複雑で仕様通りの読み込みのプログラムを完全に自作するのは難しい。しかしライブラリが多くあり、ツールも多い。汎用性が高すぎるために、XMLを元にしたフォーマット(スキーマ)を良い物にするにはそれなりの知識が必要。また冗長性が高いため、データサイズが大きくなる。
JSON 配列や連想配列などのプログラミングで必要な型をそのまま表現できるのが便利。仕様が非常にシンプルで、読み込みのプログラムも比較的簡単に作成できる。

JSONは読み込みのプログラミングが簡単と書きましたが、動的な文字列や連想配列を扱う言語でないと少し厄介かもしれません。やはりライブラリがあれば便利です。

C言語でいくつかライブラリがありますが、 Fedoraではインストーラの段階でjson-cとjson-glibが入っていました。中でもjson-cは依存関係が少なくWindowsでも使用できるなど使い勝手が良さそうなので使ってみました。

json-cの開発環境は以下でインストールできます。

$ sudo yum install json-c-devel

テストプログラムは以下です。jsontest.jsonを読み込み、表示するだけのプログラムですが、これだけの関数が使えれば読み込みは十分だと思われます。

jsontest.c

#include <stdio.h>
#include <json/json.h>

int json_print(json_object *obj)
{
        json_type jt;
        int len, i;
        
        jt = json_object_get_type(obj);
        switch(jt) {
        case json_type_null:
                printf("null\n");
                break;
        case json_type_boolean:
                if (json_object_get_boolean(obj)) {
                        printf("true\n");
                } else {
                        printf("false\n");
                }
                break;
        case json_type_double:
                printf("%f\n", json_object_get_double(obj));
                break;
        case json_type_int:
                printf("%d\n", json_object_get_int(obj));
                break;
        case json_type_object:
                {
                        struct json_object_iterator je, ji;
                        json_object *jo;
                        const char *key;

                        ji = json_object_iter_begin(obj);
                        je = json_object_iter_end(obj);
                        printf("{\n");
                        while (!json_object_iter_equal(&ji, &je)) {
                                key = json_object_iter_peek_name(&ji);
                                printf("%s:\n", key);
                                jo = json_object_object_get(obj, key);
                                json_print(jo);
                                json_object_iter_next(&ji);
                        }
                        printf("}\n");
                }
                break;
        case json_type_array:
                len = json_object_array_length(obj);
                printf("[\n");
                for (i = 0; i < len; i++) {
                        json_object *jo;
                        jo = json_object_array_get_idx(obj, i);
                        json_print(jo);
                }
                printf("]\n");
                break;
        case json_type_string:
                printf("\"%s\"\n", json_object_get_string(obj));
                break;
        default:
                return 1;
        }
        return 0;
}


int main()
{
        json_object *jo;

        jo = json_object_from_file("jsontest.json");
        json_print(jo);
        json_object_put(jo);

        return 0;
} 

コンパイル時にはlibjson-cをリンクします。
$ gcc jsontest.c -ljson-c

注意点は以下です。
  • json_objectは参照カウンタでメモリ管理されているので、使用後にjson_object_put()でメモリを解放する必要がある。
  • json_object_array_get_idx()等の関数で得たjson_objectは参照カウンタをカウントアップしない。元のjson_objectを削除すると参照できなくなる。また得られたjson_objectをjson_object_put()すると元のデータも変更され、その部分が削除される。必要な場合はjson_object_get()でカウントアップする。
  • エラーをあまり返さない。エラー時にNULLを返すため、エラーでNULLなのか、データ自体が無くNULLなのかの判断がしにくい。
  • 文字列の文字コードはutf-8。

0 件のコメント:

コメントを投稿