diff --git a/changelogs/unreleased/encode-decimals-as-json-num.md b/changelogs/unreleased/encode-decimals-as-json-num.md new file mode 100644 index 0000000000000000000000000000000000000000..8621fcfdd26cfaf423491079f97cd785a29f5fd7 --- /dev/null +++ b/changelogs/unreleased/encode-decimals-as-json-num.md @@ -0,0 +1,4 @@ +## bugfix/sql +* Add `encode_decimal_as_number` parameter to JSON config. +That forces to encode `decimal` as JSON number to force type consistency in JSON output. +Use with catious - most of JSON parsers assume that number is restricted to float64. diff --git a/src/lua/serializer.c b/src/lua/serializer.c index 7c350a3ad4cd8df79fc49a9f41f06506f486d77b..466546b6f88a8c35299c81945952445695c26062 100644 --- a/src/lua/serializer.c +++ b/src/lua/serializer.c @@ -73,6 +73,7 @@ static struct { OPTION(LUA_TBOOLEAN, encode_deep_as_nil, 0), OPTION(LUA_TBOOLEAN, encode_invalid_numbers, 1), OPTION(LUA_TNUMBER, encode_number_precision, 14), + OPTION(LUA_TBOOLEAN, encode_decimal_as_number, 0), OPTION(LUA_TBOOLEAN, encode_load_metatables, 1), OPTION(LUA_TBOOLEAN, encode_use_tostring, 0), OPTION(LUA_TBOOLEAN, encode_invalid_as_nil, 0), diff --git a/src/lua/serializer.h b/src/lua/serializer.h index 7ba85a051e876a136ab4c9b0144713846cbce172..f14979961f5a9904323e82fe30482737717288b2 100644 --- a/src/lua/serializer.h +++ b/src/lua/serializer.h @@ -118,6 +118,11 @@ struct luaL_serializer { int encode_invalid_numbers; /** Floating point numbers precision (YAML, CJSON only) */ int encode_number_precision; + /** + * Encode decimal as JSON number instead of string + * potentially losing precision on decoding) + */ + int encode_decimal_as_number; /** * Enables __serialize meta-value checking: diff --git a/test/app-tap/json.test.lua b/test/app-tap/json.test.lua index 7a967513cc20e56a2ef7f116c2172a8e368577a3..e28ec2736da2680461386118a1fdaac349061354 100755 --- a/test/app-tap/json.test.lua +++ b/test/app-tap/json.test.lua @@ -25,7 +25,7 @@ test:plan(1) test:test("json", function(test) local serializer = require('json') - test:plan(58) + test:plan(61) test:test("unsigned", common.test_unsigned, serializer) test:test("signed", common.test_signed, serializer) @@ -76,6 +76,20 @@ test:test("json", function(test) test:is(serializer.cfg.encode_number_precision, orig_encode_number_precision, 'global option remains unchanged') + local orig_encode_decimal_as_number = + serializer.cfg.encode_decimal_as_number + local dec_num = require('decimal').new('0.123456789123456789') + serializer.cfg({encode_decimal_as_number = true}) + test:ok(serializer.encode({a = dec_num}) == '{"a":0.123456789123456789}', + 'decimal is encoded as number') + serializer.cfg({encode_decimal_as_number = orig_encode_decimal_as_number}) + test:ok(serializer.encode({a = dec_num}) == '{"a":"0.123456789123456789"}', + 'decimal is encoded as string') + test:is( + serializer.cfg.encode_decimal_as_number, + orig_encode_decimal_as_number, + 'global option remains unchanged') + local orig_decode_invalid_numbers = serializer.cfg.decode_invalid_numbers serializer.cfg({decode_invalid_numbers = false}) test:ok(not pcall(serializer.decode, '{"a":inf}'), diff --git a/third_party/lua-cjson/lua_cjson.c b/third_party/lua-cjson/lua_cjson.c index cd4293df940dd88af240f26f74e491636291e167..94a7a37ca08c6161cf0abb17fa2b5db79d593a83 100644 --- a/third_party/lua-cjson/lua_cjson.c +++ b/third_party/lua-cjson/lua_cjson.c @@ -220,6 +220,32 @@ static void json_append_string(struct luaL_serializer *cfg, strbuf_t *json, strbuf_append_char_unsafe(json, '\"'); } +/* json_append_unescaped_string args: + * - lua_State + * - JSON strbuf + * - String (Lua stack index) + * + * Returns nothing. Doesn't remove string from Lua stack */ +static void json_append_decimal(struct luaL_serializer *cfg, strbuf_t *json, + decimal_t * decNumber) +{ + const char *str = decimal_str(decNumber); + const size_t len = strlen(str); + + if (! cfg->encode_decimal_as_number) { + return json_append_string(cfg, json, str, len); + } + + (void) cfg; + + // append decimal as unescaped string to represent JSON number + strbuf_ensure_empty_length(json, len); + size_t i; + for (i = 0; i < len; i++) { + strbuf_append_char_unsafe(json, str[i]); + } +} + static void json_append_data(lua_State *l, struct luaL_serializer *cfg, int current_depth, strbuf_t *json); @@ -383,8 +409,7 @@ static void json_append_data(lua_State *l, struct luaL_serializer *cfg, switch (field.ext_type) { case MP_DECIMAL: { - const char *str = decimal_str(field.decval); - return json_append_string(cfg, json, str, strlen(str)); + return json_append_decimal(cfg, json, field.decval); } case MP_UUID: return json_append_string(cfg, json, tt_uuid_str(field.uuidval),