From b4561f09c91e9942c52760e885c8b33c738f7063 Mon Sep 17 00:00:00 2001 From: youdie006 Date: Thu, 10 Sep 2026 04:01:05 +0900 Subject: [PATCH] Make State#configure and #merge only write the options they are given The pure-Ruby generator's private _configure gives every keyword a literal default, so a call that passes one option silently resets the other fifteen. The method is also aliased as merge, so a state built with indent/object_nl starts emitting compact JSON after any later configure call. The C extension does not behave this way. configure_state_i walks only the keys actually present in the hash, so State#merge really merges there. Default the keywords to the current ivars so the pure generator agrees. initialize already assigns every ivar to its literal default before calling _configure(**opts), so construction is unchanged. --- lib/json/truffle_ruby/generator.rb | 9 ++++--- test/json/json_generator_test.rb | 41 ++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 3 deletions(-) diff --git a/lib/json/truffle_ruby/generator.rb b/lib/json/truffle_ruby/generator.rb index 6dad5e7f4..fe5daafff 100644 --- a/lib/json/truffle_ruby/generator.rb +++ b/lib/json/truffle_ruby/generator.rb @@ -302,10 +302,13 @@ def configure(options) end alias merge configure + # Defaults are the current values so that #configure only writes what it was given. private def _configure( - indent: '', space: '', space_before: '', object_nl: '', array_nl: '', allow_nan: false, - as_json: false, ascii_only: false, sort_keys: false, depth: 0, buffer_initial_length: 1024, - allow_duplicate_key: false, script_safe: false, strict: false, max_nesting: 100 + indent: @indent, space: @space, space_before: @space_before, object_nl: @object_nl, + array_nl: @array_nl, allow_nan: @allow_nan, as_json: @as_json, ascii_only: @ascii_only, + sort_keys: @sort_keys, depth: @depth, buffer_initial_length: @buffer_initial_length, + allow_duplicate_key: @allow_duplicate_key, script_safe: @script_safe, strict: @strict, + max_nesting: @max_nesting ) if depth.negative? raise ArgumentError, "depth must be >= 0 (got #{depth})" diff --git a/test/json/json_generator_test.rb b/test/json/json_generator_test.rb index fcfc968f2..1f4b5d949 100755 --- a/test/json/json_generator_test.rb +++ b/test/json/json_generator_test.rb @@ -516,6 +516,47 @@ def test_configure_using_configure_and_merge assert_equal '5', state2.array_nl end + def test_configure_only_writes_the_string_options_it_is_given + state = JSON.state.new(indent: '1', space: '2', space_before: '3', object_nl: '4', array_nl: '5') + state.configure(space: '9') + assert_equal '1', state.indent + assert_equal '9', state.space + assert_equal '3', state.space_before + assert_equal '4', state.object_nl + assert_equal '5', state.array_nl + state.merge(array_nl: '8') + assert_equal '1', state.indent + assert_equal '9', state.space + assert_equal '3', state.space_before + assert_equal '4', state.object_nl + assert_equal '8', state.array_nl + end + + def test_configure_keeps_the_layout_of_a_pretty_state + state = JSON.state.new(indent: ' ', object_nl: "\n", array_nl: "\n") + state.configure(depth: 0) + assert_equal %({\n "foo":[\n 1\n ]\n}), state.generate({ 'foo' => [1] }) + end + + def test_configure_only_writes_the_other_options_it_is_given + omit 'JRuby resets the non-string options' if RUBY_ENGINE == 'jruby' + state = JSON.state.new(max_nesting: 3, allow_nan: true, ascii_only: true, script_safe: true) + state.configure(indent: '1') + assert_equal '1', state.indent + assert_equal 3, state.max_nesting + assert_equal true, state.allow_nan? + assert_equal true, state.ascii_only? + assert_equal true, state.script_safe? + end + + def test_configure_writes_a_string_option_given_as_nil + omit 'JRuby keeps the previous value for an explicit nil' if RUBY_ENGINE == 'jruby' + state = JSON.state.new(indent: '1', space: '2') + state.configure(indent: nil) + assert_equal '', state.indent + assert_equal '2', state.space + end + def test_configure_hash_conversion state = JSON.state.new state.configure(indent: '1')