diff --git a/ext/json/ext/generator/generator.c b/ext/json/ext/generator/generator.c index 0b7397c5..82e86e7d 100644 --- a/ext/json/ext/generator/generator.c +++ b/ext/json/ext/generator/generator.c @@ -1550,6 +1550,18 @@ static VALUE cState_as_json(VALUE self) return state->as_json; } +static VALUE as_json_config(VALUE value) +{ + if (rb_obj_is_proc(value)) { + return value; + } + VALUE proc = rb_check_funcall(value, rb_intern("to_proc"), 0, NULL); + if (proc == Qundef || !rb_obj_is_proc(proc)) { + rb_raise(rb_eTypeError, "as_json must be a Proc"); + } + return proc; +} + /* * call-seq: as_json=(as_json) * @@ -1559,7 +1571,7 @@ static VALUE cState_as_json_set(VALUE self, VALUE as_json) { rb_check_frozen(self); GET_STATE(self); - RB_OBJ_WRITE(self, &state->as_json, rb_convert_type(as_json, T_DATA, "Proc", "to_proc")); + RB_OBJ_WRITE(self, &state->as_json, as_json_config(as_json)); return Qnil; } @@ -1879,7 +1891,7 @@ static int configure_state_i(VALUE key, VALUE val, VALUE _arg) else if (key == sym_strict) { state->strict = RTEST(val); } else if (key == sym_allow_duplicate_key) { state->allow_duplicate_key = RTEST(val); } else if (key == sym_as_json) { - VALUE proc = RTEST(val) ? rb_convert_type(val, T_DATA, "Proc", "to_proc") : Qfalse; + VALUE proc = RTEST(val) ? as_json_config(val) : Qfalse; state->as_json_single_arg = proc && rb_proc_arity(proc) == 1; state_write_value(data, &state->as_json, proc); } diff --git a/test/json/json_generator_test.rb b/test/json/json_generator_test.rb index 1b219773..0124521a 100755 --- a/test/json/json_generator_test.rb +++ b/test/json/json_generator_test.rb @@ -1035,6 +1035,44 @@ def test_json_generate_as_json_convert_to_proc assert_equal object.object_id.to_json, JSON.generate(object, strict: true, as_json: -> (o, is_key) { o.object_id }) end + def test_json_generate_as_json_method + object = Object.new + as_json = -> (o, is_key) { o.object_id }.method(:call) + assert_equal object.object_id.to_json, JSON.generate(object, strict: true, as_json: as_json) + end + + 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_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 } + 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) + end + assert_raise(TypeError) { JSON.generate(Object.new, strict: true, as_json: as_json) } + assert_raise(TypeError) { JSON.state.new.as_json = as_json } + end + def test_as_json_nan_does_not_call_to_json def (obj = Object.new).to_json(*) "null"