summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJean Boussier <[email protected]>2024-10-31 08:52:19 +0100
committerHiroshi SHIBATA <[email protected]>2024-11-01 13:04:24 +0900
commitef5565f5d17c5c8a0557637cf40e5124b0eebb5c (patch)
treeffa97d1a7201c83ad209ec204f1e5f1ef5619db5
parentb8b33efd4d63700e06a66fb774f0470411c9f650 (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.h4
-rw-r--r--ext/json/generator/generator.c6
-rwxr-xr-xtest/json/json_generator_test.rb35
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