Skip to content

Commit 889ee46

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

File tree

3 files changed

+57
-3
lines changed

3 files changed

+57
-3
lines changed

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) \
@@ -674,6 +675,7 @@ static void State_mark(void *ptr)
674675
rb_gc_mark_movable(state->space_before);
675676
rb_gc_mark_movable(state->object_nl);
676677
rb_gc_mark_movable(state->array_nl);
678+
rb_gc_mark_movable(state->as_json);
677679
}
678680

679681
static void State_compact(void *ptr)
@@ -684,6 +686,7 @@ static void State_compact(void *ptr)
684686
state->space_before = rb_gc_location(state->space_before);
685687
state->object_nl = rb_gc_location(state->object_nl);
686688
state->array_nl = rb_gc_location(state->array_nl);
689+
state->as_json = rb_gc_location(state->as_json);
687690
}
688691

689692
static void State_free(void *ptr)
@@ -740,6 +743,7 @@ static void vstate_spill(struct generate_json_data *data)
740743
RB_OBJ_WRITTEN(vstate, Qundef, state->space_before);
741744
RB_OBJ_WRITTEN(vstate, Qundef, state->object_nl);
742745
RB_OBJ_WRITTEN(vstate, Qundef, state->array_nl);
746+
RB_OBJ_WRITTEN(vstate, Qundef, state->as_json);
743747
}
744748

745749
static inline VALUE vstate_get(struct generate_json_data *data)
@@ -1003,6 +1007,8 @@ static void generate_json_float(FBuffer *buffer, struct generate_json_data *data
10031007
static void generate_json(FBuffer *buffer, struct generate_json_data *data, JSON_Generator_State *state, VALUE obj)
10041008
{
10051009
VALUE tmp;
1010+
bool as_json_called = false;
1011+
start:
10061012
if (obj == Qnil) {
10071013
generate_json_null(buffer, data, state, obj);
10081014
} else if (obj == Qfalse) {
@@ -1042,7 +1048,13 @@ static void generate_json(FBuffer *buffer, struct generate_json_data *data, JSON
10421048
default:
10431049
general:
10441050
if (state->strict) {
1045-
raise_generator_error(obj, "%"PRIsVALUE" not allowed in JSON", CLASS_OF(obj));
1051+
if (RTEST(state->as_json) && !as_json_called) {
1052+
obj = rb_proc_call_with_block(state->as_json, 1, &obj, Qnil);
1053+
as_json_called = true;
1054+
goto start;
1055+
} else {
1056+
raise_generator_error(obj, "%"PRIsVALUE" not allowed in JSON", CLASS_OF(obj));
1057+
}
10461058
} else if (rb_respond_to(obj, i_to_json)) {
10471059
tmp = rb_funcall(obj, i_to_json, 1, vstate_get(data));
10481060
Check_Type(tmp, T_STRING);
@@ -1132,6 +1144,7 @@ static VALUE cState_init_copy(VALUE obj, VALUE orig)
11321144
objState->space_before = origState->space_before;
11331145
objState->object_nl = origState->object_nl;
11341146
objState->array_nl = origState->array_nl;
1147+
objState->as_json = origState->as_json;
11351148
return obj;
11361149
}
11371150

@@ -1504,6 +1517,7 @@ static int configure_state_i(VALUE key, VALUE val, VALUE _arg)
15041517
else if (key == sym_script_safe) { state->script_safe = RTEST(val); }
15051518
else if (key == sym_escape_slash) { state->script_safe = RTEST(val); }
15061519
else if (key == sym_strict) { state->strict = RTEST(val); }
1520+
else if (key == sym_as_json) { state->as_json = rb_convert_type(val, T_DATA, "Proc", "to_proc"); }
15071521
return ST_CONTINUE;
15081522
}
15091523

@@ -1682,6 +1696,7 @@ void Init_generator(void)
16821696
sym_script_safe = ID2SYM(rb_intern("script_safe"));
16831697
sym_escape_slash = ID2SYM(rb_intern("escape_slash"));
16841698
sym_strict = ID2SYM(rb_intern("strict"));
1699+
sym_as_json = ID2SYM(rb_intern("as_json"));
16851700

16861701
usascii_encindex = rb_usascii_encindex();
16871702
utf8_encindex = rb_utf8_encindex();

lib/json/common.rb

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -841,6 +841,18 @@ def merge_dump_options(opts, strict: NOT_SET)
841841
class << self
842842
private :merge_dump_options
843843
end
844+
845+
class Coder
846+
def initialize(options = nil, &as_json)
847+
default_options = { strict: true, as_json: as_json }
848+
849+
@options = options ? options.merge(default_options) : default_options
850+
end
851+
852+
def dump(object)
853+
State.generate(object, @options, nil)
854+
end
855+
end
844856
end
845857

846858
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)