cocos2d-xの標準JSONパーサーといえば spine Jsonだけど、spine Jsonでは、数値データはすべてfloatで扱われる。
現在製作中のアプリではデータをJSON形式で保存しているのだけど、日時のデータをJSON形式でファイルに保存し、次に読み込むと、どうも日時がずれていることに気がついた。
日時のデータは time_t 形式の値で、JSON上ではintとして扱っている。
ところが、一旦保存して次にこれを読み込む際には、spine Jsonはこの値をfloat値としてパースする。
調べてみたところ、このとき僅かではあるが、JSON文字列に記述された値とfloatにパースされた値との間に誤差が生じていることが分かった。
float計算には誤差がつきものと言うが、どうやら、大きな整数値をfloat計算でパースしようとすると、この誤差が無視できなくなってしまうようだ。
そこで、整数値は整数値としてパースするように spine Json を修正することにした。
まずは、spineのJson.hのstruct Jsonに、値が整数値かfloat値かを示すフラグ変数を新たに追加する。
/* The Json structure: */ typedef struct Json { struct Json* next; struct Json* prev; struct Json* child; int type; const char* valuestring; int valueint; float valuefloat; bool isInteger; // 整数値かfloat値かを判別するためのフラグ const char* name; } Json;
整数値かfloat値かを示すフラグ変数を導入する代わりに、 Json_NumberをJson_IntとJson_Floatに分けるやりかたもあるかと思うが、このやり方のほうが他のコードへの影響が小さくて良いのではないかと思う。
次に、Json.cppの static const char* parse_number (Json *item, const char* num) を次のように修正する。
/* Parse the input text to generate a number, and populate the result into item. */ static const char* parse_number (Json *item, const char* num) { // 途中の計算はすべてintで行う。 int n = 0, sign = 1, scale = 0, subscale = 0, signsubscale = 1; if (*num == '-') sign = -1, num++; if (*num == '0') num++; if (*num >= '1' && *num <= '9') do n = (n * 10) + (*num++ - '0'); while (*num >= '0' && *num <= '9'); if (*num == '.' && num[1] >= '0' && num[1] <= '9') { num++; do n = (n * 10) + (*num++ - '0'), scale--; while (*num >= '0' && *num <= '9'); } if (*num == 'e' || *num == 'E') { num++; if (*num == '+') num++; else if (*num == '-') signsubscale = -1, num++; while (*num >= '0' && *num <= '9') subscale = (subscale * 10) + (*num++ - '0'); } int s = scale + subscale * signsubscale; if (s == 0) { // 整数 n = sign * n; item->valuefloat = (float)n; item->valueint = n; item->isInteger = true; } else if (s > 0) { // 10のs乗の整数 n = sign * n * (int)pow(10.0, s); item->valuefloat = (float)n; item->valueint = n; item->isInteger = true; } else { // sが負数なので、小数 float n2 = (float)sign * (float)n * (float)pow(10.0f, s); item->valuefloat = n2; item->valueint = (int)n2; item->isInteger = false; } item->type = Json_Number; return num; }
これであとは、たとえばこんなふうに使えばいい。
CCObject *CCJson::parseValue(Json *pJsonValue) { if (pJsonValue->type == Json_Number) { if (pJsonValue->isInteger) { // 整数値 return CCInteger::create(pJsonValue->valueint); } else { // float値 return CCFloat::create(pJsonValue->valuefloat); } } }