json.c ー ②

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

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

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

今回は②についてです。
全体のプログラムは最後に載せます。
さて、ここで作成する関数は以下のものです。

json_val *json_readval(char **s) : 文字列からjson_valを取り出す。根幹。
char *json_readstr(char **s) : 文字列からstringを取り出す。一番めんどくさいとこ。
unsigned char json_readhex(char c) : '0'から'f'を、0から15に変換する。おまけ。
double json_readnum(char **s) : 文字列から数字を取り出す。
int json_isnumber(char c) : 数字をなす文字かどうか判断する。(- , 0-9 , . , e , E)

void json_free(json_val *v) : 上の関数でつくったjson_valを、再帰的に、正しくfreeする関数。


json_readvalでは、文字に応じてjson_typeを判断し、いろいろします。
objectやarrayであったら]や}までvalueを再帰的に読みまくり、JSON_OBJやJSON_ARYタイプのvalueに格納して返します。
stringであったらjson_readstrに任せて、帰ってきたchar *をJSON_STRタイプのvalueに格納して返します。
numberであったらjson_readnumに任せて、帰ってきたdoubleからJSON_NUMタイプのvalueを作り、それを返します。

json_readstrで特殊なのが、バックスラッシュが出てきた時の対応です。
といっても基本的に、\nや\tをちゃんと扱えばいいのですが、ひとつ大変なのが\u12a1などの表記。
utf-16での表記をutf-8に治す必要がありますので、4文字読み取り、値をcharに格納し、utf16.cを使ってutf-8に変換しています。

json_readnumは、numberと思われる範囲をメモリに格納し、sscanfを使って数字を取り出しています。
ちゃんとしたら相当ややこしい処理が必要な所。ありがたいです。scanfがありがたいと思った初めての瞬間でした。

json_freeでは、再帰的にいろいろmallocしたものをfreeします。
上の関数を用いて作ったものであれば、true, false, nullなどは一つ作ったものを全てに流用してるのでfreeしなかったりなど、ちゃんと処理できます。


というわけで、とりあえず、プログラム全容です。

#ifndef _JSON
#define _JSON

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include "utf16.c"

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;

void json_free(json_val *v);

json_val *json_readval(char **s);
char *json_readstr(char **s);
unsigned char json_readhex(char c);
double json_readnum(char **s);
int json_isnumber(char c);

void json_print(json_val *v, int space);

json_val *json_obj_getval(json_obj *object, char *name);

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);

// free all malloced. without JSON_N, JSON_T, JSON_F because those are static.
void json_free(json_val *v){
   json_obj *jop_bef, *jop_aft;
   json_ary *jap_bef, *jap_aft;
   switch(v->type){
      case JSON_N :
      case JSON_T :
      case JSON_F :
   break;
      case JSON_NUM :
      case JSON_STR :
//      printf("free content");
   free(v->data);
//      printf("free NUM or STR\n");
   free(v);
   break;
      case JSON_OBJ :
   jop_aft = (json_obj *)v->data;
   while(1){
      jop_bef = jop_aft;
      jop_aft = jop_aft->next;
//         printf("free OBJMEM\n");
      free(jop_bef);
      if(jop_aft == NULL) break;
      json_free(jop_aft->name);
      json_free(jop_aft->value);
   }
//      printf("free OBJ\n");
   free(v);
   break;
      case JSON_ARY :
   jap_aft = (json_ary *)v->data;
   while(1){
      jap_bef = jap_aft;
      jap_aft = jap_aft->next;
//         printf("free ARYMEM\n");
      free(jap_bef);
      if(jap_aft == NULL) break;
      json_free(jap_aft->value);
   }
//      printf("free ARY\n");
   free(v);
   break;    
   }
}

// read value. pointer is |[|**, **, **], ** => [**, **, **]|,| **
json_val *json_readval(char **s){
   static json_val *v_null, *v_true, *v_false;
   static int init = 0;
   if(!init){
      v_null = json_mkval(JSON_N, NULL);
      v_true = json_mkval(JSON_T, NULL);
      v_false = json_mkval(JSON_F, NULL);
   }
   while(!(**s == '{' || **s == '[' || **s == '"' || 
    json_isnumber(**s) || **s == 't' || **s == 'f' || **s == 'n')){
      if(**s == '\0') return NULL; // read end
      if(**s == ']' || **s == '}'){
   (*s)++;
   return NULL;
      }
      (*s)++;
   }
   json_val *namp, *valp;
   json_obj *jop_f, *jop_l;
   json_ary *jap_f, *jap_l;
   switch(**s){
      case '{' : // object
   (*s)++;
   jop_l = (jop_f = json_obj_mk());
   while((namp = json_readval(s)) != NULL){ // end when there is '}'
      while(*((*s)++) != ':');
      valp = json_readval(s);
      jop_l = json_obj_add(jop_l, namp, valp);
   }
   valp = json_mkval(JSON_OBJ, (void *)jop_f);
   break;
      case '[' : // array
   (*s)++;
   jap_l = (jap_f = json_ary_mk());
   while((valp = json_readval(s)) != NULL){ // end when there is ']'
      jap_l = json_ary_add(jap_l, valp);
   }
   valp = json_mkval(JSON_ARY, (void *)jap_f);
   break;
      case '"' : // string
   valp = json_mkval(JSON_STR, (void *)json_readstr(s));
   break;
      case 't' : // true
   *s += 4;
   valp = v_true;
   break;
      case 'f' : // false
   *s += 5;
   valp = v_false;
   break;
      case 'n' : // null
   *s += 4;
   valp = v_null;
   break;
      default : // number
   valp = json_mkval_num(json_readnum(s));
   break;    
   }
   return valp;
}

char *json_readstr(char **s){
   static char buf[100000];
   char *bufp = buf;
   while(*((*s)++) != '"');
   while(**s != '"'){
      if(**s == '\\'){
   unsigned char u16[2], u8[3];
   int k, u8size;
   switch(*(++(*s))){
      case '"' :
         *(bufp++) = '"';
         break;
      case '\\' :
         *(bufp++) = '\\';
         break;
      case '/' :
         *(bufp++) = '/';
         break;
      case 'b' :
         *(bufp++) = '\b';
         break;
      case 'f' :
         *(bufp++) = '\f';
         break;
      case 'n' :
         *(bufp++) = '\n';
         break;
      case 'r' :
         *(bufp++) = '\r';
         break;
      case 't' :
         *(bufp++) = '\t';
         break;
      case 'u' :
         u16[0] = json_readhex(*(++(*s))) * 0x10;
         u16[0] += json_readhex(*(++(*s)));
         u16[1] = json_readhex(*(++(*s))) * 0x10;
         u16[1] += json_readhex(*(++(*s)));
         u8size = u16_u16tou8(u8, u16);
         for(k = 0; k < u8size; ++k) *(bufp++) = u8[k];
         break;
   }
   (*s)++;
      }
      else *(bufp++) = *((*s)++);
   }
   (*s)++;
   char *ret = (char *)malloc(sizeof(char) * (bufp - buf + 1));
   long k;
   for(k = 0; k < bufp - buf; ++k) ret[k] = buf[k];
   ret[k] = '\0';
   return ret;
}

unsigned char json_readhex(char c){
   if('0' <= c && c <= '9') return (unsigned char)(c - '0');
   else if('a' <= c && c <= 'f') return (unsigned char)(c - 'a' + 10);
   else if('A' <= c && c <= 'F') return (unsigned char)(c - 'A' + 10);
   else printf("error: %c is not hex..\n", c);
   return 0;
}

double json_readnum(char **s){
   static char buf[100];
   char *bufp = buf;
   while(!json_isnumber(**s)) *(bufp++) = *((*s)++);
   while(json_isnumber(**s)) *(bufp++) = *((*s)++);
   *bufp = '\0';
   double d;
   sscanf(buf, "%lf", &d);
   return d;
}

int json_isnumber(char c){
   return (isdigit(c) || c == '+' || c == '-' || c == 'e' || c == 'E' || c == '.');
}


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_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;
}

#endif
実際に、json.c ー ①に載せたjson形式のデータをtest.txt内にコピペして保存した上で
以下のようなプログラムを作って実行すると、きちんと読み込めてることがわかります。
#include "json.c"
int main(){
   FILE *fp = fopen("./test.txt", "r");
   if(fp == NULL) exit(1);
   char s[10000], *sp = s;
   int c;
   while((c = fgetc(fp)) != EOF) *(sp++) = c;
   *sp = '\0';
   sp = s;
   json_val *jv = json_readval(&sp);
   json_print(jv, 0);
   printf("\n");
   json_free(jv);
   return 0;
}

$ ./test
{
  created_at :
    Mon Feb 24 09:10:26 +0000 2014, 
  id :
    4.38e+17, 
  id_str :
    437877167133696000, 
  text :
    どんだけwwww, 
  source :
    モバツイ / www.movatwi.jp, 
  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 :
        1.19e+09, 
      id_str :
        1190687748, 
      name :
        みそそ, 
      screen_name :
        akaiketaku, 
      location :
        , 
      url :
        null, 
      description :
        がんばってるよ, 
      protected :
        false, 
      followers_count :
        2.00e+01, 
      friends_count :
        3.00e+01, 
      listed_count :
        3.00e+00, 
      created_at :
        Sun Feb 17 18:42:35 +0000 2013, 
      favourites_count :
        9.20e+01, 
      utc_offset :
        3.24e+04, 
      time_zone :
        Irkutsk, 
      geo_enabled :
        false, 
      verified :
        false, 
      statuses_count :
        5.21e+03, 
      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.00e+00, 
  favorite_count :
    0.00e+00, 
  entities :
    {
      hashtags :
        [
        ], 
      symbols :
        [
        ], 
      urls :
        [
        ], 
      user_mentions :
        [
        ]
    }, 
  favorited :
    false, 
  retweeted :
    false, 
  filter_level :
    medium, 
  lang :
    ja
}