diff options
author | Jean Boussier <[email protected]> | 2024-10-31 08:52:19 +0100 |
---|---|---|
committer | Hiroshi SHIBATA <[email protected]> | 2024-11-01 13:04:24 +0900 |
commit | ef5565f5d17c5c8a0557637cf40e5124b0eebb5c (patch) | |
tree | ffa97d1a7201c83ad209ec204f1e5f1ef5619db5 | |
parent | b8b33efd4d63700e06a66fb774f0470411c9f650 (diff) |
JSON.generate: call to_json on String subclasses
Fix: https://github.com/ruby/json/issues/667
This is yet another behavior on which the various implementations
differed, but the C implementation used to call `to_json` on String
subclasses used as keys.
This was optimized out in e125072130229e54a651f7b11d7d5a782ae7fb65
but there is an Active Support test case for it, so it's best to
make all 3 implementation respect this behavior.
-rw-r--r-- | ext/json/fbuffer/fbuffer.h | 4 | ||||
-rw-r--r-- | ext/json/generator/generator.c | 6 | ||||
-rwxr-xr-x | test/json/json_generator_test.rb | 35 |
3 files changed, 44 insertions, 1 deletions
diff --git a/ext/json/fbuffer/fbuffer.h b/ext/json/fbuffer/fbuffer.h index 9bbfeed3cb..367ebd89ff 100644 --- a/ext/json/fbuffer/fbuffer.h +++ b/ext/json/fbuffer/fbuffer.h @@ -42,6 +42,10 @@ static VALUE fbuffer_to_s(FBuffer *fb); #define RB_UNLIKELY(expr) expr #endif +#ifndef RB_LIKELY +#define RB_LIKELY(expr) expr +#endif + static void fbuffer_stack_init(FBuffer *fb, unsigned long initial_length, char *stack_buffer, long stack_buffer_size) { fb->initial_length = (initial_length > 0) ? initial_length : FBUFFER_INITIAL_LENGTH_DEFAULT; diff --git a/ext/json/generator/generator.c b/ext/json/generator/generator.c index 00d9ffda07..8f0ef207de 100644 --- a/ext/json/generator/generator.c +++ b/ext/json/generator/generator.c @@ -737,7 +737,11 @@ json_object_i(VALUE key, VALUE val, VALUE _arg) break; } - generate_json_string(buffer, data, state, key_to_s); + if (RB_LIKELY(RBASIC_CLASS(key_to_s) == rb_cString)) { + generate_json_string(buffer, data, state, key_to_s); + } else { + generate_json(buffer, data, state, key_to_s); + } if (RB_UNLIKELY(state->space_before)) fbuffer_append_str(buffer, state->space_before); fbuffer_append_char(buffer, ':'); if (RB_UNLIKELY(state->space)) fbuffer_append_str(buffer, state->space); diff --git a/test/json/json_generator_test.rb b/test/json/json_generator_test.rb index 288cbbbb3a..6716eb82f2 100755 --- a/test/json/json_generator_test.rb +++ b/test/json/json_generator_test.rb @@ -486,6 +486,41 @@ class JSONGeneratorTest < Test::Unit::TestCase end end + class MyCustomString < String + def to_json(_state = nil) + '"my_custom_key"' + end + + def to_s + self + end + end + + def test_string_subclass_as_keys + # Ref: https://github.com/ruby/json/issues/667 + # if key.to_s doesn't return a bare string, we call `to_json` on it. + key = MyCustomString.new("won't be used") + assert_equal '{"my_custom_key":1}', JSON.generate(key => 1) + end + + class FakeString + def to_json(_state = nil) + raise "Shouldn't be called" + end + + def to_s + self + end + end + + def test_custom_object_as_keys + key = FakeString.new + error = assert_raise(TypeError) do + JSON.generate(key => 1) + end + assert_match "FakeString", error.message + end + def test_to_json_called_with_state_object object = Object.new called = false |