Skip to content

Commit d35fe1e

Browse files
etiennebarriebyroot
andcommitted
Prototype JSON::Coder
Co-authored-by: Jean Boussier <[email protected]>
1 parent f8cfa26 commit d35fe1e

File tree

4 files changed

+63
-3
lines changed

4 files changed

+63
-3
lines changed

benchmark/encoder.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,10 @@
1717

1818
def implementations(ruby_obj)
1919
state = JSON::State.new(JSON.dump_default_options)
20+
coder = JSON::Coder.new
2021
{
2122
json: ["json", proc { JSON.generate(ruby_obj) }],
23+
json_coder: ["json_coder", proc { coder.dump(ruby_obj) }],
2224
oj: ["oj", proc { Oj.dump(ruby_obj) }],
2325
}
2426
end

ext/json/ext/generator/generator.c

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ typedef struct JSON_Generator_StateStruct {
1212
VALUE space_before;
1313
VALUE object_nl;
1414
VALUE array_nl;
15+
VALUE as_json;
1516

1617
long max_nesting;
1718
long depth;
@@ -30,8 +31,8 @@ typedef struct JSON_Generator_StateStruct {
3031
static VALUE mJSON, cState, mString_Extend, eGeneratorError, eNestingError, Encoding_UTF_8;
3132

3233
static ID i_to_s, i_to_json, i_new, i_pack, i_unpack, i_create_id, i_extend, i_encode;
33-
static ID sym_indent, sym_space, sym_space_before, sym_object_nl, sym_array_nl, sym_max_nesting, sym_allow_nan,
34-
sym_ascii_only, sym_depth, sym_buffer_initial_length, sym_script_safe, sym_escape_slash, sym_strict;
34+
static VALUE sym_indent, sym_space, sym_space_before, sym_object_nl, sym_array_nl, sym_max_nesting, sym_allow_nan,
35+
sym_ascii_only, sym_depth, sym_buffer_initial_length, sym_script_safe, sym_escape_slash, sym_strict, sym_as_json;
3536

3637

3738
#define GET_STATE_TO(self, state) \
@@ -647,6 +648,7 @@ static void State_mark(void *ptr)
647648
rb_gc_mark_movable(state->space_before);
648649
rb_gc_mark_movable(state->object_nl);
649650
rb_gc_mark_movable(state->array_nl);
651+
rb_gc_mark_movable(state->as_json);
650652
}
651653

652654
static void State_compact(void *ptr)
@@ -657,6 +659,7 @@ static void State_compact(void *ptr)
657659
state->space_before = rb_gc_location(state->space_before);
658660
state->object_nl = rb_gc_location(state->object_nl);
659661
state->array_nl = rb_gc_location(state->array_nl);
662+
state->as_json = rb_gc_location(state->as_json);
660663
}
661664

662665
static void State_free(void *ptr)
@@ -713,6 +716,7 @@ static void vstate_spill(struct generate_json_data *data)
713716
RB_OBJ_WRITTEN(vstate, Qundef, state->space_before);
714717
RB_OBJ_WRITTEN(vstate, Qundef, state->object_nl);
715718
RB_OBJ_WRITTEN(vstate, Qundef, state->array_nl);
719+
RB_OBJ_WRITTEN(vstate, Qundef, state->as_json);
716720
}
717721

718722
static inline VALUE vstate_get(struct generate_json_data *data)
@@ -974,6 +978,8 @@ static void generate_json_float(FBuffer *buffer, struct generate_json_data *data
974978
static void generate_json(FBuffer *buffer, struct generate_json_data *data, JSON_Generator_State *state, VALUE obj)
975979
{
976980
VALUE tmp;
981+
bool as_json_called = false;
982+
start:
977983
if (obj == Qnil) {
978984
generate_json_null(buffer, data, state, obj);
979985
} else if (obj == Qfalse) {
@@ -1013,7 +1019,13 @@ static void generate_json(FBuffer *buffer, struct generate_json_data *data, JSON
10131019
default:
10141020
general:
10151021
if (state->strict) {
1016-
raise_generator_error(obj, "%"PRIsVALUE" not allowed in JSON", CLASS_OF(obj));
1022+
if (RTEST(state->as_json) && !as_json_called) {
1023+
obj = rb_proc_call_with_block(state->as_json, 1, &obj, Qnil);
1024+
as_json_called = true;
1025+
goto start;
1026+
} else {
1027+
raise_generator_error(obj, "%"PRIsVALUE" not allowed in JSON", CLASS_OF(obj));
1028+
}
10171029
} else if (rb_respond_to(obj, i_to_json)) {
10181030
tmp = rb_funcall(obj, i_to_json, 1, vstate_get(data));
10191031
Check_Type(tmp, T_STRING);
@@ -1114,6 +1126,7 @@ static VALUE cState_init_copy(VALUE obj, VALUE orig)
11141126
objState->space_before = origState->space_before;
11151127
objState->object_nl = origState->object_nl;
11161128
objState->array_nl = origState->array_nl;
1129+
objState->as_json = origState->as_json;
11171130
return obj;
11181131
}
11191132

@@ -1486,6 +1499,7 @@ static int configure_state_i(VALUE key, VALUE val, VALUE _arg)
14861499
else if (key == sym_script_safe) { state->script_safe = RTEST(val); }
14871500
else if (key == sym_escape_slash) { state->script_safe = RTEST(val); }
14881501
else if (key == sym_strict) { state->strict = RTEST(val); }
1502+
else if (key == sym_as_json) { state->as_json = rb_convert_type(val, T_DATA, "Proc", "to_proc"); }
14891503
return ST_CONTINUE;
14901504
}
14911505

@@ -1664,6 +1678,7 @@ void Init_generator(void)
16641678
sym_script_safe = ID2SYM(rb_intern("script_safe"));
16651679
sym_escape_slash = ID2SYM(rb_intern("escape_slash"));
16661680
sym_strict = ID2SYM(rb_intern("strict"));
1681+
sym_as_json = ID2SYM(rb_intern("as_json"));
16671682

16681683
usascii_encindex = rb_usascii_encindex();
16691684
utf8_encindex = rb_utf8_encindex();

lib/json/common.rb

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -838,6 +838,22 @@ def merge_dump_options(opts, strict: NOT_SET)
838838
class << self
839839
private :merge_dump_options
840840
end
841+
842+
class Coder
843+
def initialize(options = nil, &as_json)
844+
if options.nil?
845+
options = { strict: true }
846+
else
847+
options[:strict] = true
848+
end
849+
options[:as_json] = as_json if as_json
850+
@state = State.new(options)
851+
end
852+
853+
def dump(...)
854+
@state.generate(...)
855+
end
856+
end
841857
end
842858

843859
module ::Kernel

test/json/json_generator_test.rb

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -661,4 +661,31 @@ def test_string_ext_included_calls_super
661661
def test_nonutf8_encoding
662662
assert_equal("\"5\u{b0}\"", "5\xb0".dup.force_encoding(Encoding::ISO_8859_1).to_json)
663663
end
664+
665+
def test_json_coder_with_proc
666+
coder = JSON::Coder.new do |object|
667+
"[Object object]"
668+
end
669+
assert_equal %(["[Object object]"]), coder.dump([Object.new])
670+
end
671+
672+
def test_json_coder_with_proc_with_unsupported_value
673+
coder = JSON::Coder.new do |object|
674+
Object.new
675+
end
676+
assert_raise(JSON::GeneratorError) { coder.dump([Object.new]) }
677+
end
678+
679+
def test_json_coder_options
680+
coder = JSON::Coder.new(array_nl: "\n") do |object|
681+
42
682+
end
683+
684+
assert_equal "[\n42\n]", coder.dump([Object.new])
685+
end
686+
687+
def test_json_generate_as_json_convert_to_proc
688+
object = Object.new
689+
assert_equal object.object_id.to_json, JSON.generate(object, strict: true, as_json: :object_id)
690+
end
664691
end

0 commit comments

Comments
 (0)