9393#define DEFAULT_ENCODE_ESCAPE_FORWARD_SLASH 1
9494#define DEFAULT_ENCODE_SKIP_UNSUPPORTED_VALUE_TYPES 0
9595#define DEFAULT_ENCODE_INDENT NULL
96+ #define DEFAULT_ENCODE_SORT_KEYS 0
9697
9798#ifdef DISABLE_INVALID_NUMBERS
9899#undef DEFAULT_DECODE_INVALID_NUMBERS
@@ -157,6 +158,32 @@ static const char *json_token_type_name[] = {
157158 NULL
158159};
159160
161+ typedef struct {
162+ strbuf_t * buf ;
163+ size_t offset ;
164+ ssize_t length ;
165+ int raw_typ ;
166+ union {
167+ lua_Number number ;
168+ const char * string ;
169+ } raw ;
170+ } key_entry_t ;
171+
172+ /* Stores all keys for a table when key sorting is enabled.
173+ * - buf: buffer holding serialized key strings
174+ * - keys: array of key_entry_t pointing into buf
175+ * - size: number of keys stored
176+ * - capacity: allocated capacity of keys array
177+ */
178+ typedef struct {
179+ strbuf_t buf ;
180+ key_entry_t * keys ;
181+ size_t size ;
182+ size_t capacity ;
183+ } keybuf_t ;
184+
185+ #define KEYBUF_DEFAULT_CAPACITY 32
186+
160187typedef struct {
161188 json_token_type_t ch2token [256 ];
162189 char escape2char [256 ]; /* Decoding */
@@ -165,6 +192,10 @@ typedef struct {
165192 * encode_keep_buffer is set */
166193 strbuf_t encode_buf ;
167194
195+ /* encode_keybuf is only allocated and used when
196+ * sort_keys is set */
197+ keybuf_t encode_keybuf ;
198+
168199 int encode_sparse_convert ;
169200 int encode_sparse_ratio ;
170201 int encode_sparse_safe ;
@@ -175,6 +206,7 @@ typedef struct {
175206 int encode_empty_table_as_object ;
176207 int encode_escape_forward_slash ;
177208 const char * encode_indent ;
209+ int encode_sort_keys ;
178210
179211 int decode_invalid_numbers ;
180212 int decode_max_depth ;
@@ -487,6 +519,31 @@ static int json_cfg_encode_escape_forward_slash(lua_State *l)
487519 return ret ;
488520}
489521
522+ /* Configures JSON encoding keys sorting */
523+ static int json_cfg_encode_sort_keys (lua_State * l )
524+ {
525+ json_config_t * cfg = json_arg_init (l , 1 );
526+ int old_value ;
527+
528+ old_value = cfg -> encode_sort_keys ;
529+
530+ json_enum_option (l , 1 , & cfg -> encode_sort_keys , NULL , 1 );
531+
532+ /* Init / free the keybuf if the setting has changed */
533+ if (old_value ^ cfg -> encode_sort_keys ) {
534+ if (cfg -> encode_sort_keys ) {
535+ strbuf_init (& cfg -> encode_keybuf .buf , 0 );
536+ cfg -> encode_keybuf .size = 0 ;
537+ cfg -> encode_keybuf .capacity = 0 ;
538+ } else {
539+ strbuf_free (& cfg -> encode_keybuf .buf );
540+ free (cfg -> encode_keybuf .keys );
541+ }
542+ }
543+
544+ return 1 ;
545+ }
546+
490547static int json_destroy_config (lua_State * l )
491548{
492549 json_config_t * cfg ;
@@ -531,6 +588,7 @@ static void json_create_config(lua_State *l)
531588 cfg -> encode_escape_forward_slash = DEFAULT_ENCODE_ESCAPE_FORWARD_SLASH ;
532589 cfg -> encode_skip_unsupported_value_types = DEFAULT_ENCODE_SKIP_UNSUPPORTED_VALUE_TYPES ;
533590 cfg -> encode_indent = DEFAULT_ENCODE_INDENT ;
591+ cfg -> encode_sort_keys = DEFAULT_ENCODE_SORT_KEYS ;
534592
535593#if DEFAULT_ENCODE_KEEP_BUFFER > 0
536594 strbuf_init (& cfg -> encode_buf , 0 );
@@ -589,17 +647,12 @@ static void json_encode_exception(lua_State *l, json_config_t *cfg, strbuf_t *js
589647{
590648 if (!cfg -> encode_keep_buffer )
591649 strbuf_free (json );
650+
592651 luaL_error (l , "Cannot serialise %s: %s" ,
593652 lua_typename (l , lua_type (l , lindex )), reason );
594653}
595654
596- /* json_append_string args:
597- * - lua_State
598- * - JSON strbuf
599- * - String (Lua stack index)
600- *
601- * Returns nothing. Doesn't remove string from Lua stack */
602- static void json_append_string (lua_State * l , strbuf_t * json , int lindex )
655+ static void json_append_string_contents (lua_State * l , strbuf_t * json , int lindex )
603656{
604657 const char * escstr ;
605658 const char * str ;
@@ -612,19 +665,30 @@ static void json_append_string(lua_State *l, strbuf_t *json, int lindex)
612665 * This buffer is reused constantly for small strings
613666 * If there are any excess pages, they won't be hit anyway.
614667 * This gains ~5% speedup. */
615- if (len > SIZE_MAX / 6 - 3 )
668+ if (len >= SIZE_MAX / 6 )
616669 abort (); /* Overflow check */
617- strbuf_ensure_empty_length (json , len * 6 + 2 );
670+ strbuf_ensure_empty_length (json , len * 6 );
618671
619- strbuf_append_char_unsafe (json , '\"' );
620672 for (i = 0 ; i < len ; i ++ ) {
621673 escstr = char2escape [(unsigned char )str [i ]];
622674 if (escstr )
623675 strbuf_append_string (json , escstr );
624676 else
625677 strbuf_append_char_unsafe (json , str [i ]);
626678 }
627- strbuf_append_char_unsafe (json , '\"' );
679+ }
680+
681+ /* json_append_string args:
682+ * - lua_State
683+ * - JSON strbuf
684+ * - String (Lua stack index)
685+ *
686+ * Returns nothing. Doesn't remove string from Lua stack */
687+ static void json_append_string (lua_State * l , strbuf_t * json , int lindex )
688+ {
689+ strbuf_append_char (json , '\"' );
690+ json_append_string_contents (l , json , lindex );
691+ strbuf_append_char (json , '\"' );
628692}
629693
630694/* Find the size of the array on the top of the Lua stack
@@ -804,6 +868,17 @@ static void json_append_number(lua_State *l, json_config_t *cfg,
804868 strbuf_extend_length (json , len );
805869}
806870
871+ /* Compare key_entry_t for qsort. */
872+ static int cmp_key_entries (const void * a , const void * b ) {
873+ const key_entry_t * ka = a ;
874+ const key_entry_t * kb = b ;
875+ const size_t min_length = ka -> length < kb -> length ? ka -> length : kb -> length ;
876+ int res = memcmp (ka -> buf -> buf + ka -> offset ,
877+ kb -> buf -> buf + kb -> offset ,
878+ min_length );
879+ return res ? res : (ka -> length - kb -> length );
880+ }
881+
807882static void json_append_object (lua_State * l , json_config_t * cfg ,
808883 int current_depth , strbuf_t * json )
809884{
@@ -813,48 +888,133 @@ static void json_append_object(lua_State *l, json_config_t *cfg,
813888 /* Object */
814889 strbuf_append_char (json , '{' );
815890
816- lua_pushnil (l );
817891 /* table, startkey */
818892 comma = 0 ;
819- while (lua_next (l , -2 ) != 0 ) {
820- has_items = 1 ;
893+ lua_pushnil (l );
894+ if (cfg -> encode_sort_keys ) {
895+ keybuf_t * keybuf = & cfg -> encode_keybuf ;
896+ size_t init_keybuf_size = keybuf -> size ;
897+ size_t init_keybuf_length = strbuf_length (& keybuf -> buf );
898+
899+ /* Collect keys into keybuf */
900+ while (lua_next (l , -2 ) != 0 ) {
901+ has_items = 1 ;
902+ if (keybuf -> size == keybuf -> capacity ) {
903+ if (!keybuf -> capacity ) {
904+ keybuf -> capacity = KEYBUF_DEFAULT_CAPACITY ;
905+ keybuf -> keys = malloc (keybuf -> capacity * sizeof (key_entry_t ));
906+ if (!keybuf -> keys )
907+ json_encode_exception (l , cfg , json , -1 , "out of memory" );
908+ } else {
909+ keybuf -> capacity *= 2 ;
910+ key_entry_t * tmp = realloc (keybuf -> keys ,
911+ keybuf -> capacity * sizeof (key_entry_t ));
912+ if (!tmp )
913+ json_encode_exception (l , cfg , json , -1 , "out of memory" );
914+ keybuf -> keys = tmp ;
915+ }
916+ }
821917
822- json_pos = strbuf_length (json );
823- if (comma ++ > 0 )
824- strbuf_append_char (json , ',' );
918+ keytype = lua_type (l , -2 );
919+ key_entry_t key_entry = {
920+ .buf = & keybuf -> buf ,
921+ .offset = strbuf_length (& keybuf -> buf ),
922+ .raw_typ = keytype ,
923+ };
924+ if (keytype == LUA_TSTRING ) {
925+ json_append_string_contents (l , & keybuf -> buf , -2 );
926+ key_entry .raw .string = lua_tostring (l , -2 );
927+ } else if (keytype == LUA_TNUMBER ) {
928+ json_append_number (l , cfg , & keybuf -> buf , -2 );
929+ key_entry .raw .number = lua_tointeger (l , -2 );
930+ } else {
931+ json_encode_exception (l , cfg , json , -2 ,
932+ "table key must be number or string" );
933+ }
934+ key_entry .length = strbuf_length (& keybuf -> buf ) - key_entry .offset ;
935+ keybuf -> keys [keybuf -> size ++ ] = key_entry ;
936+ lua_pop (l , 1 );
937+ }
825938
826- if (cfg -> encode_indent )
827- json_append_newline_and_indent (json , cfg , current_depth );
939+ size_t keys_count = keybuf -> size - init_keybuf_size ;
940+ qsort (keybuf -> keys + init_keybuf_size , keys_count ,
941+ sizeof (key_entry_t ), cmp_key_entries );
828942
829- /* table, key, value */
830- keytype = lua_type (l , -2 );
831- if (keytype == LUA_TNUMBER ) {
832- strbuf_append_char (json , '"' );
833- json_append_number (l , cfg , json , -2 );
834- strbuf_append_mem (json , "\":" , 2 );
835- } else if (keytype == LUA_TSTRING ) {
836- json_append_string (l , json , -2 );
837- strbuf_append_char (json , ':' );
838- } else {
839- json_encode_exception (l , cfg , json , -2 ,
840- "table key must be a number or string" );
841- /* never returns */
842- }
843- if (cfg -> encode_indent )
844- strbuf_append_char (json , ' ' );
943+ for (size_t i = init_keybuf_size ; i < init_keybuf_size + keys_count ; i ++ ) {
944+ key_entry_t * current_key = & keybuf -> keys [i ];
945+ json_pos = strbuf_length (json );
946+ if (comma ++ > 0 )
947+ strbuf_append_char (json , ',' );
845948
949+ if (cfg -> encode_indent )
950+ json_append_newline_and_indent (json , cfg , current_depth );
846951
847- /* table, key, value */
848- err = json_append_data (l , cfg , current_depth , json );
849- if (err ) {
850- strbuf_set_length (json , json_pos );
851- if (comma == 1 ) {
852- comma = 0 ;
952+ strbuf_ensure_empty_length (json , current_key -> length + 3 );
953+ strbuf_append_char_unsafe (json , '"' );
954+ strbuf_append_mem_unsafe (json , keybuf -> buf .buf + current_key -> offset ,
955+ current_key -> length );
956+ strbuf_append_mem_unsafe (json , "\":" , 2 );
957+
958+ if (cfg -> encode_indent )
959+ strbuf_append_char (json , ' ' );
960+
961+ if (current_key -> raw_typ == LUA_TSTRING )
962+ lua_pushstring (l , current_key -> raw .string );
963+ else
964+ lua_pushnumber (l , current_key -> raw .number );
965+
966+ lua_gettable (l , -2 );
967+ err = json_append_data (l , cfg , current_depth , json );
968+ if (err ) {
969+ strbuf_set_length (json , json_pos );
970+ if (comma == 1 )
971+ comma = 0 ;
853972 }
973+ lua_pop (l , 1 );
854974 }
975+ /* resize encode_keybuf to reuse allocated memory for forward keys */
976+ strbuf_set_length (& keybuf -> buf , init_keybuf_length );
977+ keybuf -> size = init_keybuf_size ;
978+ } else {
979+ while (lua_next (l , -2 ) != 0 ) {
980+ has_items = 1 ;
981+
982+ json_pos = strbuf_length (json );
983+ if (comma ++ > 0 )
984+ strbuf_append_char (json , ',' );
985+
986+ if (cfg -> encode_indent )
987+ json_append_newline_and_indent (json , cfg , current_depth );
988+
989+ /* table, key, value */
990+ keytype = lua_type (l , -2 );
991+ if (keytype == LUA_TNUMBER ) {
992+ strbuf_append_char (json , '"' );
993+ json_append_number (l , cfg , json , -2 );
994+ strbuf_append_mem (json , "\":" , 2 );
995+ } else if (keytype == LUA_TSTRING ) {
996+ json_append_string (l , json , -2 );
997+ strbuf_append_char (json , ':' );
998+ } else {
999+ json_encode_exception (l , cfg , json , -2 ,
1000+ "table key must be a number or string" );
1001+ /* never returns */
1002+ }
1003+ if (cfg -> encode_indent )
1004+ strbuf_append_char (json , ' ' );
1005+
1006+ /* table, key, value */
1007+ err = json_append_data (l , cfg , current_depth , json );
1008+ if (err ) {
1009+ strbuf_set_length (json , json_pos );
1010+ if (comma == 1 ) {
1011+ comma = 0 ;
1012+ }
1013+ }
8551014
856- lua_pop (l , 1 );
857- /* table, key */
1015+ lua_pop (l , 1 );
1016+ /* table, key */
1017+ }
8581018 }
8591019
8601020 if (has_items && cfg -> encode_indent )
@@ -982,6 +1142,12 @@ static int json_encode(lua_State *l)
9821142 strbuf_reset (encode_buf );
9831143 }
9841144
1145+ if (cfg -> encode_sort_keys ) {
1146+ /* Reuse existing keybuf */
1147+ strbuf_reset (& cfg -> encode_keybuf .buf );
1148+ cfg -> encode_keybuf .size = 0 ;
1149+ }
1150+
9851151 json_append_data (l , cfg , 0 , encode_buf );
9861152 json = strbuf_string (encode_buf , & len );
9871153
@@ -1682,6 +1848,7 @@ static int lua_cjson_new(lua_State *l)
16821848 { "encode_escape_forward_slash" , json_cfg_encode_escape_forward_slash },
16831849 { "encode_skip_unsupported_value_types" , json_cfg_encode_skip_unsupported_value_types },
16841850 { "encode_indent" , json_cfg_encode_indent },
1851+ { "encode_sort_keys" , json_cfg_encode_sort_keys },
16851852 { "new" , lua_cjson_new },
16861853 { NULL , NULL }
16871854 };
0 commit comments