json.c ー ①

JSON形式のデータを処理するためのプログラム、json.cです。

JSON形式についてはこちらをご参照。
http://json.org/json-ja.html
ここに書いてある通りに処理すればOKです。

このプログラムでは、おおまかにわけると
① json関係の構造体の構築
② 文字列情報からjson関係構造体への格納
を行います。

今回は①についてです。
全体のプログラムは次回に載せようと思います。
まず、json形式の根幹をなすのが、[value]。
[value]には、[string],[number],[object],[array],[true],[false],[null]
の7種類があります。

stringは""で囲まれていて、バックスラッシュがでてくると、その後の文字について個別に処理をしたりします。
numberは数字。幅広い表記に対応していて、指数表記などもしているため、doubleに格納することにしてます。
objectは{}で囲まれていて、{string:value,string:value,...}と、名前/値のペアが集まったものです。
arrayは[]で囲まれていて、[value,value,...]と、valueが羅列されています。
true, false, nullは、そのままtrue, false, nullと表記されています。

例えばtwitterAPIでは、下のようなJSON形式のデータが送られてきます。
{"created_at":"Mon Feb 24 09:10:26 +0000 2014","id":437877167133696000,"id_str":"437877167133696000","text":"\u3069\u3093\u3060\u3051wwww","source":"\u003ca href=\"http:\/\/www.movatwi.jp\" rel=\"nofollow\"\u003e\u30e2\u30d0\u30c4\u30a4 \/ www.movatwi.jp\u003c\/a\u003e","truncated":false,"in_reply_to_status_id":null,"in_reply_to_status_id_str":null,"in_reply_to_user_id":null,"in_reply_to_user_id_str":null,"in_reply_to_screen_name":null,"user":{"id":1190687748,"id_str":"1190687748","name":"\u307f\u305d\u305d","screen_name":"akaiketaku","location":"","url":null,"description":"\u304c\u3093\u3070\u3063\u3066\u308b\u3088","protected":false,"followers_count":20,"friends_count":30,"listed_count":3,"created_at":"Sun Feb 17 18:42:35 +0000 2013","favourites_count":92,"utc_offset":32400,"time_zone":"Irkutsk","geo_enabled":false,"verified":false,"statuses_count":5211,"lang":"ja","contributors_enabled":false,"is_translator":false,"is_translation_enabled":false,"profile_background_color":"C0DEED","profile_background_image_url":"http:\/\/abs.twimg.com\/images\/themes\/theme1\/bg.png","profile_background_image_url_https":"https:\/\/abs.twimg.com\/images\/themes\/theme1\/bg.png","profile_background_tile":false,"profile_image_url":"http:\/\/pbs.twimg.com\/profile_images\/419770158647767040\/5kyljqgj_normal.jpeg","profile_image_url_https":"https:\/\/pbs.twimg.com\/profile_images\/419770158647767040\/5kyljqgj_normal.jpeg","profile_link_color":"0084B4","profile_sidebar_border_color":"C0DEED","profile_sidebar_fill_color":"DDEEF6","profile_text_color":"333333","profile_use_background_image":true,"default_profile":true,"default_profile_image":false,"following":null,"follow_request_sent":null,"notifications":null},"geo":null,"coordinates":null,"place":null,"contributors":null,"retweet_count":0,"favorite_count":0,"entities":{"hashtags":[],"symbols":[],"urls":[],"user_mentions":[]},"favorited":false,"retweeted":false,"filter_level":"medium","lang":"ja"}
string内のバックスラッシュ(\)のせいで、いろいろとややこしいことになっていますが、確かに上で言った通りの形式をしていることがわかります。


このプログラムでは、まずenumでjson_typeを宣言してJSON_NUMやJSON_STRなどのタイプを定義した上で、
データのタイプおよびデータ自体へのポインタを持つjson_val構造体
objectのリスト構造を作るためのjson_obje構造体
arrayのリスト構造を作るためのjson_ary構造体
を作り、得たメッセージをそれをうまく作って構造体に格納し、簡単に目的の値などを取り出せるようにしました。
まずは構造体などの宣言です。
typedef enum json_type{
   JSON_NUM, JSON_STR, JSON_OBJ, JSON_ARY,
   JSON_T, JSON_F, JSON_N
} json_type;

typedef struct json_val{
   json_type type;
   void *data;
} json_val;

// object, first name&value should be (NULL, NULL)
typedef struct json_obj{
   json_val *name;
   json_val *value;
   struct json_obj *next;
} json_obj;

// array, first value should be NULL
typedef struct json_ary{
   json_val *value;
   struct json_ary *next;
} json_ary;
json_valのデータは、メモリをどこかにとって格納し、そのポインタを(void *)型として持つことにしました。よってどのようなデータだとしても、正しく型変換してやればデータがきちんと取り出せます。

json_objでは、json_valへのポインタname, valueおよび、次のjson_objへのポインタnextを持ちます。
nameは必ずstringですしchar *でも良かったのですが、いろいろあって結局json_val *で宣言しました。理由は忘れました笑

json_aryではjson_valへのポインタvalueおよび、次のjson_aryへのポインタnextを持ちます。

これらを構築するために、いくつか関数をつくりました。
json_val *json_mkval(json_type type, void *data);
json_val *json_mkval_num(double d);
json_obj *json_obj_mk();
json_obj *json_obj_add(json_obj *object, json_val *name, json_val *value);
json_ary *json_ary_mk();
json_ary *json_ary_add(json_ary *array, json_val *value);

json_***_mk()は、中身のない、リストの先頭を作るだけの関数です。nextにもNULLを入れておきます。
json_***_add(...)は、そのリストに新たなjson_objを加える関数です。引数としてもらったjson_obj *objectやjson_ary *arrayのnextがNULLになるまで進んでいき、NULLのところに、作った新たなものへのポインタを入れます。

json_mkval(...)は、json_typeと、データへのポインタを受け取り、json_value構造体を作って格納し、返す関数です。
例えば[string]の[value]を作りたければ、
json_val *jv = json_mkvak(JSON_STR, (void *)"hogehoge");
なんかしとけば、"hogehoge"を持つ[string]の[value]のできあがりです。

さらにjson_obj *jo = json_obj_mk();でjson_obj構造体を作り、
json_obj_add(jo, jv, jv);とすれば、名前"hogehoge"、値"hogehoge"のペアを一個持つ[object]ができ、
それを[value]にしたければ
json_val *jv2 = json_mkval(JSON_OBJ, (void *)jo);
とすれば、[value]として扱えるようになりました。これをまたどっかに入れて…とできるわけです。

数字の時はより簡単にできるよう、json_mkval_num(...)なんかも作っています。

というわけで、関数の中身はこんなかんじ。
json_val *json_mkval(json_type type, void *data){
   json_val *jvp = (json_val *)malloc(sizeof(json_val));
   jvp->type = type;
   jvp->data = data;
   return jvp;
}

json_val *json_mkval_num(double d){
   double *dp = (double *)malloc(sizeof(double));
   *dp = d;
   return json_mkval(JSON_NUM, dp);
}

json_obj *json_obj_mk(){
   return json_obj_add(NULL, NULL, NULL);
}

// add to object and return added json_obj's address
json_obj *json_obj_add(json_obj *object, json_val *name, json_val *value){
   json_obj *jop = (json_obj *)malloc(sizeof(json_obj));
   jop->name = name;
   jop->value = value;
   jop->next = NULL;
   if(object != NULL){
      while(object->next != NULL) object = object->next;
      object->next = jop;
   }
   return jop;
}

json_ary *json_ary_mk(){
   return json_ary_add(NULL, NULL);
}

// add to array and return added json_ary's address
json_ary *json_ary_add(json_ary *array, json_val *value){
   json_ary *jap = (json_ary *)malloc(sizeof(json_ary));
   jap->value = value;
   jap->next = NULL;
   if(array != NULL){
      while(array->next != NULL) array = array->next;
      array->next = jap;
   }
   return jap;
}
さらにもう2つ、今後のために関数をつくっておきました。
json_valを表示する、json_print()関数と、
json_objの中からある名前のものの値を返す、json_val *json_obj_getval(json_obj *object, char *name)関数です。
void json_print(json_val *v, int space){
   json_obj *jop;
   json_ary *jap;
   int k;
   for(k = 0; k < space; ++k) putchar(' ');
   switch(v->type){
      case JSON_NUM :
   printf("%.2e", *(double *)v->data);
   break;
      case JSON_STR :
   printf("%s", (char *)v->data);
   break;
      case JSON_T :
   printf("true");
   break;
      case JSON_F :
   printf("false");
   break;
      case JSON_N :
   printf("null");
   break;
      case JSON_OBJ :
   jop = ((json_obj *)v->data)->next;
   printf("{");
   if(jop != NULL){
      while(1){
         printf("\n");
         json_print(jop->name, space + 2);
         printf(" :\n");
         json_print(jop->value, space + 4);
         if((jop = jop->next) != NULL) printf(", ");
         else break;
      }
   }
   printf("\n");
   for(k = 0; k < space; ++k) putchar(' ');
   printf("}");
   break;
      case JSON_ARY :
   jap = ((json_ary *)v->data)->next;
   printf("[");
   if(jap != NULL){
      while(1){
         printf("\n");
         json_print(jap->value, space + 2);
         if((jap = jap->next) != NULL) printf(", ");
         else break;
      }
   }
   printf("\n");
   for(k = 0; k < space; ++k) putchar(' ');
   printf("]");
   break;
   }
}

json_val *json_obj_getval(json_obj *object, char *name){
   if(object->name == NULL) object = object->next;
   while(strcmp((char *)object->name->data, name) != 0){
      if(object->next == NULL) return NULL;
      object = object->next;
   }
   return object->value;
}
json_printは、うまいこと自分自身を再帰的に呼び出して、json_valを表示します。
きれいに表示するため、前に挿入すべきスペースの数を与える必要があります。

json_obj_getvalは、object = object->nextを繰り返しながら、該当の名前をもつものがないか検索する関数です。
実際にデータを取り出すときに、とても大事です。