diff --git a/CHANGES.md b/CHANGES.md index 6a71f0015..1bde9fc3b 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,7 @@ # Changes +* Fix `JSON.load` proc argument to substitute the parsed object with the return value. + This better match `Marshal.load` behavior. * Deprecate `JSON.fast_generate` (it's not any faster, so pointless). * Deprecate `JSON.load_default_options`. * Deprecate `JSON.unsafe_load_default_options`. diff --git a/ext/json/ext/parser/extconf.rb b/ext/json/ext/parser/extconf.rb index a8e21aed4..09c963778 100644 --- a/ext/json/ext/parser/extconf.rb +++ b/ext/json/ext/parser/extconf.rb @@ -4,7 +4,6 @@ have_func("rb_enc_interned_str", "ruby.h") # RUBY_VERSION >= 3.0 have_func("rb_hash_new_capa", "ruby.h") # RUBY_VERSION >= 3.2 have_func("rb_hash_bulk_insert", "ruby.h") # Missing on TruffleRuby -have_func("rb_category_warn", "ruby.h") # Missing on TruffleRuby have_func("strnlen", "string.h") # Missing on Solaris 10 append_cflags("-std=c99") diff --git a/ext/json/ext/parser/parser.c b/ext/json/ext/parser/parser.c index d2d0d38d8..f20769a36 100644 --- a/ext/json/ext/parser/parser.c +++ b/ext/json/ext/parser/parser.c @@ -31,28 +31,15 @@ typedef unsigned char _Bool; static VALUE mJSON, eNestingError, Encoding_UTF_8; static VALUE CNaN, CInfinity, CMinusInfinity; -static ID i_json_creatable_p, i_json_create, i_create_id, - i_chr, i_deep_const_get, i_match, i_aset, i_aref, +static ID i_chr, i_aset, i_aref, i_leftshift, i_new, i_try_convert, i_uminus, i_encode; static VALUE sym_max_nesting, sym_allow_nan, sym_allow_trailing_comma, sym_symbolize_names, sym_freeze, - sym_create_additions, sym_create_id, sym_object_class, sym_array_class, - sym_decimal_class, sym_match_string; + sym_decimal_class, sym_on_load; static int binary_encindex; static int utf8_encindex; -#ifdef HAVE_RB_CATEGORY_WARN -# define json_deprecated(message) rb_category_warn(RB_WARN_CATEGORY_DEPRECATED, message) -#else -# define json_deprecated(message) rb_warn(message) -#endif - -static const char deprecated_create_additions_warning[] = - "JSON.load implicit support for `create_additions: true` is deprecated " - "and will be removed in 3.0, use JSON.unsafe_load or explicitly " - "pass `create_additions: true`"; - #ifndef HAVE_RB_HASH_BULK_INSERT // For TruffleRuby void @@ -444,20 +431,15 @@ static int convert_UTF32_to_UTF8(char *buf, uint32_t ch) } typedef struct JSON_ParserStruct { - VALUE create_id; - VALUE object_class; - VALUE array_class; + VALUE on_load_proc; VALUE decimal_class; ID decimal_method_id; - VALUE match_string; int max_nesting; bool allow_nan; bool allow_trailing_comma; bool parsing_name; bool symbolize_names; bool freeze; - bool create_additions; - bool deprecated_create_additions; } JSON_ParserConfig; typedef struct JSON_ParserStateStruct { @@ -769,18 +751,7 @@ static VALUE json_decode_float(JSON_ParserConfig *config, const char *start, con static inline VALUE json_decode_array(JSON_ParserState *state, JSON_ParserConfig *config, long count) { - VALUE array; - if (RB_UNLIKELY(config->array_class)) { - array = rb_class_new_instance(0, 0, config->array_class); - VALUE *items = rvalue_stack_peek(state->stack, count); - long index; - for (index = 0; index < count; index++) { - rb_funcall(array, i_leftshift, 1, items[index]); - } - } else { - array = rb_ary_new_from_values(count, rvalue_stack_peek(state->stack, count)); - } - + VALUE array = rb_ary_new_from_values(count, rvalue_stack_peek(state->stack, count)); rvalue_stack_pop(state->stack, count); if (config->freeze) { @@ -790,52 +761,13 @@ static inline VALUE json_decode_array(JSON_ParserState *state, JSON_ParserConfig return array; } -static bool json_obj_creatable_p(VALUE klass) -{ - if (rb_respond_to(klass, i_json_creatable_p)) { - return RTEST(rb_funcall(klass, i_json_creatable_p, 0)); - } else { - return rb_respond_to(klass, i_json_create); - } -} - static inline VALUE json_decode_object(JSON_ParserState *state, JSON_ParserConfig *config, long count) { - VALUE object; - if (RB_UNLIKELY(config->object_class)) { - object = rb_class_new_instance(0, 0, config->object_class); - long index = 0; - VALUE *items = rvalue_stack_peek(state->stack, count); - while (index < count) { - VALUE name = items[index++]; - VALUE value = items[index++]; - rb_funcall(object, i_aset, 2, name, value); - } - } else { - object = rb_hash_new_capa(count); - rb_hash_bulk_insert(count, rvalue_stack_peek(state->stack, count), object); - } + VALUE object = rb_hash_new_capa(count); + rb_hash_bulk_insert(count, rvalue_stack_peek(state->stack, count), object); rvalue_stack_pop(state->stack, count); - if (RB_UNLIKELY(config->create_additions)) { - VALUE klassname; - if (config->object_class) { - klassname = rb_funcall(object, i_aref, 1, config->create_id); - } else { - klassname = rb_hash_aref(object, config->create_id); - } - if (!NIL_P(klassname)) { - VALUE klass = rb_funcall(mJSON, i_deep_const_get, 1, klassname); - if (json_obj_creatable_p(klass)) { - if (config->deprecated_create_additions) { - json_deprecated(deprecated_create_additions_warning); - } - object = rb_funcall(klass, i_json_create, 1, object); - } - } - } - if (config->freeze) { RB_OBJ_FREEZE(object); } @@ -843,17 +775,6 @@ static inline VALUE json_decode_object(JSON_ParserState *state, JSON_ParserConfi return object; } -static int match_i(VALUE regexp, VALUE klass, VALUE memo) -{ - if (regexp == Qundef) return ST_STOP; - if (json_obj_creatable_p(klass) && - RTEST(rb_funcall(regexp, i_match, 1, rb_ary_entry(memo, 0)))) { - rb_ary_push(memo, klass); - return ST_STOP; - } - return ST_CONTINUE; -} - static inline VALUE json_decode_string(JSON_ParserState *state, JSON_ParserConfig *config, const char *start, const char *end, bool escaped, bool is_name) { VALUE string; @@ -865,21 +786,17 @@ static inline VALUE json_decode_string(JSON_ParserState *state, JSON_ParserConfi string = json_string_fastpath(state, start, end, is_name, intern, symbolize); } - if (RB_UNLIKELY(config->create_additions && RTEST(config->match_string))) { - VALUE klass; - VALUE memo = rb_ary_new2(2); - rb_ary_push(memo, string); - rb_hash_foreach(config->match_string, match_i, memo); - klass = rb_ary_entry(memo, 1); - if (RTEST(klass)) { - string = rb_funcall(klass, i_json_create, 1, string); - } - } - return string; } -#define PUSH(result) rvalue_stack_push(state->stack, result, &state->stack_handle, &state->stack) +static inline VALUE json_push_value(JSON_ParserState *state, JSON_ParserConfig *config, VALUE value) +{ + if (RB_UNLIKELY(config->on_load_proc)) { + value = rb_proc_call_with_block(config->on_load_proc, 1, &value, Qnil); + } + rvalue_stack_push(state->stack, value, &state->stack_handle, &state->stack); + return value; +} static const bool string_scan[256] = { // ASCII Control Characters @@ -906,7 +823,7 @@ static inline VALUE json_parse_string(JSON_ParserState *state, JSON_ParserConfig case '"': { VALUE string = json_decode_string(state, config, start, state->cursor, escaped, is_name); state->cursor++; - return PUSH(string); + return json_push_value(state, config, string); } case '\\': { state->cursor++; @@ -940,7 +857,7 @@ static VALUE json_parse_any(JSON_ParserState *state, JSON_ParserConfig *config) case 'n': if ((state->end - state->cursor >= 4) && (memcmp(state->cursor, "null", 4) == 0)) { state->cursor += 4; - return PUSH(Qnil); + return json_push_value(state, config, Qnil); } raise_parse_error("unexpected token at '%s'", state->cursor); @@ -948,7 +865,7 @@ static VALUE json_parse_any(JSON_ParserState *state, JSON_ParserConfig *config) case 't': if ((state->end - state->cursor >= 4) && (memcmp(state->cursor, "true", 4) == 0)) { state->cursor += 4; - return PUSH(Qtrue); + return json_push_value(state, config, Qtrue); } raise_parse_error("unexpected token at '%s'", state->cursor); @@ -957,7 +874,7 @@ static VALUE json_parse_any(JSON_ParserState *state, JSON_ParserConfig *config) // Note: memcmp with a small power of two compile to an integer comparison if ((state->end - state->cursor >= 5) && (memcmp(state->cursor + 1, "alse", 4) == 0)) { state->cursor += 5; - return PUSH(Qfalse); + return json_push_value(state, config, Qfalse); } raise_parse_error("unexpected token at '%s'", state->cursor); @@ -966,7 +883,7 @@ static VALUE json_parse_any(JSON_ParserState *state, JSON_ParserConfig *config) // Note: memcmp with a small power of two compile to an integer comparison if (config->allow_nan && (state->end - state->cursor >= 3) && (memcmp(state->cursor + 1, "aN", 2) == 0)) { state->cursor += 3; - return PUSH(CNaN); + return json_push_value(state, config, CNaN); } raise_parse_error("unexpected token at '%s'", state->cursor); @@ -974,7 +891,7 @@ static VALUE json_parse_any(JSON_ParserState *state, JSON_ParserConfig *config) case 'I': if (config->allow_nan && (state->end - state->cursor >= 8) && (memcmp(state->cursor, "Infinity", 8) == 0)) { state->cursor += 8; - return PUSH(CInfinity); + return json_push_value(state, config, CInfinity); } raise_parse_error("unexpected token at '%s'", state->cursor); @@ -984,7 +901,7 @@ static VALUE json_parse_any(JSON_ParserState *state, JSON_ParserConfig *config) if ((state->end - state->cursor >= 9) && (memcmp(state->cursor + 1, "Infinity", 8) == 0)) { if (config->allow_nan) { state->cursor += 9; - return PUSH(CMinusInfinity); + return json_push_value(state, config, CMinusInfinity); } else { raise_parse_error("unexpected token at '%s'", state->cursor); } @@ -1041,9 +958,9 @@ static VALUE json_parse_any(JSON_ParserState *state, JSON_ParserConfig *config) } if (integer) { - return PUSH(json_decode_integer(start, state->cursor)); + return json_push_value(state, config, json_decode_integer(start, state->cursor)); } - return PUSH(json_decode_float(config, start, state->cursor)); + return json_push_value(state, config, json_decode_float(config, start, state->cursor)); } case '"': { // %r{\A"[^"\\\t\n\x00]*(?:\\[bfnrtu\\/"][^"\\]*)*"} @@ -1057,7 +974,7 @@ static VALUE json_parse_any(JSON_ParserState *state, JSON_ParserConfig *config) if ((state->cursor < state->end) && (*state->cursor == ']')) { state->cursor++; - return PUSH(json_decode_array(state, config, 0)); + return json_push_value(state, config, json_decode_array(state, config, 0)); } else { state->current_nesting++; if (RB_UNLIKELY(config->max_nesting && (config->max_nesting < state->current_nesting))) { @@ -1076,7 +993,7 @@ static VALUE json_parse_any(JSON_ParserState *state, JSON_ParserConfig *config) long count = state->stack->head - stack_head; state->current_nesting--; state->in_array--; - return PUSH(json_decode_array(state, config, count)); + return json_push_value(state, config, json_decode_array(state, config, count)); } if (*state->cursor == ',') { @@ -1103,7 +1020,7 @@ static VALUE json_parse_any(JSON_ParserState *state, JSON_ParserConfig *config) if ((state->cursor < state->end) && (*state->cursor == '}')) { state->cursor++; - return PUSH(json_decode_object(state, config, 0)); + return json_push_value(state, config, json_decode_object(state, config, 0)); } else { state->current_nesting++; if (RB_UNLIKELY(config->max_nesting && (config->max_nesting < state->current_nesting))) { @@ -1132,7 +1049,7 @@ static VALUE json_parse_any(JSON_ParserState *state, JSON_ParserConfig *config) state->cursor++; state->current_nesting--; long count = state->stack->head - stack_head; - return PUSH(json_decode_object(state, config, count)); + return json_push_value(state, config, json_decode_object(state, config, count)); } if (*state->cursor == ',') { @@ -1220,10 +1137,7 @@ static int parser_config_init_i(VALUE key, VALUE val, VALUE data) else if (key == sym_allow_trailing_comma) { config->allow_trailing_comma = RTEST(val); } else if (key == sym_symbolize_names) { config->symbolize_names = RTEST(val); } else if (key == sym_freeze) { config->freeze = RTEST(val); } - else if (key == sym_create_id) { config->create_id = RTEST(val) ? val : Qfalse; } - else if (key == sym_object_class) { config->object_class = RTEST(val) ? val : Qfalse; } - else if (key == sym_array_class) { config->array_class = RTEST(val) ? val : Qfalse; } - else if (key == sym_match_string) { config->match_string = RTEST(val) ? val : Qfalse; } + else if (key == sym_on_load) { config->on_load_proc = RTEST(val) ? val : Qfalse; } else if (key == sym_decimal_class) { if (RTEST(val)) { if (rb_respond_to(val, i_try_convert)) { @@ -1253,15 +1167,6 @@ static int parser_config_init_i(VALUE key, VALUE val, VALUE data) } } } - else if (key == sym_create_additions) { - if (NIL_P(val)) { - config->create_additions = true; - config->deprecated_create_additions = true; - } else { - config->create_additions = RTEST(val); - config->deprecated_create_additions = false; - } - } return ST_CONTINUE; } @@ -1276,16 +1181,6 @@ static void parser_config_init(JSON_ParserConfig *config, VALUE opts) // We assume in most cases few keys are set so it's faster to go over // the provided keys than to check all possible keys. rb_hash_foreach(opts, parser_config_init_i, (VALUE)config); - - if (config->symbolize_names && config->create_additions) { - rb_raise(rb_eArgError, - "options :symbolize_names and :create_additions cannot be " - " used in conjunction"); - } - - if (config->create_additions && !config->create_id) { - config->create_id = rb_funcall(mJSON, i_create_id, 0); - } } } @@ -1310,15 +1205,6 @@ static void parser_config_init(JSON_ParserConfig *config, VALUE opts) * (keys) in a JSON object. Otherwise strings are returned, which is * also the default. It's not possible to use this option in * conjunction with the *create_additions* option. - * * *create_additions*: If set to false, the Parser doesn't create - * additions even if a matching class and create_id was found. This option - * defaults to false. - * * *object_class*: Defaults to Hash. If another type is provided, it will be used - * instead of Hash to represent JSON objects. The type must respond to - * +new+ without arguments, and return an object that respond to +[]=+. - * * *array_class*: Defaults to Array If another type is provided, it will be used - * instead of Hash to represent JSON arrays. The type must respond to - * +new+ without arguments, and return an object that respond to +<<+. * * *decimal_class*: Specifies which class to use instead of the default * (Float) when parsing decimal numbers. This class must accept a single * string argument in its constructor. @@ -1329,11 +1215,7 @@ static VALUE cParserConfig_initialize(VALUE self, VALUE opts) parser_config_init(config, opts); - RB_OBJ_WRITTEN(self, Qundef, config->create_id); - RB_OBJ_WRITTEN(self, Qundef, config->object_class); - RB_OBJ_WRITTEN(self, Qundef, config->array_class); RB_OBJ_WRITTEN(self, Qundef, config->decimal_class); - RB_OBJ_WRITTEN(self, Qundef, config->match_string); return self; } @@ -1396,11 +1278,8 @@ static VALUE cParser_m_parse(VALUE klass, VALUE Vsource, VALUE opts) static void JSON_ParserConfig_mark(void *ptr) { JSON_ParserConfig *config = ptr; - rb_gc_mark(config->create_id); - rb_gc_mark(config->object_class); - rb_gc_mark(config->array_class); + rb_gc_mark(config->on_load_proc); rb_gc_mark(config->decimal_class); - rb_gc_mark(config->match_string); } static void JSON_ParserConfig_free(void *ptr) @@ -1468,19 +1347,10 @@ void Init_parser(void) sym_allow_trailing_comma = ID2SYM(rb_intern("allow_trailing_comma")); sym_symbolize_names = ID2SYM(rb_intern("symbolize_names")); sym_freeze = ID2SYM(rb_intern("freeze")); - sym_create_additions = ID2SYM(rb_intern("create_additions")); - sym_create_id = ID2SYM(rb_intern("create_id")); - sym_object_class = ID2SYM(rb_intern("object_class")); - sym_array_class = ID2SYM(rb_intern("array_class")); + sym_on_load = ID2SYM(rb_intern("on_load")); sym_decimal_class = ID2SYM(rb_intern("decimal_class")); - sym_match_string = ID2SYM(rb_intern("match_string")); - i_create_id = rb_intern("create_id"); - i_json_creatable_p = rb_intern("json_creatable?"); - i_json_create = rb_intern("json_create"); i_chr = rb_intern("chr"); - i_match = rb_intern("match"); - i_deep_const_get = rb_intern("deep_const_get"); i_aset = rb_intern("[]="); i_aref = rb_intern("[]"); i_leftshift = rb_intern("<<"); diff --git a/java/src/json/ext/ParserConfig.java b/java/src/json/ext/ParserConfig.java index 83251fc8d..692b3dfaa 100644 --- a/java/src/json/ext/ParserConfig.java +++ b/java/src/json/ext/ParserConfig.java @@ -17,6 +17,7 @@ import org.jruby.RubyHash; import org.jruby.RubyInteger; import org.jruby.RubyObject; +import org.jruby.RubyProc; import org.jruby.RubyString; import org.jruby.anno.JRubyMethod; import org.jruby.exceptions.JumpException; @@ -50,16 +51,12 @@ */ public class ParserConfig extends RubyObject { private final RuntimeInfo info; - private RubyString createId; - private boolean createAdditions; - private boolean deprecatedCreateAdditions; private int maxNesting; private boolean allowNaN; private boolean allowTrailingComma; private boolean symbolizeNames; private boolean freeze; - private RubyClass objectClass; - private RubyClass arrayClass; + private RubyProc onLoadProc; private RubyClass decimalClass; BiFunction decimalFactory; private RubyHash match_string; @@ -181,25 +178,9 @@ public IRubyObject initialize(ThreadContext context, IRubyObject options) { this.allowTrailingComma = opts.getBool("allow_trailing_comma", false); this.symbolizeNames = opts.getBool("symbolize_names", false); this.freeze = opts.getBool("freeze", false); - this.createId = opts.getString("create_id", getCreateId(context)); + this.onLoadProc = opts.getProc("on_load"); - IRubyObject additions = opts.get("create_additions"); - this.createAdditions = false; - this.deprecatedCreateAdditions = false; - - if (additions != null) { - if (additions.isNil()) { - this.createAdditions = true; - this.deprecatedCreateAdditions = true; - } else { - this.createAdditions = opts.getBool("create_additions", false); - } - } - - this.objectClass = opts.getClass("object_class", runtime.getHash()); - this.arrayClass = opts.getClass("array_class", runtime.getArray()); this.decimalClass = opts.getClass("decimal_class", null); - this.match_string = opts.getHash("match_string"); if (decimalClass == null) { this.decimalFactory = this::createFloat; @@ -209,13 +190,17 @@ public IRubyObject initialize(ThreadContext context, IRubyObject options) { this.decimalFactory = this::createCustomDecimal; } - if(symbolizeNames && createAdditions) { - throw runtime.newArgumentError("options :symbolize_names and :create_additions cannot be used in conjunction"); - } - return this; } + public IRubyObject onLoad(ThreadContext context, IRubyObject object) { + if (onLoadProc == null) { + return object; + } else { + return onLoadProc.call(context, object); + } + } + /** * Checks the given string's encoding. If a non-UTF-8 encoding is detected, * a converted copy is returned. @@ -266,14 +251,6 @@ private IRubyObject createCustomDecimal(final ThreadContext context, final ByteL return decimalClass.newInstance(context, context.runtime.newString(num), Block.NULL_BLOCK); } - private static boolean isObjCreateable(final ThreadContext context, IRubyObject klass) { - if (klass.respondsTo("json_creatable?")) { - return klass.callMethod(context, "json_creatable?").isTrue(); - } else { - return klass.respondsTo("json_create"); - } - } - /** * A string parsing session. * @@ -311,11 +288,11 @@ private RaiseException unexpectedToken(ThreadContext context, int absStart, int } -// line 337 "ParserConfig.rl" +// line 314 "ParserConfig.rl" -// line 319 "ParserConfig.java" +// line 296 "ParserConfig.java" private static byte[] init__JSON_value_actions_0() { return new byte [] { @@ -429,7 +406,7 @@ private static byte[] init__JSON_value_from_state_actions_0() static final int JSON_value_en_main = 1; -// line 443 "ParserConfig.rl" +// line 420 "ParserConfig.rl" void parseValue(ThreadContext context, ParserResult res, int p, int pe) { @@ -437,14 +414,14 @@ void parseValue(ThreadContext context, ParserResult res, int p, int pe) { IRubyObject result = null; -// line 441 "ParserConfig.java" +// line 418 "ParserConfig.java" { cs = JSON_value_start; } -// line 450 "ParserConfig.rl" +// line 427 "ParserConfig.rl" -// line 448 "ParserConfig.java" +// line 425 "ParserConfig.java" { int _klen; int _trans = 0; @@ -470,13 +447,13 @@ void parseValue(ThreadContext context, ParserResult res, int p, int pe) { while ( _nacts-- > 0 ) { switch ( _JSON_value_actions[_acts++] ) { case 9: -// line 428 "ParserConfig.rl" +// line 405 "ParserConfig.rl" { p--; { p += 1; _goto_targ = 5; if (true) continue _goto;} } break; -// line 480 "ParserConfig.java" +// line 457 "ParserConfig.java" } } @@ -539,25 +516,25 @@ else if ( data[p] > _JSON_value_trans_keys[_mid+1] ) switch ( _JSON_value_actions[_acts++] ) { case 0: -// line 345 "ParserConfig.rl" +// line 322 "ParserConfig.rl" { result = context.nil; } break; case 1: -// line 348 "ParserConfig.rl" +// line 325 "ParserConfig.rl" { result = context.fals; } break; case 2: -// line 351 "ParserConfig.rl" +// line 328 "ParserConfig.rl" { result = context.tru; } break; case 3: -// line 354 "ParserConfig.rl" +// line 331 "ParserConfig.rl" { if (config.allowNaN) { result = getConstant(CONST_NAN); @@ -567,7 +544,7 @@ else if ( data[p] > _JSON_value_trans_keys[_mid+1] ) } break; case 4: -// line 361 "ParserConfig.rl" +// line 338 "ParserConfig.rl" { if (config.allowNaN) { result = getConstant(CONST_INFINITY); @@ -577,7 +554,7 @@ else if ( data[p] > _JSON_value_trans_keys[_mid+1] ) } break; case 5: -// line 368 "ParserConfig.rl" +// line 345 "ParserConfig.rl" { if (pe > p + 8 && absSubSequence(p, p + 9).equals(JSON_MINUS_INFINITY)) { @@ -606,7 +583,7 @@ else if ( data[p] > _JSON_value_trans_keys[_mid+1] ) } break; case 6: -// line 394 "ParserConfig.rl" +// line 371 "ParserConfig.rl" { parseString(context, res, p, pe); if (res.result == null) { @@ -619,7 +596,7 @@ else if ( data[p] > _JSON_value_trans_keys[_mid+1] ) } break; case 7: -// line 404 "ParserConfig.rl" +// line 381 "ParserConfig.rl" { currentNesting++; parseArray(context, res, p, pe); @@ -634,7 +611,7 @@ else if ( data[p] > _JSON_value_trans_keys[_mid+1] ) } break; case 8: -// line 416 "ParserConfig.rl" +// line 393 "ParserConfig.rl" { currentNesting++; parseObject(context, res, p, pe); @@ -648,7 +625,7 @@ else if ( data[p] > _JSON_value_trans_keys[_mid+1] ) } } break; -// line 652 "ParserConfig.java" +// line 629 "ParserConfig.java" } } } @@ -668,7 +645,7 @@ else if ( data[p] > _JSON_value_trans_keys[_mid+1] ) break; } } -// line 451 "ParserConfig.rl" +// line 428 "ParserConfig.rl" if (cs >= JSON_value_first_final && result != null) { if (config.freeze) { @@ -681,7 +658,7 @@ else if ( data[p] > _JSON_value_trans_keys[_mid+1] ) } -// line 685 "ParserConfig.java" +// line 662 "ParserConfig.java" private static byte[] init__JSON_integer_actions_0() { return new byte [] { @@ -780,7 +757,7 @@ private static byte[] init__JSON_integer_trans_actions_0() static final int JSON_integer_en_main = 1; -// line 473 "ParserConfig.rl" +// line 450 "ParserConfig.rl" void parseInteger(ThreadContext context, ParserResult res, int p, int pe) { @@ -790,22 +767,22 @@ void parseInteger(ThreadContext context, ParserResult res, int p, int pe) { return; } RubyInteger number = createInteger(context, p, new_p); - res.update(number, new_p + 1); + res.update(config.onLoad(context, number), new_p + 1); } int parseIntegerInternal(int p, int pe) { int cs; -// line 801 "ParserConfig.java" +// line 778 "ParserConfig.java" { cs = JSON_integer_start; } -// line 489 "ParserConfig.rl" +// line 466 "ParserConfig.rl" int memo = p; -// line 809 "ParserConfig.java" +// line 786 "ParserConfig.java" { int _klen; int _trans = 0; @@ -886,13 +863,13 @@ else if ( data[p] > _JSON_integer_trans_keys[_mid+1] ) switch ( _JSON_integer_actions[_acts++] ) { case 0: -// line 467 "ParserConfig.rl" +// line 444 "ParserConfig.rl" { p--; { p += 1; _goto_targ = 5; if (true) continue _goto;} } break; -// line 896 "ParserConfig.java" +// line 873 "ParserConfig.java" } } } @@ -912,7 +889,7 @@ else if ( data[p] > _JSON_integer_trans_keys[_mid+1] ) break; } } -// line 491 "ParserConfig.rl" +// line 468 "ParserConfig.rl" if (cs < JSON_integer_first_final) { return -1; @@ -932,7 +909,7 @@ RubyInteger bytesToInum(Ruby runtime, ByteList num) { } -// line 936 "ParserConfig.java" +// line 913 "ParserConfig.java" private static byte[] init__JSON_float_actions_0() { return new byte [] { @@ -1034,7 +1011,7 @@ private static byte[] init__JSON_float_trans_actions_0() static final int JSON_float_en_main = 1; -// line 524 "ParserConfig.rl" +// line 501 "ParserConfig.rl" void parseFloat(ThreadContext context, ParserResult res, int p, int pe) { @@ -1046,22 +1023,22 @@ void parseFloat(ThreadContext context, ParserResult res, int p, int pe) { final ByteList num = absSubSequence(p, new_p); IRubyObject number = config.decimalFactory.apply(context, num); - res.update(number, new_p + 1); + res.update(config.onLoad(context, number), new_p + 1); } int parseFloatInternal(int p, int pe) { int cs; -// line 1057 "ParserConfig.java" +// line 1034 "ParserConfig.java" { cs = JSON_float_start; } -// line 542 "ParserConfig.rl" +// line 519 "ParserConfig.rl" int memo = p; -// line 1065 "ParserConfig.java" +// line 1042 "ParserConfig.java" { int _klen; int _trans = 0; @@ -1142,13 +1119,13 @@ else if ( data[p] > _JSON_float_trans_keys[_mid+1] ) switch ( _JSON_float_actions[_acts++] ) { case 0: -// line 515 "ParserConfig.rl" +// line 492 "ParserConfig.rl" { p--; { p += 1; _goto_targ = 5; if (true) continue _goto;} } break; -// line 1152 "ParserConfig.java" +// line 1129 "ParserConfig.java" } } } @@ -1168,7 +1145,7 @@ else if ( data[p] > _JSON_float_trans_keys[_mid+1] ) break; } } -// line 544 "ParserConfig.rl" +// line 521 "ParserConfig.rl" if (cs < JSON_float_first_final) { return -1; @@ -1178,7 +1155,7 @@ else if ( data[p] > _JSON_float_trans_keys[_mid+1] ) } -// line 1182 "ParserConfig.java" +// line 1159 "ParserConfig.java" private static byte[] init__JSON_string_actions_0() { return new byte [] { @@ -1280,7 +1257,7 @@ private static byte[] init__JSON_string_trans_actions_0() static final int JSON_string_en_main = 1; -// line 583 "ParserConfig.rl" +// line 560 "ParserConfig.rl" void parseString(ThreadContext context, ParserResult res, int p, int pe) { @@ -1288,15 +1265,15 @@ void parseString(ThreadContext context, ParserResult res, int p, int pe) { IRubyObject result = null; -// line 1292 "ParserConfig.java" +// line 1269 "ParserConfig.java" { cs = JSON_string_start; } -// line 590 "ParserConfig.rl" +// line 567 "ParserConfig.rl" int memo = p; -// line 1300 "ParserConfig.java" +// line 1277 "ParserConfig.java" { int _klen; int _trans = 0; @@ -1377,7 +1354,7 @@ else if ( data[p] > _JSON_string_trans_keys[_mid+1] ) switch ( _JSON_string_actions[_acts++] ) { case 0: -// line 558 "ParserConfig.rl" +// line 535 "ParserConfig.rl" { int offset = byteList.begin(); ByteList decoded = decoder.decode(context, byteList, memo + 1 - offset, @@ -1392,13 +1369,13 @@ else if ( data[p] > _JSON_string_trans_keys[_mid+1] ) } break; case 1: -// line 571 "ParserConfig.rl" +// line 548 "ParserConfig.rl" { p--; { p += 1; _goto_targ = 5; if (true) continue _goto;} } break; -// line 1402 "ParserConfig.java" +// line 1379 "ParserConfig.java" } } } @@ -1418,26 +1395,7 @@ else if ( data[p] > _JSON_string_trans_keys[_mid+1] ) break; } } -// line 592 "ParserConfig.rl" - - if (config.createAdditions) { - RubyHash matchString = config.match_string; - if (matchString != null) { - final IRubyObject[] memoArray = { result, null }; - try { - matchString.visitAll(context, MATCH_VISITOR, memoArray); - } catch (JumpException e) { } - if (memoArray[1] != null) { - RubyClass klass = (RubyClass) memoArray[1]; - if (isObjCreateable(context, klass)) { - if (config.deprecatedCreateAdditions) { - context.runtime.getWarnings().warn("JSON.load implicit support for `create_additions: true` is deprecated and will be removed in 3.0, use JSON.unsafe_load or explicitly pass `create_additions: true`"); - } - result = klass.callMethod(context, "json_create", result); - } - } - } - } +// line 569 "ParserConfig.rl" if (cs >= JSON_string_first_final && result != null) { if (result instanceof RubyString) { @@ -1448,9 +1406,9 @@ else if ( data[p] > _JSON_string_trans_keys[_mid+1] ) string.setFrozen(true); string = context.runtime.freezeAndDedupString(string); } - res.update(string, p + 1); + res.update(config.onLoad(context, string), p + 1); } else { - res.update(result, p + 1); + res.update(config.onLoad(context, result), p + 1); } } else { res.update(null, p + 1); @@ -1458,7 +1416,7 @@ else if ( data[p] > _JSON_string_trans_keys[_mid+1] ) } -// line 1462 "ParserConfig.java" +// line 1420 "ParserConfig.java" private static byte[] init__JSON_array_actions_0() { return new byte [] { @@ -1625,7 +1583,7 @@ private static byte[] init__JSON_array_trans_actions_0() static final int JSON_array_en_main = 1; -// line 669 "ParserConfig.rl" +// line 623 "ParserConfig.rl" void parseArray(ThreadContext context, ParserResult res, int p, int pe) { @@ -1636,23 +1594,17 @@ void parseArray(ThreadContext context, ParserResult res, int p, int pe) { "nesting of " + currentNesting + " is too deep"); } - IRubyObject result; - if (config.arrayClass == context.runtime.getArray()) { - result = RubyArray.newArray(context.runtime); - } else { - result = config.arrayClass.newInstance(context, - IRubyObject.NULL_ARRAY, Block.NULL_BLOCK); - } + IRubyObject result = RubyArray.newArray(context.runtime); -// line 1649 "ParserConfig.java" +// line 1601 "ParserConfig.java" { cs = JSON_array_start; } -// line 688 "ParserConfig.rl" +// line 636 "ParserConfig.rl" -// line 1656 "ParserConfig.java" +// line 1608 "ParserConfig.java" { int _klen; int _trans = 0; @@ -1695,7 +1647,7 @@ else if ( _widec > _JSON_array_cond_keys[_mid+1] ) case 0: { _widec = 65536 + (data[p] - 0); if ( -// line 636 "ParserConfig.rl" +// line 594 "ParserConfig.rl" config.allowTrailingComma ) _widec += 65536; break; } @@ -1765,30 +1717,26 @@ else if ( _widec > _JSON_array_trans_keys[_mid+1] ) switch ( _JSON_array_actions[_acts++] ) { case 0: -// line 638 "ParserConfig.rl" +// line 596 "ParserConfig.rl" { parseValue(context, res, p, pe); if (res.result == null) { p--; { p += 1; _goto_targ = 5; if (true) continue _goto;} } else { - if (config.arrayClass == context.runtime.getArray()) { - ((RubyArray)result).append(res.result); - } else { - result.callMethod(context, "<<", res.result); - } + ((RubyArray)result).append(res.result); {p = (( res.p))-1;} } } break; case 1: -// line 653 "ParserConfig.rl" +// line 607 "ParserConfig.rl" { p--; { p += 1; _goto_targ = 5; if (true) continue _goto;} } break; -// line 1792 "ParserConfig.java" +// line 1740 "ParserConfig.java" } } } @@ -1808,17 +1756,17 @@ else if ( _widec > _JSON_array_trans_keys[_mid+1] ) break; } } -// line 689 "ParserConfig.rl" +// line 637 "ParserConfig.rl" if (cs >= JSON_array_first_final) { - res.update(result, p + 1); + res.update(config.onLoad(context, result), p + 1); } else { throw unexpectedToken(context, p, pe); } } -// line 1822 "ParserConfig.java" +// line 1770 "ParserConfig.java" private static byte[] init__JSON_object_actions_0() { return new byte [] { @@ -1995,13 +1943,12 @@ private static byte[] init__JSON_object_trans_actions_0() static final int JSON_object_en_main = 1; -// line 750 "ParserConfig.rl" +// line 694 "ParserConfig.rl" void parseObject(ThreadContext context, ParserResult res, int p, int pe) { int cs; IRubyObject lastName = null; - boolean objectDefault = true; if (config.maxNesting > 0 && currentNesting > config.maxNesting) { throw newException(context, Utils.M_NESTING_ERROR, @@ -2010,24 +1957,17 @@ void parseObject(ThreadContext context, ParserResult res, int p, int pe) { // this is guaranteed to be a RubyHash due to the earlier // allocator test at OptionsReader#getClass - IRubyObject result; - if (config.objectClass == context.runtime.getHash()) { - result = RubyHash.newHash(context.runtime); - } else { - objectDefault = false; - result = config.objectClass.newInstance(context, - IRubyObject.NULL_ARRAY, Block.NULL_BLOCK); - } + IRubyObject result = RubyHash.newHash(context.runtime); -// line 2024 "ParserConfig.java" +// line 1964 "ParserConfig.java" { cs = JSON_object_start; } -// line 774 "ParserConfig.rl" +// line 710 "ParserConfig.rl" -// line 2031 "ParserConfig.java" +// line 1971 "ParserConfig.java" { int _klen; int _trans = 0; @@ -2070,7 +2010,7 @@ else if ( _widec > _JSON_object_cond_keys[_mid+1] ) case 0: { _widec = 65536 + (data[p] - 0); if ( -// line 703 "ParserConfig.rl" +// line 651 "ParserConfig.rl" config.allowTrailingComma ) _widec += 65536; break; } @@ -2140,24 +2080,20 @@ else if ( _widec > _JSON_object_trans_keys[_mid+1] ) switch ( _JSON_object_actions[_acts++] ) { case 0: -// line 705 "ParserConfig.rl" +// line 653 "ParserConfig.rl" { parseValue(context, res, p, pe); if (res.result == null) { p--; { p += 1; _goto_targ = 5; if (true) continue _goto;} } else { - if (config.objectClass == context.runtime.getHash()) { - ((RubyHash)result).op_aset(context, lastName, res.result); - } else { - Helpers.invoke(context, result, "[]=", lastName, res.result); - } + ((RubyHash)result).op_aset(context, lastName, res.result); {p = (( res.p))-1;} } } break; case 1: -// line 720 "ParserConfig.rl" +// line 664 "ParserConfig.rl" { parseString(context, res, p, pe); if (res.result == null) { @@ -2175,13 +2111,13 @@ else if ( _widec > _JSON_object_trans_keys[_mid+1] ) } break; case 2: -// line 736 "ParserConfig.rl" +// line 680 "ParserConfig.rl" { p--; { p += 1; _goto_targ = 5; if (true) continue _goto;} } break; -// line 2185 "ParserConfig.java" +// line 2121 "ParserConfig.java" } } } @@ -2201,42 +2137,18 @@ else if ( _widec > _JSON_object_trans_keys[_mid+1] ) break; } } -// line 775 "ParserConfig.rl" +// line 711 "ParserConfig.rl" if (cs < JSON_object_first_final) { res.update(null, p + 1); return; } - IRubyObject returnedResult = result; - - // attempt to de-serialize object - if (config.createAdditions) { - IRubyObject vKlassName; - if (objectDefault) { - vKlassName = ((RubyHash)result).op_aref(context, config.createId); - } else { - vKlassName = result.callMethod(context, "[]", config.createId); - } - - if (!vKlassName.isNil()) { - // might throw ArgumentError, we let it propagate - IRubyObject klass = config.info.jsonModule.get(). - callMethod(context, "deep_const_get", vKlassName); - if (isObjCreateable(context, klass)) { - if (config.deprecatedCreateAdditions) { - context.runtime.getWarnings().warn("JSON.load implicit support for `create_additions: true` is deprecated and will be removed in 3.0, use JSON.unsafe_load or explicitly pass `create_additions: true`"); - } - - returnedResult = klass.callMethod(context, "json_create", result); - } - } - } - res.update(returnedResult, p + 1); + res.update(config.onLoad(context, result), p + 1); } -// line 2240 "ParserConfig.java" +// line 2152 "ParserConfig.java" private static byte[] init__JSON_actions_0() { return new byte [] { @@ -2339,7 +2251,7 @@ private static byte[] init__JSON_trans_actions_0() static final int JSON_en_main = 1; -// line 828 "ParserConfig.rl" +// line 740 "ParserConfig.rl" public IRubyObject parseImplementation(ThreadContext context) { @@ -2349,16 +2261,16 @@ public IRubyObject parseImplementation(ThreadContext context) { ParserResult res = new ParserResult(); -// line 2353 "ParserConfig.java" +// line 2265 "ParserConfig.java" { cs = JSON_start; } -// line 837 "ParserConfig.rl" +// line 749 "ParserConfig.rl" p = byteList.begin(); pe = p + byteList.length(); -// line 2362 "ParserConfig.java" +// line 2274 "ParserConfig.java" { int _klen; int _trans = 0; @@ -2439,7 +2351,7 @@ else if ( data[p] > _JSON_trans_keys[_mid+1] ) switch ( _JSON_actions[_acts++] ) { case 0: -// line 814 "ParserConfig.rl" +// line 726 "ParserConfig.rl" { parseValue(context, res, p, pe); if (res.result == null) { @@ -2451,7 +2363,7 @@ else if ( data[p] > _JSON_trans_keys[_mid+1] ) } } break; -// line 2455 "ParserConfig.java" +// line 2367 "ParserConfig.java" } } } @@ -2471,7 +2383,7 @@ else if ( data[p] > _JSON_trans_keys[_mid+1] ) break; } } -// line 840 "ParserConfig.rl" +// line 752 "ParserConfig.rl" if (cs >= JSON_first_final && p == pe) { return result; diff --git a/java/src/json/ext/ParserConfig.rl b/java/src/json/ext/ParserConfig.rl index 8688a1016..ce0d9438e 100644 --- a/java/src/json/ext/ParserConfig.rl +++ b/java/src/json/ext/ParserConfig.rl @@ -15,6 +15,7 @@ import org.jruby.RubyFloat; import org.jruby.RubyHash; import org.jruby.RubyInteger; import org.jruby.RubyObject; +import org.jruby.RubyProc; import org.jruby.RubyString; import org.jruby.anno.JRubyMethod; import org.jruby.exceptions.JumpException; @@ -48,16 +49,12 @@ import static org.jruby.util.ConvertDouble.DoubleConverter; */ public class ParserConfig extends RubyObject { private final RuntimeInfo info; - private RubyString createId; - private boolean createAdditions; - private boolean deprecatedCreateAdditions; private int maxNesting; private boolean allowNaN; private boolean allowTrailingComma; private boolean symbolizeNames; private boolean freeze; - private RubyClass objectClass; - private RubyClass arrayClass; + private RubyProc onLoadProc; private RubyClass decimalClass; BiFunction decimalFactory; private RubyHash match_string; @@ -179,25 +176,9 @@ public class ParserConfig extends RubyObject { this.allowTrailingComma = opts.getBool("allow_trailing_comma", false); this.symbolizeNames = opts.getBool("symbolize_names", false); this.freeze = opts.getBool("freeze", false); - this.createId = opts.getString("create_id", getCreateId(context)); + this.onLoadProc = opts.getProc("on_load"); - IRubyObject additions = opts.get("create_additions"); - this.createAdditions = false; - this.deprecatedCreateAdditions = false; - - if (additions != null) { - if (additions.isNil()) { - this.createAdditions = true; - this.deprecatedCreateAdditions = true; - } else { - this.createAdditions = opts.getBool("create_additions", false); - } - } - - this.objectClass = opts.getClass("object_class", runtime.getHash()); - this.arrayClass = opts.getClass("array_class", runtime.getArray()); this.decimalClass = opts.getClass("decimal_class", null); - this.match_string = opts.getHash("match_string"); if (decimalClass == null) { this.decimalFactory = this::createFloat; @@ -207,13 +188,17 @@ public class ParserConfig extends RubyObject { this.decimalFactory = this::createCustomDecimal; } - if(symbolizeNames && createAdditions) { - throw runtime.newArgumentError("options :symbolize_names and :create_additions cannot be used in conjunction"); - } - return this; } + public IRubyObject onLoad(ThreadContext context, IRubyObject object) { + if (onLoadProc == null) { + return object; + } else { + return onLoadProc.call(context, object); + } + } + /** * Checks the given string's encoding. If a non-UTF-8 encoding is detected, * a converted copy is returned. @@ -264,14 +249,6 @@ public class ParserConfig extends RubyObject { return decimalClass.newInstance(context, context.runtime.newString(num), Block.NULL_BLOCK); } - private static boolean isObjCreateable(final ThreadContext context, IRubyObject klass) { - if (klass.respondsTo("json_creatable?")) { - return klass.callMethod(context, "json_creatable?").isTrue(); - } else { - return klass.respondsTo("json_create"); - } - } - /** * A string parsing session. * @@ -479,7 +456,7 @@ public class ParserConfig extends RubyObject { return; } RubyInteger number = createInteger(context, p, new_p); - res.update(number, new_p + 1); + res.update(config.onLoad(context, number), new_p + 1); } int parseIntegerInternal(int p, int pe) { @@ -532,7 +509,7 @@ public class ParserConfig extends RubyObject { final ByteList num = absSubSequence(p, new_p); IRubyObject number = config.decimalFactory.apply(context, num); - res.update(number, new_p + 1); + res.update(config.onLoad(context, number), new_p + 1); } int parseFloatInternal(int p, int pe) { @@ -590,25 +567,6 @@ public class ParserConfig extends RubyObject { int memo = p; %% write exec; - if (config.createAdditions) { - RubyHash matchString = config.match_string; - if (matchString != null) { - final IRubyObject[] memoArray = { result, null }; - try { - matchString.visitAll(context, MATCH_VISITOR, memoArray); - } catch (JumpException e) { } - if (memoArray[1] != null) { - RubyClass klass = (RubyClass) memoArray[1]; - if (isObjCreateable(context, klass)) { - if (config.deprecatedCreateAdditions) { - context.runtime.getWarnings().warn("JSON.load implicit support for `create_additions: true` is deprecated and will be removed in 3.0, use JSON.unsafe_load or explicitly pass `create_additions: true`"); - } - result = klass.callMethod(context, "json_create", result); - } - } - } - } - if (cs >= JSON_string_first_final && result != null) { if (result instanceof RubyString) { RubyString string = (RubyString)result; @@ -618,9 +576,9 @@ public class ParserConfig extends RubyObject { string.setFrozen(true); string = context.runtime.freezeAndDedupString(string); } - res.update(string, p + 1); + res.update(config.onLoad(context, string), p + 1); } else { - res.update(result, p + 1); + res.update(config.onLoad(context, result), p + 1); } } else { res.update(null, p + 1); @@ -641,11 +599,7 @@ public class ParserConfig extends RubyObject { fhold; fbreak; } else { - if (config.arrayClass == context.runtime.getArray()) { - ((RubyArray)result).append(res.result); - } else { - result.callMethod(context, "<<", res.result); - } + ((RubyArray)result).append(res.result); fexec res.p; } } @@ -676,19 +630,13 @@ public class ParserConfig extends RubyObject { "nesting of " + currentNesting + " is too deep"); } - IRubyObject result; - if (config.arrayClass == context.runtime.getArray()) { - result = RubyArray.newArray(context.runtime); - } else { - result = config.arrayClass.newInstance(context, - IRubyObject.NULL_ARRAY, Block.NULL_BLOCK); - } + IRubyObject result = RubyArray.newArray(context.runtime); %% write init; %% write exec; if (cs >= JSON_array_first_final) { - res.update(result, p + 1); + res.update(config.onLoad(context, result), p + 1); } else { throw unexpectedToken(context, p, pe); } @@ -708,11 +656,7 @@ public class ParserConfig extends RubyObject { fhold; fbreak; } else { - if (config.objectClass == context.runtime.getHash()) { - ((RubyHash)result).op_aset(context, lastName, res.result); - } else { - Helpers.invoke(context, result, "[]=", lastName, res.result); - } + ((RubyHash)result).op_aset(context, lastName, res.result); fexec res.p; } } @@ -752,7 +696,6 @@ public class ParserConfig extends RubyObject { void parseObject(ThreadContext context, ParserResult res, int p, int pe) { int cs; IRubyObject lastName = null; - boolean objectDefault = true; if (config.maxNesting > 0 && currentNesting > config.maxNesting) { throw newException(context, Utils.M_NESTING_ERROR, @@ -761,14 +704,7 @@ public class ParserConfig extends RubyObject { // this is guaranteed to be a RubyHash due to the earlier // allocator test at OptionsReader#getClass - IRubyObject result; - if (config.objectClass == context.runtime.getHash()) { - result = RubyHash.newHash(context.runtime); - } else { - objectDefault = false; - result = config.objectClass.newInstance(context, - IRubyObject.NULL_ARRAY, Block.NULL_BLOCK); - } + IRubyObject result = RubyHash.newHash(context.runtime); %% write init; %% write exec; @@ -778,31 +714,7 @@ public class ParserConfig extends RubyObject { return; } - IRubyObject returnedResult = result; - - // attempt to de-serialize object - if (config.createAdditions) { - IRubyObject vKlassName; - if (objectDefault) { - vKlassName = ((RubyHash)result).op_aref(context, config.createId); - } else { - vKlassName = result.callMethod(context, "[]", config.createId); - } - - if (!vKlassName.isNil()) { - // might throw ArgumentError, we let it propagate - IRubyObject klass = config.info.jsonModule.get(). - callMethod(context, "deep_const_get", vKlassName); - if (isObjCreateable(context, klass)) { - if (config.deprecatedCreateAdditions) { - context.runtime.getWarnings().warn("JSON.load implicit support for `create_additions: true` is deprecated and will be removed in 3.0, use JSON.unsafe_load or explicitly pass `create_additions: true`"); - } - - returnedResult = klass.callMethod(context, "json_create", result); - } - } - } - res.update(returnedResult, p + 1); + res.update(config.onLoad(context, result), p + 1); } %%{ diff --git a/lib/json/common.rb b/lib/json/common.rb index 9e9d6fb3e..a542bdb36 100644 --- a/lib/json/common.rb +++ b/lib/json/common.rb @@ -5,6 +5,112 @@ module JSON autoload :GenericObject, 'json/generic_object' + module ParserOptions # :nodoc: + class << self + def prepare(opts) + if opts[:object_class] || opts[:array_class] + opts = opts.dup + on_load = opts[:on_load] + + on_load = object_class_proc(opts[:object_class], on_load) if opts[:object_class] + on_load = array_class_proc(opts[:array_class], on_load) if opts[:array_class] + opts[:on_load] = on_load + end + + if opts.fetch(:create_additions, false) != false + opts = create_additions_proc(opts) + end + + opts + end + + private + + def object_class_proc(object_class, on_load) + ->(obj) do + if Hash === obj + object = object_class.new + obj.each { |k, v| object[k] = v } + obj = object + end + on_load.nil? ? obj : on_load.call(obj) + end + end + + def array_class_proc(array_class, on_load) + ->(obj) do + if Array === obj + array = array_class.new + obj.each { |v| array << v } + obj = array + end + on_load.nil? ? obj : on_load.call(obj) + end + end + + # TODO: exact :create_additions support to another gem for version 3.0 + def create_additions_proc(opts) + if opts[:symbolize_names] + raise ArgumentError, "options :symbolize_names and :create_additions cannot be used in conjunction" + end + + opts = opts.dup + create_additions = opts.fetch(:create_additions, false) + on_load = opts[:on_load] + object_class = opts[:object_class] || Hash + + opts[:on_load] = ->(object) do + case object + when String + opts[:match_string]&.each do |pattern, klass| + if match = pattern.match(object) + create_additions_warning if create_additions.nil? + object = klass.json_create(object) + break + end + end + when object_class + if opts[:create_additions] != false + if class_name = object[JSON.create_id] + klass = JSON.deep_const_get(class_name) + if (klass.respond_to?(:json_creatable?) && klass.json_creatable?) || klass.respond_to?(:json_create) + create_additions_warning if create_additions.nil? + object = klass.json_create(object) + end + end + end + end + + on_load.nil? ? object : on_load.call(object) + end + + opts + end + + GEM_ROOT = File.expand_path("../../../", __FILE__) + "/" + def create_additions_warning + message = "JSON.load implicit support for `create_additions: true` is deprecated " \ + "and will be removed in 3.0, use JSON.unsafe_load or explicitly " \ + "pass `create_additions: true`" + + uplevel = 4 + caller_locations(uplevel, 10).each do |frame| + if frame.path.nil? || frame.path.start_with?(GEM_ROOT) || frame.path.end_with?("/truffle/cext_ruby.rb", ".c") + uplevel += 1 + else + break + end + end + + if RUBY_VERSION >= "3.0" + warn(message, uplevel: uplevel - 1, category: :deprecated) + else + warn(message, uplevel: uplevel - 1) + end + end + end + end + class << self # :call-seq: # JSON[object] -> new_array or new_string @@ -236,9 +342,16 @@ def to_json(state = nil, *) # JSON.parse('') # def parse(source, opts = nil) + opts = ParserOptions.prepare(opts) unless opts.nil? Parser.parse(source, opts) end + PARSE_L_OPTIONS = { + max_nesting: false, + allow_nan: true, + }.freeze + private_constant :PARSE_L_OPTIONS + # :call-seq: # JSON.parse!(source, opts) -> object # @@ -251,12 +364,11 @@ def parse(source, opts = nil) # which disables checking for nesting depth. # - Option +allow_nan+, if not provided, defaults to +true+. def parse!(source, opts = nil) - options = { - :max_nesting => false, - :allow_nan => true - } - options.merge!(opts) if opts - Parser.new(source, options).parse + if opts.nil? + parse(source, PARSE_L_OPTIONS) + else + parse(source, PARSE_L_OPTIONS.merge(opts)) + end end # :call-seq: @@ -739,23 +851,13 @@ def load(source, proc = nil, options = nil) if opts[:allow_blank] && (source.nil? || source.empty?) source = 'null' end - result = parse(source, opts) - recurse_proc(result, &proc) if proc - result - end - # Recursively calls passed _Proc_ if the parsed data structure is an _Array_ or _Hash_ - def recurse_proc(result, &proc) # :nodoc: - case result - when Array - result.each { |x| recurse_proc x, &proc } - proc.call result - when Hash - result.each { |x, y| recurse_proc x, &proc; recurse_proc y, &proc } - proc.call result - else - proc.call result + if proc + opts = opts.dup + opts[:on_load] = proc.to_proc end + + parse(source, opts) end # Sets or returns the default options for the JSON.dump method. @@ -869,10 +971,9 @@ def initialize(options = nil, &as_json) options[:strict] = true end options[:as_json] = as_json if as_json - options[:create_additions] = false unless options.key?(:create_additions) @state = State.new(options).freeze - @parser_config = Ext::Parser::Config.new(options) + @parser_config = Ext::Parser::Config.new(ParserOptions.prepare(options)) end # call-seq: diff --git a/test/json/json_addition_test.rb b/test/json/json_addition_test.rb index 4d8d18687..a98e06df8 100644 --- a/test/json/json_addition_test.rb +++ b/test/json/json_addition_test.rb @@ -151,7 +151,12 @@ def test_core end def test_deprecated_load_create_additions - assert_deprecated_warning(/use JSON\.unsafe_load/) do + pattern = /json_addition_test\.rb.*use JSON\.unsafe_load/ + if RUBY_ENGINE == 'truffleruby' + pattern = /use JSON\.unsafe_load/ + end + + assert_deprecated_warning(pattern) do JSON.load(JSON.dump(Time.now)) end end diff --git a/test/json/json_common_interface_test.rb b/test/json/json_common_interface_test.rb index 96f7ccdf9..3f4f27439 100644 --- a/test/json/json_common_interface_test.rb +++ b/test/json/json_common_interface_test.rb @@ -110,7 +110,7 @@ def test_load def test_load_with_proc visited = [] - JSON.load('{"foo": [1, 2, 3], "bar": {"baz": "plop"}}', proc { |o| visited << JSON.dump(o) }) + JSON.load('{"foo": [1, 2, 3], "bar": {"baz": "plop"}}', proc { |o| visited << JSON.dump(o); o }) expected = [ '"foo"',