@@ -34,13 +34,14 @@ typedef struct JSON_Generator_StateStruct {
3434 bool ascii_only ;
3535 bool script_safe ;
3636 bool strict ;
37+ VALUE sort_keys ;
3738} JSON_Generator_State ;
3839
39- static VALUE mJSON , cState , cFragment , eGeneratorError , eNestingError , Encoding_UTF_8 ;
40+ static VALUE mJSON , cState , cFragment , eGeneratorError , eNestingError , Encoding_UTF_8 , default_sort_keys_proc ;
4041
4142static ID i_to_s , i_to_json , i_new , i_encode ;
4243static VALUE sym_indent , sym_space , sym_space_before , sym_object_nl , sym_array_nl , sym_max_nesting , sym_allow_nan , sym_allow_duplicate_key ,
43- sym_ascii_only , sym_depth , sym_buffer_initial_length , sym_script_safe , sym_escape_slash , sym_strict , sym_as_json ;
44+ sym_ascii_only , sym_depth , sym_buffer_initial_length , sym_script_safe , sym_escape_slash , sym_strict , sym_as_json , sym_sort_keys ;
4445
4546
4647#define GET_STATE_TO (self , state ) \
@@ -709,6 +710,7 @@ static void State_mark(void *ptr)
709710 rb_gc_mark_movable (state -> object_nl );
710711 rb_gc_mark_movable (state -> array_nl );
711712 rb_gc_mark_movable (state -> as_json );
713+ rb_gc_mark_movable (state -> sort_keys );
712714}
713715
714716static void State_compact (void * ptr )
@@ -720,6 +722,7 @@ static void State_compact(void *ptr)
720722 state -> object_nl = rb_gc_location (state -> object_nl );
721723 state -> array_nl = rb_gc_location (state -> array_nl );
722724 state -> as_json = rb_gc_location (state -> as_json );
725+ state -> sort_keys = rb_gc_location (state -> sort_keys );
723726}
724727
725728static size_t State_memsize (const void * ptr )
@@ -769,6 +772,7 @@ static void vstate_spill(struct generate_json_data *data)
769772 RB_OBJ_WRITTEN (vstate , Qundef , state -> object_nl );
770773 RB_OBJ_WRITTEN (vstate , Qundef , state -> array_nl );
771774 RB_OBJ_WRITTEN (vstate , Qundef , state -> as_json );
775+ RB_OBJ_WRITTEN (vstate , Qundef , state -> sort_keys );
772776}
773777
774778static inline VALUE json_call_to_json (struct generate_json_data * data , VALUE obj )
@@ -1050,6 +1054,11 @@ static inline long increase_depth(struct generate_json_data *data)
10501054
10511055static void generate_json_object (FBuffer * buffer , struct generate_json_data * data , VALUE obj )
10521056{
1057+ if (RB_UNLIKELY (data -> state -> sort_keys )) {
1058+ obj = rb_proc_call_with_block (data -> state -> sort_keys , 1 , & obj , Qnil );
1059+ Check_Type (obj , T_HASH );
1060+ }
1061+
10531062 long depth = increase_depth (data );
10541063
10551064 if (RHASH_SIZE (obj ) == 0 ) {
@@ -1376,6 +1385,7 @@ static VALUE cState_init_copy(VALUE obj, VALUE orig)
13761385 RB_OBJ_WRITTEN (obj , Qundef , objState -> object_nl );
13771386 RB_OBJ_WRITTEN (obj , Qundef , objState -> array_nl );
13781387 RB_OBJ_WRITTEN (obj , Qundef , objState -> as_json );
1388+ RB_OBJ_WRITTEN (obj , Qundef , objState -> sort_keys );
13791389
13801390 return obj ;
13811391}
@@ -1722,6 +1732,55 @@ static VALUE cState_ascii_only_set(VALUE self, VALUE enable)
17221732 return Qnil ;
17231733}
17241734
1735+ static VALUE cState_set_default_sort_keys_proc (VALUE self , VALUE proc )
1736+ {
1737+ if (!rb_obj_is_proc (proc )) {
1738+ rb_raise (rb_eTypeError , "sort_key_proc must be a Proc" );
1739+ }
1740+ return default_sort_keys_proc = proc ;
1741+ }
1742+
1743+ static VALUE normalize_sort_keys (VALUE value )
1744+ {
1745+ if (rb_obj_is_proc (value )) {
1746+ return value ;
1747+ } else if (value == Qtrue ) {
1748+ return default_sort_keys_proc ;
1749+ } else if (RTEST (value )) {
1750+ rb_raise (rb_eTypeError , "The `sort_keys` argument must be a boolean or a Proc" );
1751+ } else {
1752+ return Qfalse ;
1753+ }
1754+ }
1755+
1756+ /*
1757+ * call-seq: sort_keys
1758+ *
1759+ * Get the value of sort_keys.
1760+ */
1761+ static VALUE cState_sort_keys_p (VALUE self )
1762+ {
1763+ GET_STATE (self );
1764+ return state -> sort_keys ;
1765+ }
1766+
1767+ /*
1768+ * call-seq: sort_keys=(value)
1769+ *
1770+ * value is a boolean or a proc. If the value is the boolean true, object keys
1771+ * will be sorted lexicographically in ascending order.
1772+ *
1773+ * If the value is a proc, it receives the entire Hash and must return a Hash
1774+ * with its pairs in the desired order, allowing for arbitrary sorting.
1775+ */
1776+ static VALUE cState_sort_keys_set (VALUE self , VALUE value )
1777+ {
1778+ rb_check_frozen (self );
1779+ GET_STATE (self );
1780+ RB_OBJ_WRITE (self , & state -> sort_keys , normalize_sort_keys (value ));
1781+ return Qnil ;
1782+ }
1783+
17251784static VALUE cState_allow_duplicate_key_p (VALUE self )
17261785{
17271786 GET_STATE (self );
@@ -1832,6 +1891,9 @@ static int configure_state_i(VALUE key, VALUE val, VALUE _arg)
18321891 state -> as_json_single_arg = proc && rb_proc_arity (proc ) == 1 ;
18331892 state_write_value (data , & state -> as_json , proc );
18341893 }
1894+ else if (key == sym_sort_keys ) {
1895+ state_write_value (data , & state -> sort_keys , normalize_sort_keys (val ));
1896+ }
18351897 return ST_CONTINUE ;
18361898}
18371899
@@ -1909,6 +1971,8 @@ void Init_generator(void)
19091971 VALUE mExt = rb_define_module_under (mJSON , "Ext" );
19101972 VALUE mGenerator = rb_define_module_under (mExt , "Generator" );
19111973
1974+ rb_global_variable (& default_sort_keys_proc );
1975+
19121976 rb_global_variable (& eGeneratorError );
19131977 eGeneratorError = rb_path2class ("JSON::GeneratorError" );
19141978
@@ -1918,6 +1982,8 @@ void Init_generator(void)
19181982 cState = rb_define_class_under (mGenerator , "State" , rb_cObject );
19191983 rb_define_alloc_func (cState , cState_s_allocate );
19201984 rb_define_singleton_method (cState , "from_state" , cState_from_state_s , 1 );
1985+ rb_define_singleton_method (cState , "default_sort_keys_proc=" , cState_set_default_sort_keys_proc , 1 );
1986+
19211987 rb_define_method (cState , "initialize" , cState_initialize , -1 );
19221988 rb_define_alias (cState , "initialize" , "initialize" ); // avoid method redefinition warnings
19231989 rb_define_private_method (cState , "_configure" , cState_configure , 1 );
@@ -1957,6 +2023,8 @@ void Init_generator(void)
19572023 rb_define_method (cState , "buffer_initial_length=" , cState_buffer_initial_length_set , 1 );
19582024 rb_define_method (cState , "generate" , cState_generate , -1 );
19592025 rb_define_method (cState , "_generate_no_fallback" , cState_generate_no_fallback , -1 );
2026+ rb_define_method (cState , "sort_keys" , cState_sort_keys_p , 0 );
2027+ rb_define_method (cState , "sort_keys=" , cState_sort_keys_set , 1 );
19602028
19612029 rb_define_private_method (cState , "allow_duplicate_key?" , cState_allow_duplicate_key_p , 0 );
19622030
@@ -1986,6 +2054,7 @@ void Init_generator(void)
19862054 sym_strict = ID2SYM (rb_intern ("strict" ));
19872055 sym_as_json = ID2SYM (rb_intern ("as_json" ));
19882056 sym_allow_duplicate_key = ID2SYM (rb_intern ("allow_duplicate_key" ));
2057+ sym_sort_keys = ID2SYM (rb_intern ("sort_keys" ));
19892058
19902059 usascii_encindex = rb_usascii_encindex ();
19912060 utf8_encindex = rb_utf8_encindex ();
0 commit comments