Skip to content

Commit d37638e

Browse files
etiennebarriebyroot
andcommitted
Optimize Symbol generation in strict mode
Co-authored-by: Jean Boussier <[email protected]>
1 parent c472d72 commit d37638e

File tree

6 files changed

+86
-14
lines changed

6 files changed

+86
-14
lines changed

CHANGES.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
# Changes
22

3+
* `strict: true` now accept symbols as values. Previously they'd only be accepted as hash keys.
34
* The C extension Parser has been entirely reimplemented from scratch.
45
* Introduced `JSON::Coder` as a new API allowing to customize how non native types are serialized in a non-global way.
56

6-
77
### 2024-12-18 (2.9.1)
88

99
* Fix support for Solaris 10.

ext/json/ext/generator/generator.c

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -991,6 +991,29 @@ static void generate_json_string(FBuffer *buffer, struct generate_json_data *dat
991991
fbuffer_append_char(buffer, '"');
992992
}
993993

994+
static void generate_json_fallback(FBuffer *buffer, struct generate_json_data *data, JSON_Generator_State *state, VALUE obj)
995+
{
996+
VALUE tmp;
997+
if (rb_respond_to(obj, i_to_json)) {
998+
tmp = rb_funcall(obj, i_to_json, 1, vstate_get(data));
999+
Check_Type(tmp, T_STRING);
1000+
fbuffer_append_str(buffer, tmp);
1001+
} else {
1002+
tmp = rb_funcall(obj, i_to_s, 0);
1003+
Check_Type(tmp, T_STRING);
1004+
generate_json_string(buffer, data, state, tmp);
1005+
}
1006+
}
1007+
1008+
static inline void generate_json_symbol(FBuffer *buffer, struct generate_json_data *data, JSON_Generator_State *state, VALUE obj)
1009+
{
1010+
if (state->strict) {
1011+
generate_json_string(buffer, data, state, rb_sym2str(obj));
1012+
} else {
1013+
generate_json_fallback(buffer, data, state, obj);
1014+
}
1015+
}
1016+
9941017
static void generate_json_null(FBuffer *buffer, struct generate_json_data *data, JSON_Generator_State *state, VALUE obj)
9951018
{
9961019
fbuffer_append(buffer, "null", 4);
@@ -1049,7 +1072,6 @@ static void generate_json_fragment(FBuffer *buffer, struct generate_json_data *d
10491072

10501073
static void generate_json(FBuffer *buffer, struct generate_json_data *data, JSON_Generator_State *state, VALUE obj)
10511074
{
1052-
VALUE tmp;
10531075
bool as_json_called = false;
10541076
start:
10551077
if (obj == Qnil) {
@@ -1063,6 +1085,8 @@ static void generate_json(FBuffer *buffer, struct generate_json_data *data, JSON
10631085
generate_json_fixnum(buffer, data, state, obj);
10641086
} else if (RB_FLONUM_P(obj)) {
10651087
generate_json_float(buffer, data, state, obj);
1088+
} else if (RB_STATIC_SYM_P(obj)) {
1089+
generate_json_symbol(buffer, data, state, obj);
10661090
} else {
10671091
goto general;
10681092
}
@@ -1084,6 +1108,9 @@ static void generate_json(FBuffer *buffer, struct generate_json_data *data, JSON
10841108
if (klass != rb_cString) goto general;
10851109
generate_json_string(buffer, data, state, obj);
10861110
break;
1111+
case T_SYMBOL:
1112+
generate_json_symbol(buffer, data, state, obj);
1113+
break;
10871114
case T_FLOAT:
10881115
if (klass != rb_cFloat) goto general;
10891116
generate_json_float(buffer, data, state, obj);
@@ -1102,14 +1129,8 @@ static void generate_json(FBuffer *buffer, struct generate_json_data *data, JSON
11021129
} else {
11031130
raise_generator_error(obj, "%"PRIsVALUE" not allowed in JSON", CLASS_OF(obj));
11041131
}
1105-
} else if (rb_respond_to(obj, i_to_json)) {
1106-
tmp = rb_funcall(obj, i_to_json, 1, vstate_get(data));
1107-
Check_Type(tmp, T_STRING);
1108-
fbuffer_append_str(buffer, tmp);
11091132
} else {
1110-
tmp = rb_funcall(obj, i_to_s, 0);
1111-
Check_Type(tmp, T_STRING);
1112-
generate_json_string(buffer, data, state, tmp);
1133+
generate_json_fallback(buffer, data, state, obj);
11131134
}
11141135
}
11151136
}

java/src/json/ext/Generator.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,8 @@ private static <T extends IRubyObject> Handler<? super T> getHandlerFor(Ruby run
108108
case FLOAT : return (Handler<T>) FLOAT_HANDLER;
109109
case FIXNUM : return (Handler<T>) FIXNUM_HANDLER;
110110
case BIGNUM : return (Handler<T>) BIGNUM_HANDLER;
111+
case SYMBOL :
112+
return (Handler<T>) SYMBOL_HANDLER;
111113
case STRING :
112114
if (Helpers.metaclass(object) != runtime.getString()) break;
113115
return (Handler<T>) STRING_HANDLER;
@@ -458,6 +460,29 @@ void generate(ThreadContext context, Session session, RubyString object, OutputS
458460
}
459461
};
460462

463+
static final Handler<RubySymbol> SYMBOL_HANDLER =
464+
new Handler<RubySymbol>() {
465+
@Override
466+
int guessSize(ThreadContext context, Session session, RubySymbol object) {
467+
GeneratorState state = session.getState(context);
468+
if (state.strict()) {
469+
return STRING_HANDLER.guessSize(context, session, object.asString());
470+
} else {
471+
return GENERIC_HANDLER.guessSize(context, session, object);
472+
}
473+
}
474+
475+
@Override
476+
void generate(ThreadContext context, Session session, RubySymbol object, OutputStream buffer) throws IOException {
477+
GeneratorState state = session.getState(context);
478+
if (state.strict()) {
479+
STRING_HANDLER.generate(context, session, object.asString(), buffer);
480+
} else {
481+
GENERIC_HANDLER.generate(context, session, object, buffer);
482+
}
483+
}
484+
};
485+
461486
static RubyString ensureValidEncoding(ThreadContext context, RubyString str) {
462487
Encoding encoding = str.getEncoding();
463488
RubyString utf8String;

lib/json/add/symbol.rb

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,13 @@ def as_json(*)
3636
#
3737
# # {"json_class":"Symbol","s":"foo"}
3838
#
39-
def to_json(*a)
40-
as_json.to_json(*a)
39+
def to_json(state = nil, *a)
40+
state = ::JSON::State.from_state(state)
41+
if state.strict?
42+
super
43+
else
44+
as_json.to_json(state, *a)
45+
end
4146
end
4247

4348
# See #as_json.

lib/json/truffle_ruby/generator.rb

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -303,7 +303,7 @@ def to_h
303303
# GeneratorError exception.
304304
def generate(obj, anIO = nil)
305305
if @indent.empty? and @space.empty? and @space_before.empty? and @object_nl.empty? and @array_nl.empty? and
306-
!@ascii_only and !@script_safe and @max_nesting == 0 and !@strict
306+
!@ascii_only and !@script_safe and @max_nesting == 0 and (!@strict || Symbol === obj)
307307
result = generate_json(obj, ''.dup)
308308
else
309309
result = obj.to_json(self)
@@ -364,6 +364,12 @@ def generate_new(obj, anIO = nil) # :nodoc:
364364
end
365365
when Integer
366366
buf << obj.to_s
367+
when Symbol
368+
if @strict
369+
fast_serialize_string(obj.name, buf)
370+
else
371+
buf << obj.to_json(self)
372+
end
367373
else
368374
# Note: Float is handled this way since Float#to_s is slow anyway
369375
buf << obj.to_json(self)
@@ -539,10 +545,10 @@ def json_transform(state)
539545
each { |value|
540546
result << delim unless first
541547
result << state.indent * depth if indent
542-
if state.strict? && !(false == value || true == value || nil == value || String === value || Array === value || Hash === value || Integer === value || Float === value || Fragment === value)
548+
if state.strict? && !(false == value || true == value || nil == value || String === value || Array === value || Hash === value || Integer === value || Float === value || Fragment === value || Symbol == value)
543549
if state.as_json
544550
value = state.as_json.call(value)
545-
unless false == value || true == value || nil == value || String === value || Array === value || Hash === value || Integer === value || Float === value || Fragment === value
551+
unless false == value || true == value || nil == value || String === value || Array === value || Hash === value || Integer === value || Float === value || Fragment === value || Symbol === value
546552
raise GeneratorError.new("#{value.class} returned by #{state.as_json} not allowed in JSON", value)
547553
end
548554
result << value.to_json(state)
@@ -591,6 +597,17 @@ def to_json(state = nil, *)
591597
end
592598
end
593599

600+
module Symbol
601+
def to_json(state = nil, *args)
602+
state = State.from_state(state)
603+
if state.strict?
604+
name.to_json(state, *args)
605+
else
606+
super
607+
end
608+
end
609+
end
610+
594611
module String
595612
# This string should be encoded with UTF-8 A call to this method
596613
# returns a JSON string encoded with UTF16 big endian characters as

test/json/json_generator_test.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,10 @@ def test_dump_strict
8686

8787
assert_equal '42', dump(42, strict: true)
8888
assert_equal 'true', dump(true, strict: true)
89+
90+
assert_equal '"hello"', dump(:hello, strict: true)
91+
assert_equal '"hello"', :hello.to_json(strict: true)
92+
assert_equal '"World"', "World".to_json(strict: true)
8993
end
9094

9195
def test_generate_pretty

0 commit comments

Comments
 (0)