Skip to content

Commit 79f5497

Browse files
committed
feature: add option to indent encoded output
1 parent 91ca29d commit 79f5497

File tree

3 files changed

+146
-0
lines changed

3 files changed

+146
-0
lines changed

README.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ Table of Contents
1616
* [encode_number_precision](#encode_number_precision)
1717
* [encode_escape_forward_slash](#encode_escape_forward_slash)
1818
* [encode_skip_unsupported_value_types](#encode_skip_unsupported_value_types)
19+
* [encode_indent](#encode_indent)
1920
* [decode_array_with_array_mt](#decode_array_with_array_mt)
2021

2122
Description
@@ -201,6 +202,29 @@ This will generate:
201202

202203
[Back to TOC](#table-of-contents)
203204

205+
encode_indent
206+
----------------------------
207+
**syntax:** `cjson.encode_indent(indent)`
208+
209+
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. Must contain only spaces, tabs, line feeds, or carriage returns.
210+
211+
Example:
212+
213+
```lua
214+
local cjson = require "cjson"
215+
216+
cjson.encode_indent(" ")
217+
print(cjson.encode({ a = 1, b = { c = 2 } }))
218+
-- {
219+
-- "a": 1,
220+
-- "b": {
221+
-- "c": 2
222+
-- }
223+
-- }
224+
```
225+
226+
[Back to TOC](#table-of-contents)
227+
204228
decode_array_with_array_mt
205229
--------------------------
206230
**syntax:** `cjson.decode_array_with_array_mt(enabled)`

lua_cjson.c

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@
9191
#define DEFAULT_DECODE_ARRAY_WITH_ARRAY_MT 0
9292
#define DEFAULT_ENCODE_ESCAPE_FORWARD_SLASH 1
9393
#define DEFAULT_ENCODE_SKIP_UNSUPPORTED_VALUE_TYPES 0
94+
#define DEFAULT_ENCODE_INDENT NULL
9495

9596
#ifdef DISABLE_INVALID_NUMBERS
9697
#undef DEFAULT_DECODE_INVALID_NUMBERS
@@ -172,6 +173,7 @@ typedef struct {
172173
int encode_keep_buffer;
173174
int encode_empty_table_as_object;
174175
int encode_escape_forward_slash;
176+
const char *encode_indent;
175177

176178
int decode_invalid_numbers;
177179
int decode_max_depth;
@@ -310,6 +312,18 @@ static int json_enum_option(lua_State *l, int optindex, int *setting,
310312
return 1;
311313
}
312314

315+
/* Process string option for a configuration function */
316+
static int json_string_option(lua_State *l, int optindex, const char **setting)
317+
{
318+
if (!lua_isnil(l, optindex)) {
319+
const char *value = luaL_checkstring(l, optindex);
320+
*setting = value;
321+
}
322+
323+
lua_pushstring(l, *setting ? *setting : "");
324+
return 1;
325+
}
326+
313327
/* Configures handling of extremely sparse arrays:
314328
* convert: Convert extremely sparse arrays into objects? Otherwise error.
315329
* ratio: 0: always allow sparse; 1: never allow sparse; >1: use ratio
@@ -400,6 +414,40 @@ static int json_cfg_encode_keep_buffer(lua_State *l)
400414
return 1;
401415
}
402416

417+
/* Returns nonzero if the string consists only of allowed whitespace characters */
418+
static int is_allowed_ws(const char *s) {
419+
if (!s) return 0;
420+
for (const unsigned char *p = (const unsigned char *)s; *p; p++) {
421+
switch (*p) {
422+
case 0x20: /* space */
423+
case 0x09: /* tab */
424+
case 0x0A: /* newline */
425+
case 0x0D: /* carriage return */
426+
break;
427+
default:
428+
return 0;
429+
}
430+
}
431+
return 1;
432+
}
433+
434+
/* Configure how to indent output */
435+
static int json_cfg_encode_indent(lua_State *l)
436+
{
437+
json_config_t *cfg = json_arg_init(l, 1);
438+
439+
const char *indent = cfg->encode_indent;
440+
json_string_option(l, 1, &indent);
441+
if (indent[0] == '\0') indent = NULL;
442+
else if (!is_allowed_ws(indent)) {
443+
luaL_argerror(l, 1, "indent must contain only spaces, tabs, line feeds, or carriage returns");
444+
}
445+
cfg->encode_indent = indent;
446+
447+
return 1;
448+
}
449+
450+
403451
#if defined(DISABLE_INVALID_NUMBERS) && !defined(USE_INTERNAL_FPCONV)
404452
void json_verify_invalid_number_setting(lua_State *l, int *setting)
405453
{
@@ -491,6 +539,7 @@ static void json_create_config(lua_State *l)
491539
cfg->decode_array_with_array_mt = DEFAULT_DECODE_ARRAY_WITH_ARRAY_MT;
492540
cfg->encode_escape_forward_slash = DEFAULT_ENCODE_ESCAPE_FORWARD_SLASH;
493541
cfg->encode_skip_unsupported_value_types = DEFAULT_ENCODE_SKIP_UNSUPPORTED_VALUE_TYPES;
542+
cfg->encode_indent = DEFAULT_ENCODE_INDENT;
494543

495544
#if DEFAULT_ENCODE_KEEP_BUFFER > 0
496545
strbuf_init(&cfg->encode_buf, 0);
@@ -660,6 +709,13 @@ static void json_check_encode_depth(lua_State *l, json_config_t *cfg,
660709
static int json_append_data(lua_State *l, json_config_t *cfg,
661710
int current_depth, strbuf_t *json);
662711

712+
static void json_append_newline_and_indent(strbuf_t *json, json_config_t *cfg, int depth)
713+
{
714+
strbuf_append_char(json, '\n');
715+
for (int i = 0; i < depth; i++)
716+
strbuf_append_string(json, cfg->encode_indent);
717+
}
718+
663719
/* json_append_array args:
664720
* - lua_State
665721
* - JSON strbuf
@@ -668,15 +724,21 @@ static void json_append_array(lua_State *l, json_config_t *cfg, int current_dept
668724
strbuf_t *json, int array_length, int raw)
669725
{
670726
int comma, i, json_pos, err;
727+
int has_items = 0;
671728

672729
strbuf_append_char(json, '[');
673730

674731
comma = 0;
675732
for (i = 1; i <= array_length; i++) {
733+
has_items = 1;
734+
676735
json_pos = strbuf_length(json);
677736
if (comma++ > 0)
678737
strbuf_append_char(json, ',');
679738

739+
if (cfg->encode_indent)
740+
json_append_newline_and_indent(json, cfg, current_depth);
741+
680742
if (raw) {
681743
lua_rawgeti(l, -1, i);
682744
} else {
@@ -698,6 +760,9 @@ static void json_append_array(lua_State *l, json_config_t *cfg, int current_dept
698760
lua_pop(l, 1);
699761
}
700762

763+
if (has_items && cfg->encode_indent)
764+
json_append_newline_and_indent(json, cfg, current_depth-1);
765+
701766
strbuf_append_char(json, ']');
702767
}
703768

@@ -752,6 +817,7 @@ static void json_append_object(lua_State *l, json_config_t *cfg,
752817
int current_depth, strbuf_t *json)
753818
{
754819
int comma, keytype, json_pos, err;
820+
int has_items = 0;
755821

756822
/* Object */
757823
strbuf_append_char(json, '{');
@@ -760,10 +826,15 @@ static void json_append_object(lua_State *l, json_config_t *cfg,
760826
/* table, startkey */
761827
comma = 0;
762828
while (lua_next(l, -2) != 0) {
829+
has_items = 1;
830+
763831
json_pos = strbuf_length(json);
764832
if (comma++ > 0)
765833
strbuf_append_char(json, ',');
766834

835+
if (cfg->encode_indent)
836+
json_append_newline_and_indent(json, cfg, current_depth);
837+
767838
/* table, key, value */
768839
keytype = lua_type(l, -2);
769840
if (keytype == LUA_TNUMBER) {
@@ -778,6 +849,9 @@ static void json_append_object(lua_State *l, json_config_t *cfg,
778849
"table key must be a number or string");
779850
/* never returns */
780851
}
852+
if (cfg->encode_indent)
853+
strbuf_append_char(json, ' ');
854+
781855

782856
/* table, key, value */
783857
err = json_append_data(l, cfg, current_depth, json);
@@ -792,6 +866,9 @@ static void json_append_object(lua_State *l, json_config_t *cfg,
792866
/* table, key */
793867
}
794868

869+
if (has_items && cfg->encode_indent)
870+
json_append_newline_and_indent(json, cfg, current_depth-1);
871+
795872
strbuf_append_char(json, '}');
796873
}
797874

@@ -1571,6 +1648,7 @@ static int lua_cjson_new(lua_State *l)
15711648
{ "decode_invalid_numbers", json_cfg_decode_invalid_numbers },
15721649
{ "encode_escape_forward_slash", json_cfg_encode_escape_forward_slash },
15731650
{ "encode_skip_unsupported_value_types", json_cfg_encode_skip_unsupported_value_types },
1651+
{ "encode_indent", json_cfg_encode_indent },
15741652
{ "new", lua_cjson_new },
15751653
{ NULL, NULL }
15761654
};

tests/test.lua

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,50 @@ local cjson_tests = {
333333
json.decode, { [["\uDB00\uD"]] },
334334
false, { "Expected value but found invalid unicode escape code at character 2" } },
335335

336+
-- Test indenting
337+
{ 'Set encode_indent(" ")',
338+
json.encode_indent, { " " }, true, { " " } },
339+
{ "Encode simple object",
340+
json.encode, { { a = "a", b = "b" } },
341+
true, {
342+
util.one_of {
343+
'{\n "a": "a",\n "b": "b"\n}',
344+
'{\n "b": "b",\n "a": "a"\n}',
345+
}
346+
} },
347+
{ "Encode empty object",
348+
json.encode, { { } }, true, { '{}' } },
349+
{ "Encode nested object",
350+
json.encode, { { a = { b = 1 } } },
351+
true, { '{\n "a": {\n "b": 1\n }\n}' } },
352+
{ "Encode simple array",
353+
json.encode, { { 1, 2, 3 } },
354+
true, { '[\n 1,\n 2,\n 3\n]' } },
355+
{ "Encode empty array",
356+
json.encode, { json.empty_array }, true, { '[]' } },
357+
{ "Encode nested arrays",
358+
json.encode, { { { 1, 2 }, { 3, 4 } } },
359+
true, { '[\n [\n 1,\n 2\n ],\n [\n 3,\n 4\n ]\n]' } },
360+
{ "Encode array of objects",
361+
json.encode, { { { a = "a" }, { b = "b" } } },
362+
true, { '[\n {\n "a": "a"\n },\n {\n "b": "b"\n }\n]' } },
363+
364+
-- Invalid values
365+
{ 'Set encode_indent("abc")',
366+
json.encode_indent, { "abc" }, false,
367+
{
368+
util.one_of {
369+
"bad argument #1 to '?' (indent must contain only spaces, tabs, line feeds, or carriage returns)",
370+
"bad argument #1 to 'cjson.encode_indent' (indent must contain only spaces, tabs, line feeds, or carriage returns)"
371+
}
372+
}
373+
},
374+
{ 'Set encode_indent("")',
375+
json.encode_indent, { "" }, true, { "" } },
376+
{ "Encode array of objects with empty indent",
377+
json.encode, { { { a = "a" }, { b = "b" } } },
378+
true, { '[{"a":"a"},{"b":"b"}]' } },
379+
336380
-- Test locale support
337381
--
338382
-- The standard Lua interpreter is ANSI C online doesn't support locales

0 commit comments

Comments
 (0)