8787#define DEFAULT_DECODE_ARRAY_WITH_ARRAY_MT 0
8888#define DEFAULT_ENCODE_ESCAPE_FORWARD_SLASH 1
8989#define DEFAULT_ENCODE_SKIP_UNSUPPORTED_VALUE_TYPES 0
90+ #define DEFAULT_ENCODE_PRETTY_ENABLED 0
91+ #define DEFAULT_ENCODE_PRETTY_INDENT " "
92+ #define DEFAULT_ENCODE_PRETTY_ITEM_SEPARATOR "\n"
93+ #define DEFAULT_ENCODE_PRETTY_KEY_SEPARATOR " "
9094
9195#ifdef DISABLE_INVALID_NUMBERS
9296#undef DEFAULT_DECODE_INVALID_NUMBERS
@@ -168,6 +172,10 @@ typedef struct {
168172 int encode_keep_buffer ;
169173 int encode_empty_table_as_object ;
170174 int encode_escape_forward_slash ;
175+ int encode_pretty_enabled ;
176+ const char * encode_pretty_indent ;
177+ const char * encode_pretty_item_separator ;
178+ const char * encode_pretty_key_separator ;
171179
172180 int decode_invalid_numbers ;
173181 int decode_max_depth ;
@@ -177,6 +185,10 @@ typedef struct {
177185
178186typedef struct {
179187 const char * * char2escape [256 ];
188+ int pretty_enabled ;
189+ const char * pretty_indent ;
190+ const char * pretty_item_separator ;
191+ const char * pretty_key_separator ;
180192} json_encode_options_t ;
181193
182194typedef struct {
@@ -330,6 +342,20 @@ static int json_enum_option(lua_State *l, int optindex, int *setting,
330342}
331343*/
332344
345+ /* Process string option for a configuration function */
346+ /*
347+ static int json_string_option(lua_State *l, int optindex, const char **setting)
348+ {
349+ if (!lua_isnil(l, optindex)) {
350+ const char *value = luaL_checkstring(l, optindex);
351+ *setting = value;
352+ }
353+
354+ lua_pushstring(l, *setting ? *setting : "");
355+ return 1;
356+ }
357+ */
358+
333359/* Configures handling of extremely sparse arrays:
334360 * convert: Convert extremely sparse arrays into objects? Otherwise error.
335361 * ratio: 0: always allow sparse; 1: never allow sparse; >1: use ratio
@@ -436,6 +462,75 @@ static int json_cfg_encode_keep_buffer(lua_State *l)
436462}
437463*/
438464
465+ /* Returns nonzero if the string consists only of allowed whitespace characters */
466+ static int is_allowed_ws (const char * s ) {
467+ if (!s ) return 0 ;
468+ for (const unsigned char * p = (const unsigned char * )s ; * p ; p ++ ) {
469+ switch (* p ) {
470+ case 0x20 : /* space */
471+ case 0x09 : /* tab */
472+ case 0x0A : /* newline */
473+ case 0x0D : /* carriage return */
474+ break ;
475+ default :
476+ return 0 ;
477+ }
478+ }
479+ return 1 ;
480+ }
481+
482+ /* Configure pretty encoding */
483+ /*
484+ static int json_cfg_encode_pretty(lua_State *l)
485+ {
486+ json_config_t *cfg = json_arg_init(l, 4);
487+
488+ json_enum_option(l, 1, &cfg->encode_pretty_enabled, NULL, 1);
489+
490+ // Process "indent" option, it can be either a string or a non-negative integer.
491+ int optindex = 2;
492+ if (!lua_isnil(l, optindex)) {
493+ if (lua_isnumber(l, optindex)) {
494+ lua_Integer n = luaL_checkinteger(l, optindex);
495+ luaL_argcheck(l, n >= 0, optindex, "indent must be non-negative");
496+
497+ luaL_argcheck(l, (unsigned long long)n <= SIZE_MAX, optindex, "indent is too large");
498+ size_t indentLen = (size_t)n;
499+
500+ strbuf_t b;
501+ strbuf_init(&b, indentLen);
502+ for (size_t i = 0; i < indentLen; i++)
503+ strbuf_append_char_unsafe(&b, ' ');
504+
505+ cfg->encode_pretty_indent = strbuf_free_to_string(&b, &indentLen);
506+ } else if (lua_isstring(l, optindex)) {
507+ const char *indent = luaL_checkstring(l, optindex);
508+ if (!is_allowed_ws(indent))
509+ luaL_argerror(l, optindex, "indent may only contain space, tab, LF, or CR");
510+ cfg->encode_pretty_indent = indent;
511+ } else {
512+ luaL_argerror(l, optindex, "string or number expected");
513+ }
514+ }
515+ lua_pushstring(l, cfg->encode_pretty_indent ? cfg->encode_pretty_indent : "");
516+
517+
518+ const char *item_separator = cfg->encode_pretty_item_separator;
519+ json_string_option(l, 3, &item_separator);
520+ if (!is_allowed_ws(item_separator))
521+ luaL_argerror(l, optindex, "item_separator may only contain space, tab, LF, or CR");
522+ cfg->encode_pretty_item_separator = item_separator;
523+
524+ const char *key_separator = cfg->encode_pretty_key_separator;
525+ json_string_option(l, 4, &key_separator);
526+ if (!is_allowed_ws(key_separator))
527+ luaL_argerror(l, optindex, "key_separator may only contain space, tab, LF, or CR");
528+ cfg->encode_pretty_key_separator = key_separator;
529+
530+ return 4;
531+ }
532+ */
533+
439534#if defined(DISABLE_INVALID_NUMBERS ) && !defined(USE_INTERNAL_FPCONV )
440535void json_verify_invalid_number_setting (lua_State * l , int * setting )
441536{
@@ -533,6 +628,10 @@ static void json_create_config(lua_State *l)
533628 cfg -> decode_array_with_array_mt = DEFAULT_DECODE_ARRAY_WITH_ARRAY_MT ;
534629 cfg -> encode_escape_forward_slash = DEFAULT_ENCODE_ESCAPE_FORWARD_SLASH ;
535630 cfg -> encode_skip_unsupported_value_types = DEFAULT_ENCODE_SKIP_UNSUPPORTED_VALUE_TYPES ;
631+ cfg -> encode_pretty_enabled = DEFAULT_ENCODE_PRETTY_ENABLED ;
632+ cfg -> encode_pretty_indent = DEFAULT_ENCODE_PRETTY_INDENT ;
633+ cfg -> encode_pretty_item_separator = DEFAULT_ENCODE_PRETTY_ITEM_SEPARATOR ;
634+ cfg -> encode_pretty_key_separator = DEFAULT_ENCODE_PRETTY_KEY_SEPARATOR ;
536635
537636#if DEFAULT_ENCODE_KEEP_BUFFER > 0
538637 strbuf_init (& cfg -> encode_buf , 0 );
@@ -704,6 +803,13 @@ static void json_check_encode_depth(lua_State *l, json_config_t *cfg,
704803static int json_append_data (lua_State * l , json_encode_t * cfg ,
705804 int current_depth );
706805
806+ static void json_append_item_separator_and_indent (strbuf_t * json , json_encode_t * ctx , int depth )
807+ {
808+ strbuf_append_string (json , ctx -> options -> pretty_item_separator );
809+ for (int i = 0 ; i < depth ; i ++ )
810+ strbuf_append_string (json , ctx -> options -> pretty_indent );
811+ }
812+
707813/* json_append_array args:
708814 * - lua_State
709815 * - JSON strbuf
@@ -712,15 +818,22 @@ static void json_append_array(lua_State *l, json_encode_t *ctx, int current_dept
712818 int array_length , int raw )
713819{
714820 int comma , i , json_pos , err ;
821+ int has_items = 0 ;
715822 strbuf_t * json = ctx -> json ;
716823
717824 strbuf_append_char (json , '[' );
718825
719826 comma = 0 ;
720827 for (i = 1 ; i <= array_length ; i ++ ) {
828+ has_items = 1 ;
829+
721830 json_pos = strbuf_length (json );
722831 if (comma ++ > 0 )
723832 strbuf_append_char (json , ',' );
833+
834+ if (ctx -> options -> pretty_enabled )
835+ json_append_item_separator_and_indent (json , ctx , current_depth );
836+
724837 if (raw ) {
725838 lua_rawgeti (l , -1 , i );
726839 } else {
@@ -742,6 +855,9 @@ static void json_append_array(lua_State *l, json_encode_t *ctx, int current_dept
742855 lua_pop (l , 1 );
743856 }
744857
858+ if (has_items && ctx -> options -> pretty_enabled )
859+ json_append_item_separator_and_indent (json , ctx , current_depth - 1 );
860+
745861 strbuf_append_char (json , ']' );
746862}
747863
@@ -798,6 +914,7 @@ static void json_append_object(lua_State *l, json_encode_t *ctx,
798914 int current_depth )
799915{
800916 int comma , keytype , json_pos , err ;
917+ int has_items = 0 ;
801918 strbuf_t * json = ctx -> json ;
802919
803920 /* Object */
@@ -807,12 +924,17 @@ static void json_append_object(lua_State *l, json_encode_t *ctx,
807924 /* table, startkey */
808925 comma = 0 ;
809926 while (lua_next (l , -2 ) != 0 ) {
927+ has_items = 1 ;
928+
810929 json_pos = strbuf_length (json );
811930 if (comma ++ > 0 )
812931 strbuf_append_char (json , ',' );
813932 else
814933 comma = 1 ;
815934
935+ if (ctx -> options -> pretty_enabled )
936+ json_append_item_separator_and_indent (json , ctx , current_depth );
937+
816938 /* table, key, value */
817939 keytype = lua_type (l , -2 );
818940 if (keytype == LUA_TNUMBER ) {
@@ -827,6 +949,8 @@ static void json_append_object(lua_State *l, json_encode_t *ctx,
827949 "table key must be a number or string" );
828950 /* never returns */
829951 }
952+ if (ctx -> options -> pretty_enabled )
953+ strbuf_append_string (json , ctx -> options -> pretty_key_separator );
830954
831955 /* table, key, value */
832956 err = json_append_data (l , ctx , current_depth );
@@ -841,6 +965,9 @@ static void json_append_object(lua_State *l, json_encode_t *ctx,
841965 /* table, key */
842966 }
843967
968+ if (has_items && ctx -> options -> pretty_enabled )
969+ json_append_item_separator_and_indent (json , ctx , current_depth - 1 );
970+
844971 strbuf_append_char (json , '}' );
845972}
846973
@@ -966,7 +1093,13 @@ static int json_append_data(lua_State *l, json_encode_t *ctx,
9661093static int json_encode (lua_State * l )
9671094{
9681095 json_config_t * cfg = json_fetch_config (l );
969- json_encode_options_t options = { .char2escape = { char2escape } };
1096+ json_encode_options_t options = {
1097+ .char2escape = { char2escape },
1098+ .pretty_enabled = DEFAULT_ENCODE_PRETTY_ENABLED ,
1099+ .pretty_indent = DEFAULT_ENCODE_PRETTY_INDENT ,
1100+ .pretty_item_separator = DEFAULT_ENCODE_PRETTY_ITEM_SEPARATOR ,
1101+ .pretty_key_separator = DEFAULT_ENCODE_PRETTY_KEY_SEPARATOR ,
1102+ };
9701103 json_encode_t ctx = { .options = & options , .cfg = cfg };
9711104 strbuf_t local_encode_buf ;
9721105 strbuf_t * encode_buf ;
@@ -979,26 +1112,69 @@ static int json_encode(lua_State *l)
9791112 break ;
9801113 case 2 :
9811114 luaL_checktype (l , 2 , LUA_TTABLE );
982- lua_getfield (l , 2 , "escape_slash" );
9831115
984- /* We only handle the escape_slash option for now */
985- if (lua_isnil (l , -1 )) {
986- lua_pop (l , 2 );
987- break ;
1116+ lua_getfield (l , 2 , "escape_slash" );
1117+ if (!lua_isnil (l , -1 )) {
1118+ luaL_checktype (l , -1 , LUA_TBOOLEAN );
1119+
1120+ int escape_slash = lua_toboolean (l , -1 );
1121+ if (escape_slash ) {
1122+ /* This can be optimised by adding a new hard-coded escape table for this case,
1123+ * but this path will rarely if ever be used, so let's just memcpy. */
1124+ memcpy (customChar2escape , char2escape , sizeof (char2escape ));
1125+ customChar2escape ['/' ] = "\\/" ;
1126+ * ctx .options -> char2escape = customChar2escape ;
1127+ }
9881128 }
1129+ lua_pop (l , 1 );
9891130
990- luaL_checktype (l , -1 , LUA_TBOOLEAN );
1131+ lua_getfield (l , 2 , "pretty" );
1132+ if (!lua_isnil (l , -1 )) {
1133+ if (lua_isboolean (l , -1 )) {
1134+ options .pretty_enabled = lua_toboolean (l , -1 );
1135+ } else {
1136+ luaL_checktype (l , -1 , LUA_TTABLE );
1137+ /* enable pretty-formatting if table provided */
1138+ options .pretty_enabled = 1 ;
1139+
1140+ lua_getfield (l , -1 , "indent" );
1141+ if (lua_isnumber (l , -1 )) {
1142+ lua_Integer n = luaL_checkinteger (l , -1 );
1143+ luaL_argcheck (l , n >= 0 , 2 , "indent must be non-negative" );
1144+
1145+ luaL_argcheck (l , (unsigned long long )n <= SIZE_MAX , 2 , "indent too large" );
1146+ size_t indent_len = (size_t )n ;
1147+
1148+ strbuf_t b ;
1149+ strbuf_init (& b , indent_len );
1150+ for (size_t i = 0 ; i < indent_len ; i ++ )
1151+ strbuf_append_char_unsafe (& b , ' ' );
1152+
1153+ options .pretty_indent = strbuf_free_to_string (& b , & indent_len );
1154+ } else if (lua_isstring (l , -1 )) {
1155+ options .pretty_indent = lua_tostring (l , -1 );
1156+ if (!is_allowed_ws (options .pretty_indent ))
1157+ luaL_error (l , "indent may only contain space, tab, LF, or CR" );
1158+ }
1159+ lua_pop (l , 1 );
9911160
992- int escape_slash = lua_toboolean (l , -1 );
1161+ lua_getfield (l , -1 , "item_separator" );
1162+ if (lua_isstring (l , -1 )) {
1163+ options .pretty_item_separator = lua_tostring (l , -1 );
1164+ if (!is_allowed_ws (options .pretty_item_separator ))
1165+ luaL_error (l , "item_separator may only contain space, tab, LF, or CR" );
1166+ }
1167+ lua_pop (l , 1 );
9931168
994- if (escape_slash ) {
995- /* This can be optimised by adding a new hard-coded escape table for this case,
996- * but this path will rarely if ever be used, so let's just memcpy.*/
997- memcpy (customChar2escape , char2escape , sizeof (char2escape ));
998- customChar2escape ['/' ] = "\\/" ;
999- * ctx .options -> char2escape = customChar2escape ;
1169+ lua_getfield (l , -1 , "key_separator" );
1170+ if (lua_isstring (l , -1 )) {
1171+ options .pretty_key_separator = lua_tostring (l , -1 );
1172+ if (!is_allowed_ws (options .pretty_key_separator ))
1173+ luaL_error (l , "key_separator may only contain space, tab, LF, or CR" );
1174+ }
1175+ lua_pop (l , 1 );
1176+ }
10001177 }
1001-
10021178 lua_pop (l , 2 );
10031179 break ;
10041180 default :
@@ -1710,6 +1886,7 @@ int lua_cjson_new(lua_State *l)
17101886 { "decode_invalid_numbers", json_cfg_decode_invalid_numbers },
17111887 { "encode_escape_forward_slash", json_cfg_encode_escape_forward_slash },
17121888 { "encode_skip_unsupported_value_types", json_cfg_encode_skip_unsupported_value_types },
1889+ { "encode_pretty", json_cfg_encode_pretty },
17131890 */
17141891 { "new" , lua_cjson_new },
17151892 { NULL , NULL }
0 commit comments