Skip to content

Commit 862b6c5

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

File tree

3 files changed

+143
-0
lines changed

3 files changed

+143
-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: 77 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,39 @@ 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+
403450
#if defined(DISABLE_INVALID_NUMBERS) && !defined(USE_INTERNAL_FPCONV)
404451
void json_verify_invalid_number_setting(lua_State *l, int *setting)
405452
{
@@ -491,6 +538,7 @@ static void json_create_config(lua_State *l)
491538
cfg->decode_array_with_array_mt = DEFAULT_DECODE_ARRAY_WITH_ARRAY_MT;
492539
cfg->encode_escape_forward_slash = DEFAULT_ENCODE_ESCAPE_FORWARD_SLASH;
493540
cfg->encode_skip_unsupported_value_types = DEFAULT_ENCODE_SKIP_UNSUPPORTED_VALUE_TYPES;
541+
cfg->encode_indent = DEFAULT_ENCODE_INDENT;
494542

495543
#if DEFAULT_ENCODE_KEEP_BUFFER > 0
496544
strbuf_init(&cfg->encode_buf, 0);
@@ -660,6 +708,13 @@ static void json_check_encode_depth(lua_State *l, json_config_t *cfg,
660708
static int json_append_data(lua_State *l, json_config_t *cfg,
661709
int current_depth, strbuf_t *json);
662710

711+
static void json_append_newline_and_indent(strbuf_t *json, json_config_t *cfg, int depth)
712+
{
713+
strbuf_append_char(json, '\n');
714+
for (int i = 0; i < depth; i++)
715+
strbuf_append_string(json, cfg->encode_indent);
716+
}
717+
663718
/* json_append_array args:
664719
* - lua_State
665720
* - JSON strbuf
@@ -668,15 +723,21 @@ static void json_append_array(lua_State *l, json_config_t *cfg, int current_dept
668723
strbuf_t *json, int array_length, int raw)
669724
{
670725
int comma, i, json_pos, err;
726+
int has_items = 0;
671727

672728
strbuf_append_char(json, '[');
673729

674730
comma = 0;
675731
for (i = 1; i <= array_length; i++) {
732+
has_items = 1;
733+
676734
json_pos = strbuf_length(json);
677735
if (comma++ > 0)
678736
strbuf_append_char(json, ',');
679737

738+
if (cfg->encode_indent)
739+
json_append_newline_and_indent(json, cfg, current_depth);
740+
680741
if (raw) {
681742
lua_rawgeti(l, -1, i);
682743
} else {
@@ -698,6 +759,9 @@ static void json_append_array(lua_State *l, json_config_t *cfg, int current_dept
698759
lua_pop(l, 1);
699760
}
700761

762+
if (has_items && cfg->encode_indent)
763+
json_append_newline_and_indent(json, cfg, current_depth-1);
764+
701765
strbuf_append_char(json, ']');
702766
}
703767

@@ -752,6 +816,7 @@ static void json_append_object(lua_State *l, json_config_t *cfg,
752816
int current_depth, strbuf_t *json)
753817
{
754818
int comma, keytype, json_pos, err;
819+
int has_items = 0;
755820

756821
/* Object */
757822
strbuf_append_char(json, '{');
@@ -760,10 +825,15 @@ static void json_append_object(lua_State *l, json_config_t *cfg,
760825
/* table, startkey */
761826
comma = 0;
762827
while (lua_next(l, -2) != 0) {
828+
has_items = 1;
829+
763830
json_pos = strbuf_length(json);
764831
if (comma++ > 0)
765832
strbuf_append_char(json, ',');
766833

834+
if (cfg->encode_indent)
835+
json_append_newline_and_indent(json, cfg, current_depth);
836+
767837
/* table, key, value */
768838
keytype = lua_type(l, -2);
769839
if (keytype == LUA_TNUMBER) {
@@ -778,6 +848,9 @@ static void json_append_object(lua_State *l, json_config_t *cfg,
778848
"table key must be a number or string");
779849
/* never returns */
780850
}
851+
if (cfg->encode_indent)
852+
strbuf_append_char(json, ' ');
853+
781854

782855
/* table, key, value */
783856
err = json_append_data(l, cfg, current_depth, json);
@@ -792,6 +865,9 @@ static void json_append_object(lua_State *l, json_config_t *cfg,
792865
/* table, key */
793866
}
794867

868+
if (has_items && cfg->encode_indent)
869+
json_append_newline_and_indent(json, cfg, current_depth-1);
870+
795871
strbuf_append_char(json, '}');
796872
}
797873

@@ -1571,6 +1647,7 @@ static int lua_cjson_new(lua_State *l)
15711647
{ "decode_invalid_numbers", json_cfg_decode_invalid_numbers },
15721648
{ "encode_escape_forward_slash", json_cfg_encode_escape_forward_slash },
15731649
{ "encode_skip_unsupported_value_types", json_cfg_encode_skip_unsupported_value_types },
1650+
{ "encode_indent", json_cfg_encode_indent },
15741651
{ "new", lua_cjson_new },
15751652
{ NULL, NULL }
15761653
};

tests/test.lua

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,48 @@ 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 with indenting",
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 with indenting",
348+
json.encode, { { } }, true, { '{}' } },
349+
{ "Encode nested object with indenting",
350+
json.encode, { { a = { b = 1 } } },
351+
true, { '{\n "a": {\n "b": 1\n }\n}' } },
352+
{ "Encode simple array with indenting",
353+
json.encode, { { 1, 2, 3 } },
354+
true, { '[\n 1,\n 2,\n 3\n]' } },
355+
{ "Encode empty array with indenting",
356+
json.encode, { json.empty_array }, true, { '[]' } },
357+
{ "Encode nested arrays with indenting",
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 with indenting",
361+
json.encode, { { { a = "a" }, { b = "b" } } },
362+
true, { '[\n {\n "a": "a"\n },\n {\n "b": "b"\n }\n]' } },
363+
{ 'Set encode_indent("abc")',
364+
json.encode_indent, { "abc" }, false,
365+
{
366+
util.one_of {
367+
"bad argument #1 to '?' (indent must contain only spaces, tabs, line feeds, or carriage returns)",
368+
"bad argument #1 to 'cjson.encode_indent' (indent must contain only spaces, tabs, line feeds, or carriage returns)"
369+
}
370+
}
371+
},
372+
{ 'Set encode_indent("")',
373+
json.encode_indent, { "" }, true, { "" } },
374+
{ "Encode array of objects with empty indent",
375+
json.encode, { { { a = "a" }, { b = "b" } } },
376+
true, { '[{"a":"a"},{"b":"b"}]' } },
377+
336378
-- Test locale support
337379
--
338380
-- The standard Lua interpreter is ANSI C online doesn't support locales

0 commit comments

Comments
 (0)