From 1b1761087dce88a499e1e8df523acd66990ac743 Mon Sep 17 00:00:00 2001 From: skewb1k Date: Wed, 27 Aug 2025 01:42:24 +0300 Subject: [PATCH] feature: add option to indent encoded output --- README.md | 26 +++++++++++++++++++++++ lua_cjson.c | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++ tests/test.lua | 38 ++++++++++++++++++++++++++++++++++ 3 files changed, 120 insertions(+) diff --git a/README.md b/README.md index debe1ae8..d2094b1c 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ Table of Contents * [encode_number_precision](#encode_number_precision) * [encode_escape_forward_slash](#encode_escape_forward_slash) * [encode_skip_unsupported_value_types](#encode_skip_unsupported_value_types) + * [encode_indent](#encode_indent) * [decode_array_with_array_mt](#decode_array_with_array_mt) Description @@ -201,6 +202,31 @@ This will generate: [Back to TOC](#table-of-contents) +encode_indent +---------------------------- +**syntax:** `cjson.encode_indent(indent)` + +If non-empty string provided, JSON values encoded by `cjson.encode()` will be +formatted in a human-readable way, using `indent` for indentation +at each nesting level. Also enables newlines and a space after colons. + +Example: + +```lua +local cjson = require "cjson" + +cjson.encode_indent(" ") +print(cjson.encode({ a = 1, b = { c = 2 } })) +-- { +-- "a": 1, +-- "b": { +-- "c": 2 +-- } +-- } +``` + +[Back to TOC](#table-of-contents) + decode_array_with_array_mt -------------------------- **syntax:** `cjson.decode_array_with_array_mt(enabled)` diff --git a/lua_cjson.c b/lua_cjson.c index bbd8eff8..d32552c2 100644 --- a/lua_cjson.c +++ b/lua_cjson.c @@ -91,6 +91,7 @@ #define DEFAULT_DECODE_ARRAY_WITH_ARRAY_MT 0 #define DEFAULT_ENCODE_ESCAPE_FORWARD_SLASH 1 #define DEFAULT_ENCODE_SKIP_UNSUPPORTED_VALUE_TYPES 0 +#define DEFAULT_ENCODE_INDENT NULL #ifdef DISABLE_INVALID_NUMBERS #undef DEFAULT_DECODE_INVALID_NUMBERS @@ -172,6 +173,7 @@ typedef struct { int encode_keep_buffer; int encode_empty_table_as_object; int encode_escape_forward_slash; + const char *encode_indent; int decode_invalid_numbers; int decode_max_depth; @@ -310,6 +312,18 @@ static int json_enum_option(lua_State *l, int optindex, int *setting, return 1; } +/* Process string option for a configuration function */ +static int json_string_option(lua_State *l, int optindex, const char **setting) +{ + if (!lua_isnil(l, optindex)) { + const char *value = luaL_checkstring(l, optindex); + *setting = value; + } + + lua_pushstring(l, *setting ? *setting : ""); + return 1; +} + /* Configures handling of extremely sparse arrays: * convert: Convert extremely sparse arrays into objects? Otherwise error. * ratio: 0: always allow sparse; 1: never allow sparse; >1: use ratio @@ -400,6 +414,18 @@ static int json_cfg_encode_keep_buffer(lua_State *l) return 1; } +/* Configure how to indent output */ +static int json_cfg_encode_indent(lua_State *l) +{ + json_config_t *cfg = json_arg_init(l, 1); + + json_string_option(l, 1, &cfg->encode_indent); + /* simplify further checking */ + if (cfg->encode_indent[0] == '\0') cfg->encode_indent = NULL; + + return 1; +} + #if defined(DISABLE_INVALID_NUMBERS) && !defined(USE_INTERNAL_FPCONV) void json_verify_invalid_number_setting(lua_State *l, int *setting) { @@ -491,6 +517,7 @@ static void json_create_config(lua_State *l) cfg->decode_array_with_array_mt = DEFAULT_DECODE_ARRAY_WITH_ARRAY_MT; cfg->encode_escape_forward_slash = DEFAULT_ENCODE_ESCAPE_FORWARD_SLASH; cfg->encode_skip_unsupported_value_types = DEFAULT_ENCODE_SKIP_UNSUPPORTED_VALUE_TYPES; + cfg->encode_indent = DEFAULT_ENCODE_INDENT; #if DEFAULT_ENCODE_KEEP_BUFFER > 0 strbuf_init(&cfg->encode_buf, 0); @@ -660,6 +687,13 @@ static void json_check_encode_depth(lua_State *l, json_config_t *cfg, static int json_append_data(lua_State *l, json_config_t *cfg, int current_depth, strbuf_t *json); +static void json_append_newline_and_indent(strbuf_t *json, json_config_t *cfg, int depth) +{ + strbuf_append_char(json, '\n'); + for (int i = 0; i < depth; i++) + strbuf_append_string(json, cfg->encode_indent); +} + /* json_append_array args: * - lua_State * - JSON strbuf @@ -668,15 +702,21 @@ static void json_append_array(lua_State *l, json_config_t *cfg, int current_dept strbuf_t *json, int array_length, int raw) { int comma, i, json_pos, err; + int has_items = 0; strbuf_append_char(json, '['); comma = 0; for (i = 1; i <= array_length; i++) { + has_items = 1; + json_pos = strbuf_length(json); if (comma++ > 0) strbuf_append_char(json, ','); + if (cfg->encode_indent) + json_append_newline_and_indent(json, cfg, current_depth); + if (raw) { lua_rawgeti(l, -1, i); } else { @@ -698,6 +738,9 @@ static void json_append_array(lua_State *l, json_config_t *cfg, int current_dept lua_pop(l, 1); } + if (has_items && cfg->encode_indent) + json_append_newline_and_indent(json, cfg, current_depth-1); + strbuf_append_char(json, ']'); } @@ -752,6 +795,7 @@ static void json_append_object(lua_State *l, json_config_t *cfg, int current_depth, strbuf_t *json) { int comma, keytype, json_pos, err; + int has_items = 0; /* Object */ strbuf_append_char(json, '{'); @@ -760,10 +804,15 @@ static void json_append_object(lua_State *l, json_config_t *cfg, /* table, startkey */ comma = 0; while (lua_next(l, -2) != 0) { + has_items = 1; + json_pos = strbuf_length(json); if (comma++ > 0) strbuf_append_char(json, ','); + if (cfg->encode_indent) + json_append_newline_and_indent(json, cfg, current_depth); + /* table, key, value */ keytype = lua_type(l, -2); if (keytype == LUA_TNUMBER) { @@ -778,6 +827,9 @@ static void json_append_object(lua_State *l, json_config_t *cfg, "table key must be a number or string"); /* never returns */ } + if (cfg->encode_indent) + strbuf_append_char(json, ' '); + /* table, key, value */ err = json_append_data(l, cfg, current_depth, json); @@ -792,6 +844,9 @@ static void json_append_object(lua_State *l, json_config_t *cfg, /* table, key */ } + if (has_items && cfg->encode_indent) + json_append_newline_and_indent(json, cfg, current_depth-1); + strbuf_append_char(json, '}'); } @@ -1571,6 +1626,7 @@ static int lua_cjson_new(lua_State *l) { "decode_invalid_numbers", json_cfg_decode_invalid_numbers }, { "encode_escape_forward_slash", json_cfg_encode_escape_forward_slash }, { "encode_skip_unsupported_value_types", json_cfg_encode_skip_unsupported_value_types }, + { "encode_indent", json_cfg_encode_indent }, { "new", lua_cjson_new }, { NULL, NULL } }; diff --git a/tests/test.lua b/tests/test.lua index cf7a54a2..0513d979 100755 --- a/tests/test.lua +++ b/tests/test.lua @@ -333,6 +333,44 @@ local cjson_tests = { json.decode, { [["\uDB00\uD"]] }, false, { "Expected value but found invalid unicode escape code at character 2" } }, + -- Test indenting + { 'Set encode_indent(" ")', + json.encode_indent, { " " }, true, { " " } }, + { "Encode object with indenting", + json.encode, { { a = "a", b = "b" } }, + true, { + util.one_of { + '{\n "a": "a",\n "b": "b"\n}', + '{\n "b": "b",\n "a": "a"\n}', + } + } }, + { "Encode empty object with indenting", + json.encode, { { } }, true, { '{}' } }, + { "Encode nested object with indenting", + json.encode, { { a = { b = 1 } } }, + true, { '{\n "a": {\n "b": 1\n }\n}' } }, + { "Encode array with indenting", + json.encode, { { 1, 2, 3 } }, + true, { '[\n 1,\n 2,\n 3\n]' } }, + { "Encode empty array with indenting", + json.encode, { json.empty_array }, true, { '[]' } }, + { "Encode nested arrays with indenting", + json.encode, { { { 1, 2 }, { 3, 4 } } }, + true, { '[\n [\n 1,\n 2\n ],\n [\n 3,\n 4\n ]\n]' } }, + { "Encode array of objects with indenting", + json.encode, { { { a = "a" }, { b = "b" } } }, + true, { '[\n {\n "a": "a"\n },\n {\n "b": "b"\n }\n]' } }, + { 'Set encode_indent("abc")', + json.encode_indent, { "abc" }, true, { "abc" } }, + { "Encode object with non-whitespace indenting", + json.encode, { { a = { b = 1 } } }, + true, { '{\nabc"a": {\nabcabc"b": 1\nabc}\n}' } }, + { 'Set encode_indent("")', + json.encode_indent, { "" }, true, { "" } }, + { "Encode array of objects with empty indenting", + json.encode, { { { a = "a" }, { b = "b" } } }, + true, { '[{"a":"a"},{"b":"b"}]' } }, + -- Test locale support -- -- The standard Lua interpreter is ANSI C online doesn't support locales