9292#define DEFAULT_ENCODE_ESCAPE_FORWARD_SLASH 1
9393#define DEFAULT_ENCODE_SKIP_UNSUPPORTED_VALUE_TYPES 0
9494#define DEFAULT_ENCODE_INDENT NULL
95+ #define DEFAULT_ENCODE_SORT_KEYS 0
9596
9697#ifdef DISABLE_INVALID_NUMBERS
9798#undef DEFAULT_DECODE_INVALID_NUMBERS
@@ -156,6 +157,32 @@ static const char *json_token_type_name[] = {
156157 NULL
157158};
158159
160+ typedef struct {
161+ strbuf_t * buf ;
162+ size_t offset ;
163+ size_t length ;
164+ int raw_typ ;
165+ union {
166+ lua_Number number ;
167+ const char * string ;
168+ } raw ;
169+ } key_entry_t ;
170+
171+ /* Stores all keys for a table when key sorting is enabled.
172+ * - buf: buffer holding serialized key strings
173+ * - keys: array of key_entry_t pointing into buf
174+ * - size: number of keys stored
175+ * - capacity: allocated capacity of keys array
176+ */
177+ typedef struct {
178+ strbuf_t buf ;
179+ key_entry_t * keys ;
180+ size_t size ;
181+ size_t capacity ;
182+ } keybuf_t ;
183+
184+ #define KEYBUF_DEFAULT_CAPACITY 32
185+
159186typedef struct {
160187 json_token_type_t ch2token [256 ];
161188 char escape2char [256 ]; /* Decoding */
@@ -164,6 +191,10 @@ typedef struct {
164191 * encode_keep_buffer is set */
165192 strbuf_t encode_buf ;
166193
194+ /* encode_keybuf is only allocated and used when
195+ * sort_keys is set */
196+ keybuf_t encode_keybuf ;
197+
167198 int encode_sparse_convert ;
168199 int encode_sparse_ratio ;
169200 int encode_sparse_safe ;
@@ -174,6 +205,7 @@ typedef struct {
174205 int encode_empty_table_as_object ;
175206 int encode_escape_forward_slash ;
176207 const char * encode_indent ;
208+ int encode_sort_keys ;
177209
178210 int decode_invalid_numbers ;
179211 int decode_max_depth ;
@@ -475,6 +507,15 @@ static int json_cfg_encode_escape_forward_slash(lua_State *l)
475507 return ret ;
476508}
477509
510+ static int json_cfg_encode_sort_keys (lua_State * l )
511+ {
512+ json_config_t * cfg = json_arg_init (l , 1 );
513+
514+ json_enum_option (l , 1 , & cfg -> encode_sort_keys , NULL , 1 );
515+
516+ return 1 ;
517+ }
518+
478519static int json_destroy_config (lua_State * l )
479520{
480521 json_config_t * cfg ;
@@ -518,6 +559,7 @@ static void json_create_config(lua_State *l)
518559 cfg -> encode_escape_forward_slash = DEFAULT_ENCODE_ESCAPE_FORWARD_SLASH ;
519560 cfg -> encode_skip_unsupported_value_types = DEFAULT_ENCODE_SKIP_UNSUPPORTED_VALUE_TYPES ;
520561 cfg -> encode_indent = DEFAULT_ENCODE_INDENT ;
562+ cfg -> encode_sort_keys = DEFAULT_ENCODE_SORT_KEYS ;
521563
522564#if DEFAULT_ENCODE_KEEP_BUFFER > 0
523565 strbuf_init (& cfg -> encode_buf , 0 );
@@ -576,17 +618,17 @@ static void json_encode_exception(lua_State *l, json_config_t *cfg, strbuf_t *js
576618{
577619 if (!cfg -> encode_keep_buffer )
578620 strbuf_free (json );
621+
622+ if (cfg -> encode_sort_keys ) {
623+ strbuf_free (& cfg -> encode_keybuf .buf );
624+ free (cfg -> encode_keybuf .keys );
625+ }
626+
579627 luaL_error (l , "Cannot serialise %s: %s" ,
580628 lua_typename (l , lua_type (l , lindex )), reason );
581629}
582630
583- /* json_append_string args:
584- * - lua_State
585- * - JSON strbuf
586- * - String (Lua stack index)
587- *
588- * Returns nothing. Doesn't remove string from Lua stack */
589- static void json_append_string (lua_State * l , strbuf_t * json , int lindex )
631+ static void json_append_string_contents (lua_State * l , strbuf_t * json , int lindex )
590632{
591633 const char * escstr ;
592634 const char * str ;
@@ -599,19 +641,30 @@ static void json_append_string(lua_State *l, strbuf_t *json, int lindex)
599641 * This buffer is reused constantly for small strings
600642 * If there are any excess pages, they won't be hit anyway.
601643 * This gains ~5% speedup. */
602- if (len > SIZE_MAX / 6 - 3 )
644+ if (len >= SIZE_MAX / 6 )
603645 abort (); /* Overflow check */
604- strbuf_ensure_empty_length (json , len * 6 + 2 );
646+ strbuf_ensure_empty_length (json , len * 6 );
605647
606- strbuf_append_char_unsafe (json , '\"' );
607648 for (i = 0 ; i < len ; i ++ ) {
608649 escstr = char2escape [(unsigned char )str [i ]];
609650 if (escstr )
610651 strbuf_append_string (json , escstr );
611652 else
612653 strbuf_append_char_unsafe (json , str [i ]);
613654 }
614- strbuf_append_char_unsafe (json , '\"' );
655+ }
656+
657+ /* json_append_string args:
658+ * - lua_State
659+ * - JSON strbuf
660+ * - String (Lua stack index)
661+ *
662+ * Returns nothing. Doesn't remove string from Lua stack */
663+ static void json_append_string (lua_State * l , strbuf_t * json , int lindex )
664+ {
665+ strbuf_append_char (json , '\"' );
666+ json_append_string_contents (l , json , lindex );
667+ strbuf_append_char (json , '\"' );
615668}
616669
617670/* Find the size of the array on the top of the Lua stack
@@ -791,6 +844,17 @@ static void json_append_number(lua_State *l, json_config_t *cfg,
791844 strbuf_extend_length (json , len );
792845}
793846
847+ /* Compare key_entry_t for qsort. */
848+ static int cmp_key_entries (const void * a , const void * b ) {
849+ const key_entry_t * ka = a ;
850+ const key_entry_t * kb = b ;
851+ const size_t min_length = ka -> length < kb -> length ? ka -> length : kb -> length ;
852+ int res = memcmp (ka -> buf -> buf + ka -> offset ,
853+ kb -> buf -> buf + kb -> offset ,
854+ min_length );
855+ return res ? res : (ka -> length - kb -> length );
856+ }
857+
794858static void json_append_object (lua_State * l , json_config_t * cfg ,
795859 int current_depth , strbuf_t * json )
796860{
@@ -800,48 +864,132 @@ static void json_append_object(lua_State *l, json_config_t *cfg,
800864 /* Object */
801865 strbuf_append_char (json , '{' );
802866
803- lua_pushnil (l );
804867 /* table, startkey */
805868 comma = 0 ;
806- while (lua_next (l , -2 ) != 0 ) {
807- has_items = 1 ;
869+ lua_pushnil (l );
870+ if (cfg -> encode_sort_keys ) {
871+ keybuf_t * keybuf = & cfg -> encode_keybuf ;
872+ size_t init_keybuf_size = keybuf -> size ;
873+ size_t init_keybuf_length = strbuf_length (& keybuf -> buf );
874+
875+ while (lua_next (l , -2 ) != 0 ) {
876+ has_items = 1 ;
877+ if (keybuf -> size == keybuf -> capacity ) {
878+ if (!keybuf -> capacity ) {
879+ keybuf -> capacity = KEYBUF_DEFAULT_CAPACITY ;
880+ keybuf -> keys = malloc (keybuf -> capacity * sizeof (key_entry_t ));
881+ if (!keybuf -> keys )
882+ json_encode_exception (l , cfg , json , -1 , "out of memory" );
883+ } else {
884+ keybuf -> capacity *= 2 ;
885+ key_entry_t * tmp = realloc (keybuf -> keys ,
886+ keybuf -> capacity * sizeof (key_entry_t ));
887+ if (!tmp )
888+ json_encode_exception (l , cfg , json , -1 , "out of memory" );
889+ keybuf -> keys = tmp ;
890+ }
891+ }
808892
809- json_pos = strbuf_length (json );
810- if (comma ++ > 0 )
811- strbuf_append_char (json , ',' );
893+ keytype = lua_type (l , -2 );
894+ key_entry_t key_entry = {
895+ .buf = & keybuf -> buf ,
896+ .offset = strbuf_length (& keybuf -> buf ),
897+ .raw_typ = keytype ,
898+ };
899+ if (keytype == LUA_TSTRING ) {
900+ json_append_string_contents (l , & keybuf -> buf , -2 );
901+ key_entry .raw .string = lua_tostring (l , -2 );
902+ } else if (keytype == LUA_TNUMBER ) {
903+ json_append_number (l , cfg , & keybuf -> buf , -2 );
904+ key_entry .raw .number = lua_tointeger (l , -2 );
905+ } else {
906+ json_encode_exception (l , cfg , json , -2 ,
907+ "table key must be number or string" );
908+ }
909+ key_entry .length = strbuf_length (& keybuf -> buf ) - key_entry .offset ;
910+ keybuf -> keys [keybuf -> size ++ ] = key_entry ;
911+ lua_pop (l , 1 );
912+ }
812913
813- if (cfg -> encode_indent )
814- json_append_newline_and_indent (json , cfg , current_depth );
914+ size_t keys_count = keybuf -> size - init_keybuf_size ;
915+ qsort (keybuf -> keys + init_keybuf_size , keys_count ,
916+ sizeof (key_entry_t ), cmp_key_entries );
815917
816- /* table, key, value */
817- keytype = lua_type (l , -2 );
818- if (keytype == LUA_TNUMBER ) {
819- strbuf_append_char (json , '"' );
820- json_append_number (l , cfg , json , -2 );
821- strbuf_append_mem (json , "\":" , 2 );
822- } else if (keytype == LUA_TSTRING ) {
823- json_append_string (l , json , -2 );
824- strbuf_append_char (json , ':' );
825- } else {
826- json_encode_exception (l , cfg , json , -2 ,
827- "table key must be a number or string" );
828- /* never returns */
829- }
830- if (cfg -> encode_indent )
831- strbuf_append_char (json , ' ' );
918+ for (size_t i = init_keybuf_size ; i < init_keybuf_size + keys_count ; i ++ ) {
919+ key_entry_t * current_key = & keybuf -> keys [i ];
920+ json_pos = strbuf_length (json );
921+ if (comma ++ > 0 )
922+ strbuf_append_char (json , ',' );
832923
924+ if (cfg -> encode_indent )
925+ json_append_newline_and_indent (json , cfg , current_depth );
833926
834- /* table, key, value */
835- err = json_append_data (l , cfg , current_depth , json );
836- if (err ) {
837- strbuf_set_length (json , json_pos );
838- if (comma == 1 ) {
839- comma = 0 ;
927+ strbuf_ensure_empty_length (json , current_key -> length + 3 );
928+ strbuf_append_char_unsafe (json , '"' );
929+ strbuf_append_mem_unsafe (json , keybuf -> buf .buf + current_key -> offset ,
930+ current_key -> length );
931+ strbuf_append_mem_unsafe (json , "\":" , 2 );
932+
933+ if (cfg -> encode_indent )
934+ strbuf_append_char (json , ' ' );
935+
936+ if (current_key -> raw_typ == LUA_TSTRING )
937+ lua_pushstring (l , current_key -> raw .string );
938+ else
939+ lua_pushnumber (l , current_key -> raw .number );
940+
941+ lua_gettable (l , -2 );
942+ err = json_append_data (l , cfg , current_depth , json );
943+ if (err ) {
944+ strbuf_set_length (json , json_pos );
945+ if (comma == 1 )
946+ comma = 0 ;
840947 }
948+ lua_pop (l , 1 );
841949 }
950+ /* resize encode_keybuf to reuse allocated memory for forward keys */
951+ strbuf_set_length (& keybuf -> buf , init_keybuf_length );
952+ keybuf -> size = init_keybuf_size ;
953+ } else {
954+ while (lua_next (l , -2 ) != 0 ) {
955+ has_items = 1 ;
956+
957+ json_pos = strbuf_length (json );
958+ if (comma ++ > 0 )
959+ strbuf_append_char (json , ',' );
960+
961+ if (cfg -> encode_indent )
962+ json_append_newline_and_indent (json , cfg , current_depth );
963+
964+ /* table, key, value */
965+ keytype = lua_type (l , -2 );
966+ if (keytype == LUA_TNUMBER ) {
967+ strbuf_append_char (json , '"' );
968+ json_append_number (l , cfg , json , -2 );
969+ strbuf_append_mem (json , "\":" , 2 );
970+ } else if (keytype == LUA_TSTRING ) {
971+ json_append_string (l , json , -2 );
972+ strbuf_append_char (json , ':' );
973+ } else {
974+ json_encode_exception (l , cfg , json , -2 ,
975+ "table key must be a number or string" );
976+ /* never returns */
977+ }
978+ if (cfg -> encode_indent )
979+ strbuf_append_char (json , ' ' );
980+
981+ /* table, key, value */
982+ err = json_append_data (l , cfg , current_depth , json );
983+ if (err ) {
984+ strbuf_set_length (json , json_pos );
985+ if (comma == 1 ) {
986+ comma = 0 ;
987+ }
988+ }
842989
843- lua_pop (l , 1 );
844- /* table, key */
990+ lua_pop (l , 1 );
991+ /* table, key */
992+ }
845993 }
846994
847995 if (has_items && cfg -> encode_indent )
@@ -969,6 +1117,12 @@ static int json_encode(lua_State *l)
9691117 strbuf_reset (encode_buf );
9701118 }
9711119
1120+ if (cfg -> encode_sort_keys ) {
1121+ strbuf_init (& cfg -> encode_keybuf .buf , 0 );
1122+ cfg -> encode_keybuf .size = 0 ;
1123+ cfg -> encode_keybuf .capacity = 0 ;
1124+ }
1125+
9721126 json_append_data (l , cfg , 0 , encode_buf );
9731127 json = strbuf_string (encode_buf , & len );
9741128
@@ -977,6 +1131,11 @@ static int json_encode(lua_State *l)
9771131 if (!cfg -> encode_keep_buffer )
9781132 strbuf_free (encode_buf );
9791133
1134+ if (cfg -> encode_sort_keys ) {
1135+ strbuf_free (& cfg -> encode_keybuf .buf );
1136+ free (cfg -> encode_keybuf .keys );
1137+ }
1138+
9801139 return 1 ;
9811140}
9821141
@@ -1635,6 +1794,7 @@ static int lua_cjson_new(lua_State *l)
16351794 { "encode_escape_forward_slash" , json_cfg_encode_escape_forward_slash },
16361795 { "encode_skip_unsupported_value_types" , json_cfg_encode_skip_unsupported_value_types },
16371796 { "encode_indent" , json_cfg_encode_indent },
1797+ { "encode_sort_keys" , json_cfg_encode_sort_keys },
16381798 { "new" , lua_cjson_new },
16391799 { NULL , NULL }
16401800 };
0 commit comments