From 0e0f0dfd070fc156ec74c58f44d86a884a0580e0 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Sat, 23 Aug 2025 16:58:54 +0200 Subject: Fix `JSON::Coder` to cast non-string keys. --- ext/json/generator/generator.c | 11 ++++++++++- test/json/json_coder_test.rb | 12 ++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/ext/json/generator/generator.c b/ext/json/generator/generator.c index c71e2f28a7..52dcd24f0e 100644 --- a/ext/json/generator/generator.c +++ b/ext/json/generator/generator.c @@ -1026,6 +1026,9 @@ json_object_i(VALUE key, VALUE val, VALUE _arg) } VALUE key_to_s; + bool as_json_called = false; + + start: switch (rb_type(key)) { case T_STRING: if (RB_LIKELY(RBASIC_CLASS(key) == rb_cString)) { @@ -1039,7 +1042,13 @@ json_object_i(VALUE key, VALUE val, VALUE _arg) break; default: if (data->state->strict) { - raise_generator_error(key, "%"PRIsVALUE" not allowed in JSON", rb_funcall(key, i_to_s, 0)); + if (RTEST(data->state->as_json) && !as_json_called) { + key = rb_proc_call_with_block(data->state->as_json, 1, &key, Qnil); + as_json_called = true; + goto start; + } else { + raise_generator_error(key, "%"PRIsVALUE" not allowed as object key in JSON", CLASS_OF(key)); + } } key_to_s = rb_convert_type(key, T_STRING, "String", "to_s"); break; diff --git a/test/json/json_coder_test.rb b/test/json/json_coder_test.rb index 9861181910..fc4aba2968 100755 --- a/test/json/json_coder_test.rb +++ b/test/json/json_coder_test.rb @@ -18,6 +18,18 @@ class JSONCoderTest < Test::Unit::TestCase assert_raise(JSON::GeneratorError) { coder.dump([Object.new]) } end + def test_json_coder_hash_key + obj = Object.new + coder = JSON::Coder.new(&:to_s) + assert_equal %({#{obj.to_s.inspect}:1}), coder.dump({ obj => 1 }) + + coder = JSON::Coder.new { 42 } + error = assert_raise JSON::GeneratorError do + coder.dump({ obj => 1 }) + end + assert_equal "Integer not allowed as object key in JSON", error.message + end + def test_json_coder_options coder = JSON::Coder.new(array_nl: "\n") do |object| 42 -- cgit v1.2.3