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