From da24ef7b9c28029be9962f1b281985a4a672f086 Mon Sep 17 00:00:00 2001 From: ydah Date: Mon, 5 Oct 2026 19:49:57 +0900 Subject: [PATCH] Honor as_json and reject disabled strict mode in JSON::Coder --- lib/json/common.rb | 9 +++++++-- lib/json/truffle_ruby/generator.rb | 17 +++++++++++++++-- test/json/json_coder_test.rb | 26 ++++++++++++++++++++++++++ test/json/json_generator_test.rb | 5 ++--- 4 files changed, 50 insertions(+), 7 deletions(-) diff --git a/lib/json/common.rb b/lib/json/common.rb index e1a8f6814..11f564083 100644 --- a/lib/json/common.rb +++ b/lib/json/common.rb @@ -827,7 +827,12 @@ class Coder # # puts MyApp::API_JSON_CODER.dump(Time.now.utc) # => "2025-01-21T08:41:44.286Z" # - def initialize(object_class: nil, array_class: nil, on_load: nil, **options, &as_json) + # The conversion callback can also be passed as +as_json:+. If both are given, the block takes precedence. + # Passing +false+ or +nil+ for +strict:+ raises ArgumentError. + # + def initialize(object_class: nil, array_class: nil, on_load: nil, strict: true, **options, &as_json) + raise ArgumentError, "JSON::Coder requires strict mode" unless strict + if object_class || array_class on_load = ParserOptions.on_load(on_load, object_class, array_class) end @@ -839,7 +844,7 @@ def initialize(object_class: nil, array_class: nil, on_load: nil, **options, &as @state = State.new( **generator_options, strict: true, - as_json: as_json, + as_json: as_json || generator_options[:as_json], ).freeze end diff --git a/lib/json/truffle_ruby/generator.rb b/lib/json/truffle_ruby/generator.rb index 99e5e7ec6..de3bcf366 100644 --- a/lib/json/truffle_ruby/generator.rb +++ b/lib/json/truffle_ruby/generator.rb @@ -192,7 +192,16 @@ def initialize(opts = nil) attr_accessor :array_nl # This proc converts unsupported types into native JSON types. - attr_accessor :as_json + attr_reader :as_json + + def as_json=(value) + raise FrozenError, "can't modify frozen #{self.class}: #{inspect}" if frozen? + value = value.to_proc if !(Proc === value) && value.respond_to?(:to_proc) + unless Proc === value + raise TypeError, "as_json must be a Proc" + end + @as_json = value + end # This integer returns the maximum level of data structure nesting in # the generated JSON, max_nesting = 0 if no maximum is checked. @@ -335,7 +344,11 @@ def configure(options) @object_nl = object_nl || '' @array_nl = array_nl || '' @allow_nan = allow_nan || false - @as_json = as_json || false + if as_json + self.as_json = as_json + else + @as_json = false + end @ascii_only = ascii_only || false self.sort_keys = sort_keys @depth = depth diff --git a/test/json/json_coder_test.rb b/test/json/json_coder_test.rb index 5ff314478..574222bf2 100755 --- a/test/json/json_coder_test.rb +++ b/test/json/json_coder_test.rb @@ -10,6 +10,32 @@ def test_json_coder_with_proc assert_equal %(["[Object object]"]), coder.dump([Object.new]) end + def test_json_coder_with_as_json_option + callback = ->(object, is_key) { 42 } + [callback, callback.method(:call)].each do |as_json| + coder = JSON::Coder.new(as_json: as_json) + assert_equal '[42]', coder.dump([Object.new]) + end + end + + def test_json_coder_block_overrides_as_json_option + coder = JSON::Coder.new(as_json: ->(object) { 1 }) { 2 } + assert_equal '[2]', coder.dump([Object.new]) + end + + def test_json_coder_as_json_invalid_type + assert_raise(TypeError) { JSON::Coder.new(as_json: 'x') } + end + + def test_json_coder_rejects_disabling_strict_mode + [false, nil].each do |strict| + error = assert_raise(ArgumentError) { JSON::Coder.new(strict: strict) } + assert_equal 'JSON::Coder requires strict mode', error.message + end + assert_raise(JSON::GeneratorError) { JSON::Coder.new.dump(Object.new) } + assert_raise(JSON::GeneratorError) { JSON::Coder.new(strict: true).dump(Object.new) } + end + def test_json_coder_with_proc_with_unsupported_value coder = JSON::Coder.new do |object, is_key| assert_equal false, is_key diff --git a/test/json/json_generator_test.rb b/test/json/json_generator_test.rb index 0a20357f0..326780177 100755 --- a/test/json/json_generator_test.rb +++ b/test/json/json_generator_test.rb @@ -1086,26 +1086,25 @@ def test_state_as_json_method object = Object.new state = JSON.state.new(strict: true) state.as_json = -> (o, is_key) { o.object_id }.method(:call) + assert_kind_of Proc, state.as_json assert_equal object.object_id.to_json, state.generate(object) end def test_json_generate_as_json_invalid_type - omit 'TruffleRuby does not convert as_json to Proc' if RUBY_ENGINE == 'truffleruby' [Object.new, Time.now].each do |as_json| assert_raise(TypeError) { JSON.generate(Object.new, strict: true, as_json: as_json) } end end def test_state_as_json_invalid_type - omit 'TruffleRuby does not convert as_json to Proc' if RUBY_ENGINE == 'truffleruby' state = JSON.state.new(strict: true) [Object.new, Time.now].each do |as_json| assert_raise(TypeError) { state.as_json = as_json } + assert_raise(TypeError) { state.configure(as_json: as_json) } end end def test_as_json_to_proc_returns_invalid_type - omit 'TruffleRuby does not convert as_json to Proc' if RUBY_ENGINE == 'truffleruby' as_json = Object.new def as_json.to_proc method(:to_proc)