diff --git a/CHANGES.md b/CHANGES.md index b13642ae5..84fca3bb1 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,6 +2,22 @@ ### Unreleased +- The `allow_comments` parsing option now default to `false`. +- The `allow_duplicate_key` option now defaults to `false`, for both parsing and generating JSON. +- Removed `Kernel#j` and `Kernel#jj`. +- Removed `JSON.load_default_options`. +- Removed `JSON.unsafe_load_default_options`. +- Removed `JSON.dump_default_options`. +- Removed `JSON::State#[]` and `JSON::State#[]=`. +- Removed `JSON.unparse`. +- Removed `JSON.fast_generate`. +- Removed `JSON.fast_unparse`. +- Removed `JSON.pretty_unparse`. +- Removed `JSON.restore`. +- Removed `JSON::PRETTY_STATE_PROTOTYPE`. +- Removed the insecure `create_additions` option. +- Removed `JSON::GenericObject`. + ### 2026-07-13 (2.21.1) * Fix a compilation issue on Window and Microsoft Visual C++. diff --git a/README.md b/README.md index 5e6ffe48f..79a8c3f3e 100644 --- a/README.md +++ b/README.md @@ -143,79 +143,7 @@ posts_json.map! { |post_json| JSON::Fragment.new(post_json) } JSON.generate({ posts: posts_json, count: posts_json.count }) ``` -## Round-tripping arbitrary types - -> [!CAUTION] -> You should never use `JSON.unsafe_load` nor `JSON.parse(str, create_additions: true)` to parse untrusted user input, -> as it can lead to remote code execution vulnerabilities. - -To create a JSON document from a ruby data structure, you can call -`JSON.generate` like that: - -```ruby -json = JSON.generate [1, 2, {"a"=>3.141}, false, true, nil, 4..10] -# => "[1,2,{\"a\":3.141},false,true,null,\"4..10\"]" -``` - -To get back a ruby data structure from a JSON document, you have to call -JSON.parse on it: - -```ruby -JSON.parse json -# => [1, 2, {"a"=>3.141}, false, true, nil, "4..10"] -``` - -Note, that the range from the original data structure is a simple -string now. The reason for this is, that JSON doesn't support ranges -or arbitrary classes. In this case the json library falls back to call -`Object#to_json`, which is the same as `#to_s.to_json`. - -It's possible to add JSON support serialization to arbitrary classes by -simply implementing a more specialized version of the `#to_json method`, that -should return a JSON object (a hash converted to JSON with `#to_json`) like -this (don't forget the `*a` for all the arguments): - -```ruby -class Range - def to_json(*a) - { - 'json_class' => self.class.name, # = 'Range' - 'data' => [ first, last, exclude_end? ] - }.to_json(*a) - end -end -``` - -The hash key `json_class` is the class, that will be asked to deserialise the -JSON representation later. In this case it's `Range`, but any namespace of -the form `A::B` or `::A::B` will do. All other keys are arbitrary and can be -used to store the necessary data to configure the object to be deserialised. - -If the key `json_class` is found in a JSON object, the JSON parser checks -if the given class responds to the `json_create` class method. If so, it is -called with the JSON object converted to a Ruby hash. So a range can -be deserialised by implementing `Range.json_create` like this: - -```ruby -class Range - def self.json_create(o) - new(*o['data']) - end -end -``` - -Now it possible to serialise/deserialise ranges as well: - -```ruby -json = JSON.generate [1, 2, {"a"=>3.141}, false, true, nil, 4..10] -# => "[1,2,{\"a\":3.141},false,true,null,{\"json_class\":\"Range\",\"data\":[4,10,false]}]" -JSON.parse json -# => [1, 2, {"a"=>3.141}, false, true, nil, 4..10] -json = JSON.generate [1, 2, {"a"=>3.141}, false, true, nil, 4..10] -# => "[1,2,{\"a\":3.141},false,true,null,{\"json_class\":\"Range\",\"data\":[4,10,false]}]" -JSON.unsafe_load json -# => [1, 2, {"a"=>3.141}, false, true, nil, 4..10] -``` +## Pretty Printing `JSON.generate` always creates the shortest possible string representation of a ruby data structure in one line. This is good for data storage or network diff --git a/benchmark/standalone.rb b/benchmark/standalone.rb index deeb45dce..ebe759c5f 100644 --- a/benchmark/standalone.rb +++ b/benchmark/standalone.rb @@ -32,7 +32,7 @@ JSON.dump(obj) end else - x.report('JSON.load(str)') do # max_nesting: false, allow_nan: true, allow_blank: true, create_additions: true + x.report('JSON.load(str)') do # max_nesting: false, allow_nan: true, allow_blank: true JSON.load(str) end end diff --git a/ext/json/ext/generator/generator.c b/ext/json/ext/generator/generator.c index 6f473325e..a45fd3928 100644 --- a/ext/json/ext/generator/generator.c +++ b/ext/json/ext/generator/generator.c @@ -9,12 +9,6 @@ /* ruby api and some helpers */ -enum duplicate_key_action { - JSON_DEPRECATED = 0, - JSON_IGNORE, - JSON_RAISE, -}; - typedef struct JSON_Generator_StateStruct { VALUE indent; VALUE space; @@ -27,8 +21,7 @@ typedef struct JSON_Generator_StateStruct { long depth; long buffer_initial_length; - enum duplicate_key_action on_duplicate_key; - + bool allow_duplicate_key; bool as_json_single_arg; bool allow_nan; bool ascii_only; @@ -956,9 +949,8 @@ json_inspect_hash_with_mixed_keys(struct hash_foreach_arg *arg) arg->mixed_keys_encountered = true; JSON_Generator_State *state = arg->data->state; - if (state->on_duplicate_key != JSON_IGNORE) { - VALUE do_raise = state->on_duplicate_key == JSON_RAISE ? Qtrue : Qfalse; - rb_funcall(mJSON, rb_intern("on_mixed_keys_hash"), 2, arg->hash, do_raise); + if (!state->allow_duplicate_key) { + rb_funcall(mJSON, rb_intern("on_mixed_keys_hash"), 1, arg->hash); } } @@ -1784,14 +1776,7 @@ static VALUE cState_sort_keys_set(VALUE self, VALUE value) static VALUE cState_allow_duplicate_key_p(VALUE self) { GET_STATE(self); - switch (state->on_duplicate_key) { - case JSON_IGNORE: - return Qtrue; - case JSON_DEPRECATED: - return Qnil; - default: - return Qfalse; - } + return state->allow_duplicate_key ? Qtrue : Qfalse; } /* @@ -1885,7 +1870,7 @@ static int configure_state_i(VALUE key, VALUE val, VALUE _arg) else if (key == sym_script_safe) { state->script_safe = RTEST(val); } else if (key == sym_escape_slash) { state->script_safe = RTEST(val); } else if (key == sym_strict) { state->strict = RTEST(val); } - else if (key == sym_allow_duplicate_key) { state->on_duplicate_key = RTEST(val) ? JSON_IGNORE : JSON_RAISE; } + else if (key == sym_allow_duplicate_key) { state->allow_duplicate_key = RTEST(val); } else if (key == sym_as_json) { VALUE proc = RTEST(val) ? rb_convert_type(val, T_DATA, "Proc", "to_proc") : Qfalse; state->as_json_single_arg = proc && rb_proc_arity(proc) == 1; diff --git a/ext/json/ext/parser/parser.c b/ext/json/ext/parser/parser.c index d1c90ff8d..768931d34 100644 --- a/ext/json/ext/parser/parser.c +++ b/ext/json/ext/parser/parser.c @@ -403,19 +403,13 @@ typedef struct json_frame_stack_struct { json_frame *ptr; } json_frame_stack; -enum deprecatable_action { - JSON_DEPRECATED = 0, - JSON_IGNORE, - JSON_RAISE, -}; - typedef struct JSON_ParserStruct { VALUE on_load_proc; VALUE decimal_class; ID decimal_method_id; - enum deprecatable_action on_duplicate_key; - enum deprecatable_action on_comment; int max_nesting; + bool allow_comments; + bool allow_duplicate_key; bool allow_nan; bool allow_trailing_comma; bool allow_control_characters; @@ -435,7 +429,6 @@ typedef struct JSON_ParserStateStruct { rvalue_cache name_cache; int in_array; int current_nesting; - unsigned int emitted_deprecations; VALUE parser; } JSON_ParserState; @@ -617,17 +610,6 @@ static void cursor_position(JSON_ParserState *state, long *line_out, long *colum *column_out = column; } -static const unsigned int MAX_DEPRECATIONS = 5; - -static void emit_parse_warning(const char *message, JSON_ParserState *state) -{ - long line, column; - cursor_position(state, &line, &column); - - VALUE warning = rb_sprintf("%s at line %ld column %ld", message, line, column); - rb_funcall(mJSON, rb_intern("deprecation_warning"), 1, warning); -} - #define PARSE_ERROR_FRAGMENT_LEN 32 static VALUE build_parse_error_message(const char *format, JSON_ParserState *state) @@ -766,11 +748,10 @@ static uint32_t unescape_unicode(JSON_ParserState *state, const char *sp, const static const rb_data_type_t JSON_ParserConfig_type; -const char *COMMENT_DEPRECATION_MESSAGE = "Encountered comment in JSON. This will raise an error in json 3.0 unless enabled via `allow_comments: true`"; NOINLINE(static) void json_eat_comments(JSON_ParserState *state, JSON_ParserConfig *config, const char *resume_pos) { - if (config->on_comment == JSON_RAISE) { + if (!config->allow_comments) { raise_syntax_error("unexpected token %s", state); } @@ -820,11 +801,6 @@ json_eat_comments(JSON_ParserState *state, JSON_ParserConfig *config, const char raise_parse_error_at("unexpected token %s", state, eos(state) ? rewind_pos : start, eos(state)); break; } - - if (config->on_comment == JSON_DEPRECATED && state->emitted_deprecations < MAX_DEPRECATIONS) { - state->emitted_deprecations++; - emit_parse_warning(COMMENT_DEPRECATION_MESSAGE, state); - } } ALWAYS_INLINE(static) void @@ -1193,17 +1169,6 @@ static VALUE json_find_duplicated_key(size_t count, const VALUE *pairs) return Qfalse; } -NOINLINE(static) void emit_duplicate_key_warning(JSON_ParserState *state, VALUE duplicate_key) -{ - VALUE message = rb_sprintf( - "detected duplicate key %"PRIsVALUE" in JSON object. This will raise an error in json 3.0 unless enabled via `allow_duplicate_key: true`", - rb_inspect(duplicate_key) - ); - - emit_parse_warning(RSTRING_PTR(message), state); - RB_GC_GUARD(message); -} - NORETURN(static) void raise_duplicate_key_error(JSON_ParserState *state, VALUE duplicate_key) { VALUE message = rb_sprintf( @@ -1224,23 +1189,9 @@ NORETURN(static) void raise_duplicate_key_error(JSON_ParserState *state, VALUE d NOINLINE(static) void json_on_duplicate_key(JSON_ParserState *state, JSON_ParserConfig *config, size_t count, const VALUE *pairs) { - switch (config->on_duplicate_key) { - case JSON_IGNORE: - return; - - case JSON_DEPRECATED: - // Only emit the first few deprecations to avoid spamming. - if (state->emitted_deprecations < MAX_DEPRECATIONS) { - state->emitted_deprecations++; - emit_duplicate_key_warning(state, json_find_duplicated_key(count, pairs)); - } - return; - - case JSON_RAISE: - raise_duplicate_key_error(state, json_find_duplicated_key(count, pairs)); - return; + if (!config->allow_duplicate_key) { + raise_duplicate_key_error(state, json_find_duplicated_key(count, pairs)); } - UNREACHABLE; } static inline VALUE json_decode_object(JSON_ParserState *state, JSON_ParserConfig *config, size_t count) @@ -2006,13 +1957,13 @@ static int parser_config_init_i(VALUE key, VALUE val, VALUE data) if (key == sym_max_nesting) { config->max_nesting = RTEST(val) ? FIX2INT(val) : 0; } else if (key == sym_allow_nan) { config->allow_nan = RTEST(val); } else if (key == sym_allow_trailing_comma) { config->allow_trailing_comma = RTEST(val); } - else if (key == sym_allow_comments) { config->on_comment = RTEST(val) ? JSON_IGNORE : JSON_RAISE; } + else if (key == sym_allow_comments) { config->allow_comments = RTEST(val); } else if (key == sym_allow_control_characters) { config->allow_control_characters = RTEST(val); } else if (key == sym_allow_invalid_escape) { config->allow_invalid_escape = 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_on_load) { parser_config_wb_write(self, &config->on_load_proc, RTEST(val) ? val : Qfalse); } - else if (key == sym_allow_duplicate_key) { config->on_duplicate_key = RTEST(val) ? JSON_IGNORE : JSON_RAISE; } + else if (key == sym_allow_duplicate_key) { config->allow_duplicate_key = RTEST(val); } else if (key == sym_decimal_class) { if (RTEST(val)) { if (rb_respond_to(val, i_try_convert)) { @@ -2639,7 +2590,6 @@ static VALUE cResumableParser_clear(VALUE self) parser->state.name_cache.length = 0; parser->state.current_nesting = 0; parser->state.in_array = 1; - parser->state.emitted_deprecations = 0; parser->state.start = parser->state.cursor = parser->state.end = NULL; return self; } diff --git a/java/src/json/ext/Generator.java b/java/src/json/ext/Generator.java index f1617a051..ce1b5b57e 100644 --- a/java/src/json/ext/Generator.java +++ b/java/src/json/ext/Generator.java @@ -553,13 +553,8 @@ private void report(ThreadContext context, Session session) { final GeneratorState state = session.getState(context); if (!state.getAllowDuplicateKey()) { - if (state.getDeprecateDuplicateKey()) { - IRubyObject args[] = new IRubyObject[]{hash, context.getRuntime().getFalse()}; - info.jsonModule.get().callMethod(context, "on_mixed_keys_hash", args); - } else { - IRubyObject args[] = new IRubyObject[]{hash, context.getRuntime().getTrue()}; - info.jsonModule.get().callMethod(context, "on_mixed_keys_hash", args); - } + IRubyObject args[] = new IRubyObject[]{hash}; + info.jsonModule.get().callMethod(context, "on_mixed_keys_hash", args); } } } diff --git a/java/src/json/ext/GeneratorState.java b/java/src/json/ext/GeneratorState.java index 2d20ea461..1086bf6d3 100644 --- a/java/src/json/ext/GeneratorState.java +++ b/java/src/json/ext/GeneratorState.java @@ -35,7 +35,6 @@ */ public class GeneratorState extends RubyObject { private boolean allowDuplicateKey = false; - private boolean deprecateDuplicateKey = true; private static IRubyObject defaultSortKeyProc; @@ -236,7 +235,6 @@ public IRubyObject initialize_copy(ThreadContext context, IRubyObject vOrig) { this.depth = orig.depth; this.allowDuplicateKey = orig.allowDuplicateKey; - this.deprecateDuplicateKey = orig.deprecateDuplicateKey; this.sortKeys = orig.sortKeys; return this; @@ -272,30 +270,6 @@ public IRubyObject generate(ThreadContext context, IRubyObject obj) { return generate(context, obj, context.nil); } - @JRubyMethod(name="[]") - public IRubyObject op_aref(ThreadContext context, IRubyObject vName) { - String name = vName.asJavaString(); - if (getMetaClass().isMethodBound(name, true)) { - return send(context, vName, Block.NULL_BLOCK); - } else { - IRubyObject value = getInstanceVariables().getInstanceVariable("@" + name); - return value == null ? context.nil : value; - } - } - - @JRubyMethod(name="[]=") - public IRubyObject op_aset(ThreadContext context, IRubyObject vName, IRubyObject value) { - checkFrozen(); - String name = vName.asJavaString(); - String nameWriter = name + "="; - if (getMetaClass().isMethodBound(nameWriter, true)) { - return send(context, context.runtime.newString(nameWriter), value, Block.NULL_BLOCK); - } else { - getInstanceVariables().setInstanceVariable("@" + name, value); - } - return context.nil; - } - public ByteList getIndent() { return indent; } @@ -550,8 +524,6 @@ private ByteList prepareByteList(ThreadContext context, IRubyObject value) { public IRubyObject allow_duplicate_key_p(ThreadContext context) { if (allowDuplicateKey) { return context.runtime.getTrue(); - } else if (deprecateDuplicateKey) { - return context.runtime.getNil(); } return context.runtime.getFalse(); } @@ -560,10 +532,6 @@ public boolean getAllowDuplicateKey() { return allowDuplicateKey; } - public boolean getDeprecateDuplicateKey() { - return deprecateDuplicateKey; - } - /** * State#configure(opts) * @@ -608,11 +576,7 @@ public IRubyObject _configure(ThreadContext context, IRubyObject vOpts) { if (depth < 0) { throw context.runtime.newArgumentError("depth must be >= 0 (got: " + depth + ")"); } - - if (opts.hasKey("allow_duplicate_key")) { - this.allowDuplicateKey = opts.getBool("allow_duplicate_key", false); - this.deprecateDuplicateKey = false; - } + this.allowDuplicateKey = opts.getBool("allow_duplicate_key", false); sortKeys = normalizeSortKeys(context, opts.get("sort_keys")); @@ -645,15 +609,7 @@ public RubyHash to_h(ThreadContext context) { result.op_aset(context, runtime.newSymbol("depth"), depth_get(context)); result.op_aset(context, runtime.newSymbol("buffer_initial_length"), buffer_initial_length_get(context)); result.op_aset(context, runtime.newSymbol("sort_keys"), sort_keys_get(context)); - - if (this.allowDuplicateKey) { - if (!this.deprecateDuplicateKey) { - result.op_aset(context, runtime.newSymbol("allow_duplicate_key"), runtime.getTrue()); - } - } - else { - result.op_aset(context, runtime.newSymbol("allow_duplicate_key"), runtime.getFalse()); - } + result.op_aset(context, runtime.newSymbol("allow_duplicate_key"), allow_duplicate_key_p(context)); for (String name: getInstanceVariableNameList()) { result.op_aset(context, runtime.newSymbol(name.substring(1)), getInstanceVariables().getInstanceVariable(name)); diff --git a/java/src/json/ext/Parser.java b/java/src/json/ext/Parser.java index c54d1a7f9..b2be38d15 100644 --- a/java/src/json/ext/Parser.java +++ b/java/src/json/ext/Parser.java @@ -46,11 +46,9 @@ public class Parser extends RubyObject { private boolean allowNaN; private boolean allowTrailingComma; private boolean allowComments; - private boolean deprecateComments; private boolean allowControlCharacters; private boolean allowInvalidEscape; private boolean allowDuplicateKey; - private boolean deprecateDuplicateKey; private boolean symbolizeNames; private boolean freeze; private RubyProc onLoadProc; @@ -97,25 +95,12 @@ public IRubyObject initialize(ThreadContext context, IRubyObject options) { OptionsReader opts = new OptionsReader(context, options); this.maxNesting = opts.getInt("max_nesting", DEFAULT_MAX_NESTING); this.allowNaN = opts.getBool("allow_nan", false); - if (opts.hasKey("allow_comments")) { - this.allowComments = opts.getBool("allow_comments", false); - this.deprecateComments = false; - } else { - this.allowComments = true; - this.deprecateComments = true; - } - + this.allowComments = opts.getBool("allow_comments", false); this.allowControlCharacters = opts.getBool("allow_control_characters", false); this.allowInvalidEscape = opts.getBool("allow_invalid_escape", false); this.allowTrailingComma = opts.getBool("allow_trailing_comma", false); this.symbolizeNames = opts.getBool("symbolize_names", false); - if (opts.hasKey("allow_duplicate_key")) { - this.allowDuplicateKey = opts.getBool("allow_duplicate_key", false); - this.deprecateDuplicateKey = false; - } else { - this.allowDuplicateKey = false; - this.deprecateDuplicateKey = true; - } + this.allowDuplicateKey = opts.getBool("allow_duplicate_key", false); this.freeze = opts.getBool("freeze", false); this.onLoadProc = opts.getProc("on_load"); @@ -585,16 +570,7 @@ private void onDuplicateKey(IRubyObject key) { // match the C parser's message. String keyInspect = key.callMethod(context, "to_s") .callMethod(context, "inspect").asJavaString(); - if (config.deprecateDuplicateKey) { - if (emittedDeprecations < MAX_DEPRECATIONS) { - emittedDeprecations++; - context.runtime.getWarnings().warning( - "detected duplicate key " + keyInspect + " in JSON object. " + - "This will raise an error in json 3.0 unless enabled via `allow_duplicate_key: true`"); - } - } else { - throw newException(Utils.M_PARSER_ERROR, "duplicate key " + keyInspect); - } + throw newException(Utils.M_PARSER_ERROR, "duplicate key " + keyInspect); } private int parseDigits(long value) { @@ -859,15 +835,7 @@ private void eatWhitespace() { private void eatComments() { if (!config.allowComments) { - if (config.deprecateComments) { - if (emittedDeprecations < MAX_DEPRECATIONS) { - emittedDeprecations++; - context.runtime.getWarnings().warning( - "Encountered comment in JSON. This will raise an error in json 3.0 unless enabled via `allow_comments: true`"); - } - } else { - throw unexpectedToken(cursor, end); - } + throw unexpectedToken(cursor, end); } int start = cursor; diff --git a/lib/json.rb b/lib/json.rb index d1e94147d..9d6e969ca 100644 --- a/lib/json.rb +++ b/lib/json.rb @@ -139,18 +139,12 @@ # Option +allow_duplicate_key+ specifies whether duplicate keys in objects # should be ignored or cause an error to be raised: # -# When not specified: -# # The last value is used and a deprecation warning emitted. -# JSON.parse('{"a": 1, "a":2}') => {"a" => 2} -# # warning: detected duplicate keys in JSON object. -# # This will raise an error in json 3.0 unless enabled via `allow_duplicate_key: true` +# When set to +false+, the default: +# JSON.parse('{"a": 1, "a":2}') => duplicate key at line 1 column 1 (JSON::ParserError) # # When set to +true+: # # The last value is used. -# JSON.parse('{"a": 1, "a":2}') => {"a" => 2} -# -# When set to +false+, the future default: -# JSON.parse('{"a": 1, "a":2}') => duplicate key at line 1 column 1 (JSON::ParserError) +# JSON.parse('{"a": 1, "a":2}', allow_duplicate_key: true) => {"a" => 2} # # --- # @@ -188,13 +182,11 @@ # JavaScript style comments (either // comment or /* comment */); # defaults to +false+. # -# When not specified, a deprecation warning is emitted if a comment is encountered. +# When set to +false+, the default: +# JSON.parse('/* comment */ {"a": 1, "a":2}') # unexpected character: '/' at line 1 column 1 (JSON::ParserError) # # When set to +true+, comments are ignored: -# JSON.parse('/* comment */ {"a": 1, "a":2}') # => {"a" => 2} -# -# When set to +false+, the future default: -# JSON.parse('/* comment */ {"a": 1, "a":2}') # unexpected character: '/' at line 1 column 1 (JSON::ParserError) +# JSON.parse('/* comment */ {"a": 1, "a":2} // more comment') # => {"a" => 2} # # --- # @@ -265,11 +257,6 @@ # ruby = JSON.parse(source, {array_class: Set}) # ruby # => # # -# --- -# -# Option +create_additions+ (boolean) specifies whether to use \JSON additions in parsing. -# See {\JSON Additions}[#module-JSON-label-JSON+Additions]. -# # === Generating \JSON # # To generate a Ruby \String containing \JSON data, @@ -358,18 +345,13 @@ # hashes with duplicate keys should be allowed or produce an error. # defaults to emit a deprecation warning. # -# With the default, (not set): -# Warning[:deprecated] = true +# With the default, false: # JSON.generate({ foo: 1, "foo" => 2 }) -# # warning: detected duplicate key "foo" in {foo: 1, "foo" => 2}. -# # This will raise an error in json 3.0 unless enabled via `allow_duplicate_key: true` -# # => '{"foo":1,"foo":2}' -# -# With false -# JSON.generate({ foo: 1, "foo" => 2 }, allow_duplicate_key: false) # # detected duplicate key "foo" in {foo: 1, "foo" => 2} (JSON::GeneratorError) # -# In version 3.0, false will become the default. +# With true +# JSON.generate({ foo: 1, "foo" => 2 }, allow_duplicate_key: true) +# # => '{"foo":1,"foo":2}' # # --- # @@ -452,241 +434,6 @@ # } # } # -# == \JSON Additions -# -# Note that JSON Additions must only be used with trusted data, and is -# deprecated. -# -# When you "round trip" a non-\String object from Ruby to \JSON and back, -# you have a new \String, instead of the object you began with: -# ruby0 = Range.new(0, 2) -# json = JSON.generate(ruby0) -# json # => '0..2"' -# ruby1 = JSON.parse(json) -# ruby1 # => '0..2' -# ruby1.class # => String -# -# You can use \JSON _additions_ to preserve the original object. -# The addition is an extension of a ruby class, so that: -# - \JSON.generate stores more information in the \JSON string. -# - \JSON.parse, called with option +create_additions+, -# uses that information to create a proper Ruby object. -# -# This example shows a \Range being generated into \JSON -# and parsed back into Ruby, both without and with -# the addition for \Range: -# ruby = Range.new(0, 2) -# # This passage does not use the addition for Range. -# json0 = JSON.generate(ruby) -# ruby0 = JSON.parse(json0) -# # This passage uses the addition for Range. -# require 'json/add/range' -# json1 = JSON.generate(ruby) -# ruby1 = JSON.parse(json1, create_additions: true) -# # Make a nice display. -# display = <<~EOT -# Generated JSON: -# Without addition: #{json0} (#{json0.class}) -# With addition: #{json1} (#{json1.class}) -# Parsed JSON: -# Without addition: #{ruby0.inspect} (#{ruby0.class}) -# With addition: #{ruby1.inspect} (#{ruby1.class}) -# EOT -# puts display -# -# This output shows the different results: -# Generated JSON: -# Without addition: "0..2" (String) -# With addition: {"json_class":"Range","a":[0,2,false]} (String) -# Parsed JSON: -# Without addition: "0..2" (String) -# With addition: 0..2 (Range) -# -# The \JSON module includes additions for certain classes. -# You can also craft custom additions. -# See {Custom \JSON Additions}[#module-JSON-label-Custom+JSON+Additions]. -# -# === Built-in Additions -# -# The \JSON module includes additions for certain classes. -# To use an addition, +require+ its source: -# - BigDecimal: require 'json/add/bigdecimal' -# - Complex: require 'json/add/complex' -# - Date: require 'json/add/date' -# - DateTime: require 'json/add/date_time' -# - Exception: require 'json/add/exception' -# - OpenStruct: require 'json/add/ostruct' -# - Range: require 'json/add/range' -# - Rational: require 'json/add/rational' -# - Regexp: require 'json/add/regexp' -# - Set: require 'json/add/set' -# - Struct: require 'json/add/struct' -# - Symbol: require 'json/add/symbol' -# - Time: require 'json/add/time' -# -# To reduce punctuation clutter, the examples below -# show the generated \JSON via +puts+, rather than the usual +inspect+, -# -# \BigDecimal: -# require 'json/add/bigdecimal' -# ruby0 = BigDecimal(0) # 0.0 -# json = JSON.generate(ruby0) # {"json_class":"BigDecimal","b":"27:0.0"} -# ruby1 = JSON.parse(json, create_additions: true) # 0.0 -# ruby1.class # => BigDecimal -# -# \Complex: -# require 'json/add/complex' -# ruby0 = Complex(1+0i) # 1+0i -# json = JSON.generate(ruby0) # {"json_class":"Complex","r":1,"i":0} -# ruby1 = JSON.parse(json, create_additions: true) # 1+0i -# ruby1.class # Complex -# -# \Date: -# require 'json/add/date' -# ruby0 = Date.today # 2020-05-02 -# json = JSON.generate(ruby0) # {"json_class":"Date","y":2020,"m":5,"d":2,"sg":2299161.0} -# ruby1 = JSON.parse(json, create_additions: true) # 2020-05-02 -# ruby1.class # Date -# -# \DateTime: -# require 'json/add/date_time' -# ruby0 = DateTime.now # 2020-05-02T10:38:13-05:00 -# json = JSON.generate(ruby0) # {"json_class":"DateTime","y":2020,"m":5,"d":2,"H":10,"M":38,"S":13,"of":"-5/24","sg":2299161.0} -# ruby1 = JSON.parse(json, create_additions: true) # 2020-05-02T10:38:13-05:00 -# ruby1.class # DateTime -# -# \Exception (and its subclasses including \RuntimeError): -# require 'json/add/exception' -# ruby0 = Exception.new('A message') # A message -# json = JSON.generate(ruby0) # {"json_class":"Exception","m":"A message","b":null} -# ruby1 = JSON.parse(json, create_additions: true) # A message -# ruby1.class # Exception -# ruby0 = RuntimeError.new('Another message') # Another message -# json = JSON.generate(ruby0) # {"json_class":"RuntimeError","m":"Another message","b":null} -# ruby1 = JSON.parse(json, create_additions: true) # Another message -# ruby1.class # RuntimeError -# -# \OpenStruct: -# require 'json/add/ostruct' -# ruby0 = OpenStruct.new(name: 'Matz', language: 'Ruby') # # -# json = JSON.generate(ruby0) # {"json_class":"OpenStruct","t":{"name":"Matz","language":"Ruby"}} -# ruby1 = JSON.parse(json, create_additions: true) # # -# ruby1.class # OpenStruct -# -# \Range: -# require 'json/add/range' -# ruby0 = Range.new(0, 2) # 0..2 -# json = JSON.generate(ruby0) # {"json_class":"Range","a":[0,2,false]} -# ruby1 = JSON.parse(json, create_additions: true) # 0..2 -# ruby1.class # Range -# -# \Rational: -# require 'json/add/rational' -# ruby0 = Rational(1, 3) # 1/3 -# json = JSON.generate(ruby0) # {"json_class":"Rational","n":1,"d":3} -# ruby1 = JSON.parse(json, create_additions: true) # 1/3 -# ruby1.class # Rational -# -# \Regexp: -# require 'json/add/regexp' -# ruby0 = Regexp.new('foo') # (?-mix:foo) -# json = JSON.generate(ruby0) # {"json_class":"Regexp","o":0,"s":"foo"} -# ruby1 = JSON.parse(json, create_additions: true) # (?-mix:foo) -# ruby1.class # Regexp -# -# \Set: -# require 'json/add/set' -# ruby0 = Set.new([0, 1, 2]) # # -# json = JSON.generate(ruby0) # {"json_class":"Set","a":[0,1,2]} -# ruby1 = JSON.parse(json, create_additions: true) # # -# ruby1.class # Set -# -# \Struct: -# require 'json/add/struct' -# Customer = Struct.new(:name, :address) # Customer -# ruby0 = Customer.new("Dave", "123 Main") # # -# json = JSON.generate(ruby0) # {"json_class":"Customer","v":["Dave","123 Main"]} -# ruby1 = JSON.parse(json, create_additions: true) # # -# ruby1.class # Customer -# -# \Symbol: -# require 'json/add/symbol' -# ruby0 = :foo # foo -# json = JSON.generate(ruby0) # {"json_class":"Symbol","s":"foo"} -# ruby1 = JSON.parse(json, create_additions: true) # foo -# ruby1.class # Symbol -# -# \Time: -# require 'json/add/time' -# ruby0 = Time.now # 2020-05-02 11:28:26 -0500 -# json = JSON.generate(ruby0) # {"json_class":"Time","s":1588436906,"n":840560000} -# ruby1 = JSON.parse(json, create_additions: true) # 2020-05-02 11:28:26 -0500 -# ruby1.class # Time -# -# -# === Custom \JSON Additions -# -# In addition to the \JSON additions provided, -# you can craft \JSON additions of your own, -# either for Ruby built-in classes or for user-defined classes. -# -# Here's a user-defined class +Foo+: -# class Foo -# attr_accessor :bar, :baz -# def initialize(bar, baz) -# self.bar = bar -# self.baz = baz -# end -# end -# -# Here's the \JSON addition for it: -# # Extend class Foo with JSON addition. -# class Foo -# # Serialize Foo object with its class name and arguments -# def to_json(*args) -# { -# JSON.create_id => self.class.name, -# 'a' => [ bar, baz ] -# }.to_json(*args) -# end -# # Deserialize JSON string by constructing new Foo object with arguments. -# def self.json_create(object) -# new(*object['a']) -# end -# end -# -# Demonstration: -# require 'json' -# # This Foo object has no custom addition. -# foo0 = Foo.new(0, 1) -# json0 = JSON.generate(foo0) -# obj0 = JSON.parse(json0) -# # Lood the custom addition. -# require_relative 'foo_addition' -# # This foo has the custom addition. -# foo1 = Foo.new(0, 1) -# json1 = JSON.generate(foo1) -# obj1 = JSON.parse(json1, create_additions: true) -# # Make a nice display. -# display = <<~EOT -# Generated JSON: -# Without custom addition: #{json0} (#{json0.class}) -# With custom addition: #{json1} (#{json1.class}) -# Parsed JSON: -# Without custom addition: #{obj0.inspect} (#{obj0.class}) -# With custom addition: #{obj1.inspect} (#{obj1.class}) -# EOT -# puts display -# -# Output: -# -# Generated JSON: -# Without custom addition: "#" (String) -# With custom addition: {"json_class":"Foo","a":[0,1]} (String) -# Parsed JSON: -# Without custom addition: "#" (String) -# With custom addition: # (Foo) -# module JSON require 'json/version' require 'json/ext' diff --git a/lib/json/add/bigdecimal.rb b/lib/json/add/bigdecimal.rb deleted file mode 100644 index 5dbc12c07..000000000 --- a/lib/json/add/bigdecimal.rb +++ /dev/null @@ -1,58 +0,0 @@ -# frozen_string_literal: true -unless defined?(::JSON::JSON_LOADED) and ::JSON::JSON_LOADED - require 'json' -end -begin - require 'bigdecimal' -rescue LoadError -end - -class BigDecimal - - # See #as_json. - def self.json_create(object) - BigDecimal._load object['b'] - end - - # Methods BigDecimal#as_json and +BigDecimal.json_create+ may be used - # to serialize and deserialize a \BigDecimal object; - # see Marshal[https://docs.ruby-lang.org/en/master/Marshal.html]. - # - # \Method BigDecimal#as_json serializes +self+, - # returning a 2-element hash representing +self+: - # - # require 'json/add/bigdecimal' - # x = BigDecimal(2).as_json # => {"json_class"=>"BigDecimal", "b"=>"27:0.2e1"} - # y = BigDecimal(2.0, 4).as_json # => {"json_class"=>"BigDecimal", "b"=>"36:0.2e1"} - # z = BigDecimal(Complex(2, 0)).as_json # => {"json_class"=>"BigDecimal", "b"=>"27:0.2e1"} - # - # \Method +JSON.create+ deserializes such a hash, returning a \BigDecimal object: - # - # BigDecimal.json_create(x) # => 0.2e1 - # BigDecimal.json_create(y) # => 0.2e1 - # BigDecimal.json_create(z) # => 0.2e1 - # - def as_json(*) - { - JSON.create_id => self.class.name, - 'b' => _dump.force_encoding(Encoding::UTF_8), - } - end - - # Returns a JSON string representing +self+: - # - # require 'json/add/bigdecimal' - # puts BigDecimal(2).to_json - # puts BigDecimal(2.0, 4).to_json - # puts BigDecimal(Complex(2, 0)).to_json - # - # Output: - # - # {"json_class":"BigDecimal","b":"27:0.2e1"} - # {"json_class":"BigDecimal","b":"36:0.2e1"} - # {"json_class":"BigDecimal","b":"27:0.2e1"} - # - def to_json(*args) - as_json.to_json(*args) - end -end if defined?(::BigDecimal) diff --git a/lib/json/add/complex.rb b/lib/json/add/complex.rb deleted file mode 100644 index a69002eff..000000000 --- a/lib/json/add/complex.rb +++ /dev/null @@ -1,51 +0,0 @@ -# frozen_string_literal: true -unless defined?(::JSON::JSON_LOADED) and ::JSON::JSON_LOADED - require 'json' -end - -class Complex - - # See #as_json. - def self.json_create(object) - Complex(object['r'], object['i']) - end - - # Methods Complex#as_json and +Complex.json_create+ may be used - # to serialize and deserialize a \Complex object; - # see Marshal[https://docs.ruby-lang.org/en/master/Marshal.html]. - # - # \Method Complex#as_json serializes +self+, - # returning a 2-element hash representing +self+: - # - # require 'json/add/complex' - # x = Complex(2).as_json # => {"json_class"=>"Complex", "r"=>2, "i"=>0} - # y = Complex(2.0, 4).as_json # => {"json_class"=>"Complex", "r"=>2.0, "i"=>4} - # - # \Method +JSON.create+ deserializes such a hash, returning a \Complex object: - # - # Complex.json_create(x) # => (2+0i) - # Complex.json_create(y) # => (2.0+4i) - # - def as_json(*) - { - JSON.create_id => self.class.name, - 'r' => real, - 'i' => imag, - } - end - - # Returns a JSON string representing +self+: - # - # require 'json/add/complex' - # puts Complex(2).to_json - # puts Complex(2.0, 4).to_json - # - # Output: - # - # {"json_class":"Complex","r":2,"i":0} - # {"json_class":"Complex","r":2.0,"i":4} - # - def to_json(*args) - as_json.to_json(*args) - end -end diff --git a/lib/json/add/core.rb b/lib/json/add/core.rb deleted file mode 100644 index 61ff45421..000000000 --- a/lib/json/add/core.rb +++ /dev/null @@ -1,13 +0,0 @@ -# frozen_string_literal: true -# This file requires the implementations of ruby core's custom objects for -# serialisation/deserialisation. - -require 'json/add/date' -require 'json/add/date_time' -require 'json/add/exception' -require 'json/add/range' -require 'json/add/regexp' -require 'json/add/string' -require 'json/add/struct' -require 'json/add/symbol' -require 'json/add/time' diff --git a/lib/json/add/date.rb b/lib/json/add/date.rb deleted file mode 100644 index 66965d491..000000000 --- a/lib/json/add/date.rb +++ /dev/null @@ -1,54 +0,0 @@ -# frozen_string_literal: true -unless defined?(::JSON::JSON_LOADED) and ::JSON::JSON_LOADED - require 'json' -end -require 'date' - -class Date - - # See #as_json. - def self.json_create(object) - civil(*object.values_at('y', 'm', 'd', 'sg')) - end - - alias start sg unless method_defined?(:start) - - # Methods Date#as_json and +Date.json_create+ may be used - # to serialize and deserialize a \Date object; - # see Marshal[https://docs.ruby-lang.org/en/master/Marshal.html]. - # - # \Method Date#as_json serializes +self+, - # returning a 2-element hash representing +self+: - # - # require 'json/add/date' - # x = Date.today.as_json - # # => {"json_class"=>"Date", "y"=>2023, "m"=>11, "d"=>21, "sg"=>2299161.0} - # - # \Method +JSON.create+ deserializes such a hash, returning a \Date object: - # - # Date.json_create(x) - # # => # - # - def as_json(*) - { - JSON.create_id => self.class.name, - 'y' => year, - 'm' => month, - 'd' => day, - 'sg' => start, - } - end - - # Returns a JSON string representing +self+: - # - # require 'json/add/date' - # puts Date.today.to_json - # - # Output: - # - # {"json_class":"Date","y":2023,"m":11,"d":21,"sg":2299161.0} - # - def to_json(*args) - as_json.to_json(*args) - end -end diff --git a/lib/json/add/date_time.rb b/lib/json/add/date_time.rb deleted file mode 100644 index 569f6ec17..000000000 --- a/lib/json/add/date_time.rb +++ /dev/null @@ -1,67 +0,0 @@ -# frozen_string_literal: true -unless defined?(::JSON::JSON_LOADED) and ::JSON::JSON_LOADED - require 'json' -end -require 'date' - -class DateTime - - # See #as_json. - def self.json_create(object) - args = object.values_at('y', 'm', 'd', 'H', 'M', 'S') - of_a, of_b = object['of'].split('/') - if of_b and of_b != '0' - args << Rational(of_a.to_i, of_b.to_i) - else - args << of_a - end - args << object['sg'] - civil(*args) - end - - alias start sg unless method_defined?(:start) - - # Methods DateTime#as_json and +DateTime.json_create+ may be used - # to serialize and deserialize a \DateTime object; - # see Marshal[https://docs.ruby-lang.org/en/master/Marshal.html]. - # - # \Method DateTime#as_json serializes +self+, - # returning a 2-element hash representing +self+: - # - # require 'json/add/datetime' - # x = DateTime.now.as_json - # # => {"json_class"=>"DateTime", "y"=>2023, "m"=>11, "d"=>21, "sg"=>2299161.0} - # - # \Method +JSON.create+ deserializes such a hash, returning a \DateTime object: - # - # DateTime.json_create(x) # BUG? Raises Date::Error "invalid date" - # - def as_json(*) - { - JSON.create_id => self.class.name, - 'y' => year, - 'm' => month, - 'd' => day, - 'H' => hour, - 'M' => min, - 'S' => sec, - 'of' => offset.to_s, - 'sg' => start, - } - end - - # Returns a JSON string representing +self+: - # - # require 'json/add/datetime' - # puts DateTime.now.to_json - # - # Output: - # - # {"json_class":"DateTime","y":2023,"m":11,"d":21,"sg":2299161.0} - # - def to_json(*args) - as_json.to_json(*args) - end -end - - diff --git a/lib/json/add/exception.rb b/lib/json/add/exception.rb deleted file mode 100644 index 5338ff83d..000000000 --- a/lib/json/add/exception.rb +++ /dev/null @@ -1,49 +0,0 @@ -# frozen_string_literal: true -unless defined?(::JSON::JSON_LOADED) and ::JSON::JSON_LOADED - require 'json' -end - -class Exception - - # See #as_json. - def self.json_create(object) - result = new(object['m']) - result.set_backtrace object['b'] - result - end - - # Methods Exception#as_json and +Exception.json_create+ may be used - # to serialize and deserialize a \Exception object; - # see Marshal[https://docs.ruby-lang.org/en/master/Marshal.html]. - # - # \Method Exception#as_json serializes +self+, - # returning a 2-element hash representing +self+: - # - # require 'json/add/exception' - # x = Exception.new('Foo').as_json # => {"json_class"=>"Exception", "m"=>"Foo", "b"=>nil} - # - # \Method +JSON.create+ deserializes such a hash, returning a \Exception object: - # - # Exception.json_create(x) # => # - # - def as_json(*) - { - JSON.create_id => self.class.name, - 'm' => message, - 'b' => backtrace, - } - end - - # Returns a JSON string representing +self+: - # - # require 'json/add/exception' - # puts Exception.new('Foo').to_json - # - # Output: - # - # {"json_class":"Exception","m":"Foo","b":null} - # - def to_json(*args) - as_json.to_json(*args) - end -end diff --git a/lib/json/add/ostruct.rb b/lib/json/add/ostruct.rb deleted file mode 100644 index 534403b78..000000000 --- a/lib/json/add/ostruct.rb +++ /dev/null @@ -1,54 +0,0 @@ -# frozen_string_literal: true -unless defined?(::JSON::JSON_LOADED) and ::JSON::JSON_LOADED - require 'json' -end -begin - require 'ostruct' -rescue LoadError -end - -class OpenStruct - - # See #as_json. - def self.json_create(object) - new(object['t'] || object[:t]) - end - - # Methods OpenStruct#as_json and +OpenStruct.json_create+ may be used - # to serialize and deserialize a \OpenStruct object; - # see Marshal[https://docs.ruby-lang.org/en/master/Marshal.html]. - # - # \Method OpenStruct#as_json serializes +self+, - # returning a 2-element hash representing +self+: - # - # require 'json/add/ostruct' - # x = OpenStruct.new('name' => 'Rowdy', :age => nil).as_json - # # => {"json_class"=>"OpenStruct", "t"=>{:name=>'Rowdy', :age=>nil}} - # - # \Method +JSON.create+ deserializes such a hash, returning a \OpenStruct object: - # - # OpenStruct.json_create(x) - # # => # - # - def as_json(*) - klass = self.class.name - klass.to_s.empty? and raise JSON::JSONError, "Only named structs are supported!" - { - JSON.create_id => klass, - 't' => table, - } - end - - # Returns a JSON string representing +self+: - # - # require 'json/add/ostruct' - # puts OpenStruct.new('name' => 'Rowdy', :age => nil).to_json - # - # Output: - # - # {"json_class":"OpenStruct","t":{'name':'Rowdy',"age":null}} - # - def to_json(*args) - as_json.to_json(*args) - end -end if defined?(::OpenStruct) diff --git a/lib/json/add/range.rb b/lib/json/add/range.rb deleted file mode 100644 index eb4b29a8e..000000000 --- a/lib/json/add/range.rb +++ /dev/null @@ -1,54 +0,0 @@ -# frozen_string_literal: true -unless defined?(::JSON::JSON_LOADED) and ::JSON::JSON_LOADED - require 'json' -end - -class Range - - # See #as_json. - def self.json_create(object) - new(*object['a']) - end - - # Methods Range#as_json and +Range.json_create+ may be used - # to serialize and deserialize a \Range object; - # see Marshal[https://docs.ruby-lang.org/en/master/Marshal.html]. - # - # \Method Range#as_json serializes +self+, - # returning a 2-element hash representing +self+: - # - # require 'json/add/range' - # x = (1..4).as_json # => {"json_class"=>"Range", "a"=>[1, 4, false]} - # y = (1...4).as_json # => {"json_class"=>"Range", "a"=>[1, 4, true]} - # z = ('a'..'d').as_json # => {"json_class"=>"Range", "a"=>["a", "d", false]} - # - # \Method +JSON.create+ deserializes such a hash, returning a \Range object: - # - # Range.json_create(x) # => 1..4 - # Range.json_create(y) # => 1...4 - # Range.json_create(z) # => "a".."d" - # - def as_json(*) - { - JSON.create_id => self.class.name, - 'a' => [ first, last, exclude_end? ] - } - end - - # Returns a JSON string representing +self+: - # - # require 'json/add/range' - # puts (1..4).to_json - # puts (1...4).to_json - # puts ('a'..'d').to_json - # - # Output: - # - # {"json_class":"Range","a":[1,4,false]} - # {"json_class":"Range","a":[1,4,true]} - # {"json_class":"Range","a":["a","d",false]} - # - def to_json(*args) - as_json.to_json(*args) - end -end diff --git a/lib/json/add/rational.rb b/lib/json/add/rational.rb deleted file mode 100644 index 1eb231474..000000000 --- a/lib/json/add/rational.rb +++ /dev/null @@ -1,49 +0,0 @@ -# frozen_string_literal: true -unless defined?(::JSON::JSON_LOADED) and ::JSON::JSON_LOADED - require 'json' -end - -class Rational - - # See #as_json. - def self.json_create(object) - Rational(object['n'], object['d']) - end - - # Methods Rational#as_json and +Rational.json_create+ may be used - # to serialize and deserialize a \Rational object; - # see Marshal[https://docs.ruby-lang.org/en/master/Marshal.html]. - # - # \Method Rational#as_json serializes +self+, - # returning a 2-element hash representing +self+: - # - # require 'json/add/rational' - # x = Rational(2, 3).as_json - # # => {"json_class"=>"Rational", "n"=>2, "d"=>3} - # - # \Method +JSON.create+ deserializes such a hash, returning a \Rational object: - # - # Rational.json_create(x) - # # => (2/3) - # - def as_json(*) - { - JSON.create_id => self.class.name, - 'n' => numerator, - 'd' => denominator, - } - end - - # Returns a JSON string representing +self+: - # - # require 'json/add/rational' - # puts Rational(2, 3).to_json - # - # Output: - # - # {"json_class":"Rational","n":2,"d":3} - # - def to_json(*args) - as_json.to_json(*args) - end -end diff --git a/lib/json/add/regexp.rb b/lib/json/add/regexp.rb deleted file mode 100644 index f033dd1de..000000000 --- a/lib/json/add/regexp.rb +++ /dev/null @@ -1,48 +0,0 @@ -# frozen_string_literal: true -unless defined?(::JSON::JSON_LOADED) and ::JSON::JSON_LOADED - require 'json' -end - -class Regexp - - # See #as_json. - def self.json_create(object) - new(object['s'], object['o']) - end - - # Methods Regexp#as_json and +Regexp.json_create+ may be used - # to serialize and deserialize a \Regexp object; - # see Marshal[https://docs.ruby-lang.org/en/master/Marshal.html]. - # - # \Method Regexp#as_json serializes +self+, - # returning a 2-element hash representing +self+: - # - # require 'json/add/regexp' - # x = /foo/.as_json - # # => {"json_class"=>"Regexp", "o"=>0, "s"=>"foo"} - # - # \Method +JSON.create+ deserializes such a hash, returning a \Regexp object: - # - # Regexp.json_create(x) # => /foo/ - # - def as_json(*) - { - JSON.create_id => self.class.name, - 'o' => options, - 's' => source, - } - end - - # Returns a JSON string representing +self+: - # - # require 'json/add/regexp' - # puts /foo/.to_json - # - # Output: - # - # {"json_class":"Regexp","o":0,"s":"foo"} - # - def to_json(*args) - as_json.to_json(*args) - end -end diff --git a/lib/json/add/set.rb b/lib/json/add/set.rb deleted file mode 100644 index c521d8b90..000000000 --- a/lib/json/add/set.rb +++ /dev/null @@ -1,48 +0,0 @@ -unless defined?(::JSON::JSON_LOADED) and ::JSON::JSON_LOADED - require 'json' -end -defined?(::Set) or require 'set' - -class Set - - # See #as_json. - def self.json_create(object) - new object['a'] - end - - # Methods Set#as_json and +Set.json_create+ may be used - # to serialize and deserialize a \Set object; - # see Marshal[https://docs.ruby-lang.org/en/master/Marshal.html]. - # - # \Method Set#as_json serializes +self+, - # returning a 2-element hash representing +self+: - # - # require 'json/add/set' - # x = Set.new(%w/foo bar baz/).as_json - # # => {"json_class"=>"Set", "a"=>["foo", "bar", "baz"]} - # - # \Method +JSON.create+ deserializes such a hash, returning a \Set object: - # - # Set.json_create(x) # => # - # - def as_json(*) - { - JSON.create_id => self.class.name, - 'a' => to_a, - } - end - - # Returns a JSON string representing +self+: - # - # require 'json/add/set' - # puts Set.new(%w/foo bar baz/).to_json - # - # Output: - # - # {"json_class":"Set","a":["foo","bar","baz"]} - # - def to_json(*args) - as_json.to_json(*args) - end -end - diff --git a/lib/json/add/string.rb b/lib/json/add/string.rb deleted file mode 100644 index 9c3bde27f..000000000 --- a/lib/json/add/string.rb +++ /dev/null @@ -1,35 +0,0 @@ -# frozen_string_literal: true -unless defined?(::JSON::JSON_LOADED) and ::JSON::JSON_LOADED - require 'json' -end - -class String - # call-seq: json_create(o) - # - # Raw Strings are JSON Objects (the raw bytes are stored in an array for the - # key "raw"). The Ruby String can be created by this class method. - def self.json_create(object) - object["raw"].pack("C*") - end - - # call-seq: to_json_raw_object() - # - # This method creates a raw object hash, that can be nested into - # other data structures and will be generated as a raw string. This - # method should be used, if you want to convert raw strings to JSON - # instead of UTF-8 strings, e. g. binary data. - def to_json_raw_object - { - JSON.create_id => self.class.name, - "raw" => unpack("C*"), - } - end - - # call-seq: to_json_raw(*args) - # - # This method creates a JSON text from the result of a call to - # to_json_raw_object of this String. - def to_json_raw(...) - to_json_raw_object.to_json(...) - end -end diff --git a/lib/json/add/struct.rb b/lib/json/add/struct.rb deleted file mode 100644 index 98c38d326..000000000 --- a/lib/json/add/struct.rb +++ /dev/null @@ -1,52 +0,0 @@ -# frozen_string_literal: true -unless defined?(::JSON::JSON_LOADED) and ::JSON::JSON_LOADED - require 'json' -end - -class Struct - - # See #as_json. - def self.json_create(object) - new(*object['v']) - end - - # Methods Struct#as_json and +Struct.json_create+ may be used - # to serialize and deserialize a \Struct object; - # see Marshal[https://docs.ruby-lang.org/en/master/Marshal.html]. - # - # \Method Struct#as_json serializes +self+, - # returning a 2-element hash representing +self+: - # - # require 'json/add/struct' - # Customer = Struct.new('Customer', :name, :address, :zip) - # x = Struct::Customer.new.as_json - # # => {"json_class"=>"Struct::Customer", "v"=>[nil, nil, nil]} - # - # \Method +JSON.create+ deserializes such a hash, returning a \Struct object: - # - # Struct::Customer.json_create(x) - # # => # - # - def as_json(*) - klass = self.class.name - klass.to_s.empty? and raise JSON::JSONError, "Only named structs are supported!" - { - JSON.create_id => klass, - 'v' => values, - } - end - - # Returns a JSON string representing +self+: - # - # require 'json/add/struct' - # Customer = Struct.new('Customer', :name, :address, :zip) - # puts Struct::Customer.new.to_json - # - # Output: - # - # {"json_class":"Struct","t":{'name':'Rowdy',"age":null}} - # - def to_json(*args) - as_json.to_json(*args) - end -end diff --git a/lib/json/add/symbol.rb b/lib/json/add/symbol.rb deleted file mode 100644 index 20dd59485..000000000 --- a/lib/json/add/symbol.rb +++ /dev/null @@ -1,52 +0,0 @@ -# frozen_string_literal: true -unless defined?(::JSON::JSON_LOADED) and ::JSON::JSON_LOADED - require 'json' -end - -class Symbol - - # Methods Symbol#as_json and +Symbol.json_create+ may be used - # to serialize and deserialize a \Symbol object; - # see Marshal[https://docs.ruby-lang.org/en/master/Marshal.html]. - # - # \Method Symbol#as_json serializes +self+, - # returning a 2-element hash representing +self+: - # - # require 'json/add/symbol' - # x = :foo.as_json - # # => {"json_class"=>"Symbol", "s"=>"foo"} - # - # \Method +JSON.create+ deserializes such a hash, returning a \Symbol object: - # - # Symbol.json_create(x) # => :foo - # - def as_json(*) - { - JSON.create_id => self.class.name, - 's' => to_s, - } - end - - # Returns a JSON string representing +self+: - # - # require 'json/add/symbol' - # puts :foo.to_json - # - # Output: - # - # # {"json_class":"Symbol","s":"foo"} - # - def to_json(state = nil, *a) - state = ::JSON::State.from_state(state) - if state.strict? - super - else - as_json.to_json(state, *a) - end - end - - # See #as_json. - def self.json_create(o) - o['s'].to_sym - end -end diff --git a/lib/json/add/time.rb b/lib/json/add/time.rb deleted file mode 100644 index 05a1f242f..000000000 --- a/lib/json/add/time.rb +++ /dev/null @@ -1,52 +0,0 @@ -# frozen_string_literal: true -unless defined?(::JSON::JSON_LOADED) and ::JSON::JSON_LOADED - require 'json' -end - -class Time - - # See #as_json. - def self.json_create(object) - if usec = object.delete('u') # used to be tv_usec -> tv_nsec - object['n'] = usec * 1000 - end - at(object['s'], Rational(object['n'], 1000)) - end - - # Methods Time#as_json and +Time.json_create+ may be used - # to serialize and deserialize a \Time object; - # see Marshal[https://docs.ruby-lang.org/en/master/Marshal.html]. - # - # \Method Time#as_json serializes +self+, - # returning a 2-element hash representing +self+: - # - # require 'json/add/time' - # x = Time.now.as_json - # # => {"json_class"=>"Time", "s"=>1700931656, "n"=>472846644} - # - # \Method +JSON.create+ deserializes such a hash, returning a \Time object: - # - # Time.json_create(x) - # # => 2023-11-25 11:00:56.472846644 -0600 - # - def as_json(*) - { - JSON.create_id => self.class.name, - 's' => tv_sec, - 'n' => tv_nsec, - } - end - - # Returns a JSON string representing +self+: - # - # require 'json/add/time' - # puts Time.now.to_json - # - # Output: - # - # {"json_class":"Time","s":1700931678,"n":980650786} - # - def to_json(*args) - as_json.to_json(*args) - end -end diff --git a/lib/json/common.rb b/lib/json/common.rb index fdcb860db..6f1176156 100644 --- a/lib/json/common.rb +++ b/lib/json/common.rb @@ -17,10 +17,6 @@ def prepare(opts) opts[:on_load] = on_load end - if opts.fetch(:create_additions, false) != false - opts = create_additions_proc(opts) - end - opts end @@ -47,77 +43,10 @@ def array_class_proc(array_class, on_load) on_load.nil? ? obj : on_load.call(obj) end end - - # TODO: extract :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_path = object[JSON.create_id] - klass = begin - Object.const_get(class_path) - rescue NameError => e - raise ArgumentError, "can't get const #{class_path}: #{e}" - end - - 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 - - def create_additions_warning - JSON.deprecation_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`" - end end end class << self - def deprecation_warning(message, uplevel = 3) # :nodoc: - gem_root = File.expand_path("..", __dir__) + "/" - 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, category: :deprecated) - else - warn(message, uplevel: uplevel) - end - end - # :call-seq: # JSON[object] -> new_array or new_string # @@ -193,44 +122,18 @@ def generator=(generator) # :nodoc: private # Called from the extension when a hash has both string and symbol keys - def on_mixed_keys_hash(hash, do_raise) + def on_mixed_keys_hash(hash) set = {} hash.each_key do |key| key_str = key.to_s if set[key_str] - message = "detected duplicate key #{key_str.inspect} in #{hash.inspect}" - if do_raise - raise GeneratorError, message - else - deprecation_warning("#{message}.\nThis will raise an error in json 3.0 unless enabled via `allow_duplicate_key: true`") - end + raise GeneratorError, "detected duplicate key #{key_str.inspect} in #{hash.inspect}" else set[key_str] = true end end end - - def deprecated_singleton_attr_accessor(*attrs) - args = RUBY_VERSION >= "3.0" ? ", category: :deprecated" : "" - attrs.each do |attr| - singleton_class.class_eval <<~RUBY - def #{attr} - warn "JSON.#{attr} is deprecated and will be removed in json 3.0.0", uplevel: 1 #{args} - @#{attr} - end - - def #{attr}=(val) - warn "JSON.#{attr}= is deprecated and will be removed in json 3.0.0", uplevel: 1 #{args} - @#{attr} = val - end - - def _#{attr} - @#{attr} - end - RUBY - end - end end # Sets create identifier, which is used to decide if the _json_create_ @@ -455,28 +358,6 @@ def generate(obj, opts = nil) end end - # :call-seq: - # JSON.fast_generate(obj, opts) -> new_string - # - # Arguments +obj+ and +opts+ here are the same as - # arguments +obj+ and +opts+ in JSON.generate. - # - # By default, generates \JSON data without checking - # for circular references in +obj+ (option +max_nesting+ set to +false+, disabled). - # - # Raises an exception if +obj+ contains circular references: - # a = []; b = []; a.push(b); b.push(a) - # # Raises SystemStackError (stack level too deep): - # JSON.fast_generate(a) - def fast_generate(obj, opts = nil) - if RUBY_VERSION >= "3.0" - warn "JSON.fast_generate is deprecated and will be removed in json 3.0.0, just use JSON.generate", uplevel: 1, category: :deprecated - else - warn "JSON.fast_generate is deprecated and will be removed in json 3.0.0, just use JSON.generate", uplevel: 1 - end - generate(obj, opts) - end - PRETTY_GENERATE_OPTIONS = { indent: ' ', space: ' ', @@ -536,30 +417,6 @@ def pretty_generate(obj, opts = nil) State.generate(obj, options, nil) end - # Sets or returns default options for the JSON.unsafe_load method. - # Initially: - # opts = JSON.load_default_options - # opts # => {:max_nesting=>false, :allow_nan=>true, :allow_blank=>true, :create_additions=>true} - deprecated_singleton_attr_accessor :unsafe_load_default_options - - @unsafe_load_default_options = { - :max_nesting => false, - :allow_nan => true, - :allow_blank => true, - :create_additions => true, - } - - # Sets or returns default options for the JSON.load method. - # Initially: - # opts = JSON.load_default_options - # opts # => {:max_nesting=>false, :allow_nan=>true, :allow_blank=>true, :create_additions=>true} - deprecated_singleton_attr_accessor :load_default_options - - @load_default_options = { - :allow_nan => true, - :allow_blank => true, - :create_additions => nil, - } # :call-seq: # JSON.unsafe_load(source, options = {}) -> object # JSON.unsafe_load(source, proc = nil, options = {}) -> object @@ -586,7 +443,6 @@ def pretty_generate(obj, opts = nil) # See details below. # - Argument +opts+, if given, contains a \Hash of options for the parsing. # See {Parsing Options}[#module-JSON-label-Parsing+Options]. - # The default options can be changed via method JSON.unsafe_load_default_options=. # # --- # @@ -692,17 +548,17 @@ def pretty_generate(obj, opts = nil) # @attributes={"type"=>"Admin", "password"=>"0wn3d"}>} # def unsafe_load(source, proc = nil, options = nil) - opts = if options.nil? - if proc && proc.is_a?(Hash) - options, proc = proc, nil - options - else - _unsafe_load_default_options - end - else - _unsafe_load_default_options.merge(options) + opts = { + max_nesting: false, + allow_nan: true, + allow_blank: true, + } + if options.nil? && proc && proc.is_a?(Hash) + options, proc = proc, nil end + opts.merge!(options) unless options.nil? + unless source.is_a?(String) if source.respond_to? :to_str source = source.to_str @@ -731,16 +587,6 @@ def unsafe_load(source, proc = nil, options = nil) # # Returns the Ruby objects created by parsing the given +source+. # - # BEWARE: This method is meant to serialise data from trusted user input, - # like from your own database server or clients under your control, it could - # be dangerous to allow untrusted users to pass JSON sources into it. - # If you must use it, use JSON.unsafe_load instead to make it clear. - # - # Since JSON version 2.8.0, `load` emits a deprecation warning when a - # non native type is deserialized, without `create_additions` being explicitly - # enabled, and in JSON version 3.0, `load` will have `create_additions` disabled - # by default. - # # - Argument +source+ must be, or be convertible to, a \String: # - If +source+ responds to instance method +to_str+, # source.to_str becomes the source. @@ -757,7 +603,6 @@ def unsafe_load(source, proc = nil, options = nil) # See details below. # - Argument +opts+, if given, contains a \Hash of options for the parsing. # See {Parsing Options}[#module-JSON-label-Parsing+Options]. - # The default options can be changed via method JSON.load_default_options=. # # --- # @@ -868,16 +713,12 @@ def load(source, proc = nil, options = nil) proc = nil end - opts = if options.nil? - if proc && proc.is_a?(Hash) - options, proc = proc, nil - options - else - _load_default_options - end - else - _load_default_options.merge(options) - end + opts = { + allow_nan: true, + allow_blank: true, + } + + opts.merge!(options) unless options.nil? unless source.is_a?(String) if source.respond_to? :to_str @@ -901,16 +742,6 @@ def load(source, proc = nil, options = nil) parse(source, opts) end - # Sets or returns the default options for the JSON.dump method. - # Initially: - # opts = JSON.dump_default_options - # opts # => {:max_nesting=>false, :allow_nan=>true} - deprecated_singleton_attr_accessor :dump_default_options - @dump_default_options = { - :max_nesting => false, - :allow_nan => true, - } - # :call-seq: # JSON.dump(obj, io = nil, limit = nil) # @@ -959,9 +790,12 @@ def dump(obj, anIO = nil, limit = nil, kwargs = nil) end end - opts = JSON._dump_default_options - opts = opts.merge(:max_nesting => limit) if limit - opts = opts.merge(kwargs) if kwargs + opts = { + max_nesting: false, + allow_nan: true, + max_nesting: limit, + } + opts.merge!(kwargs) if kwargs begin State.generate(obj, opts, anIO) @@ -970,68 +804,6 @@ def dump(obj, anIO = nil, limit = nil, kwargs = nil) end end - # :stopdoc: - # All these were meant to be deprecated circa 2009, but were just set as undocumented - # so usage still exist in the wild. - def unparse(...) - if RUBY_VERSION >= "3.0" - warn "JSON.unparse is deprecated and will be removed in json 3.0.0, just use JSON.generate", uplevel: 1, category: :deprecated - else - warn "JSON.unparse is deprecated and will be removed in json 3.0.0, just use JSON.generate", uplevel: 1 - end - generate(...) - end - module_function :unparse - - def fast_unparse(...) - if RUBY_VERSION >= "3.0" - warn "JSON.fast_unparse is deprecated and will be removed in json 3.0.0, just use JSON.generate", uplevel: 1, category: :deprecated - else - warn "JSON.fast_unparse is deprecated and will be removed in json 3.0.0, just use JSON.generate", uplevel: 1 - end - generate(...) - end - module_function :fast_unparse - - def pretty_unparse(...) - if RUBY_VERSION >= "3.0" - warn "JSON.pretty_unparse is deprecated and will be removed in json 3.0.0, just use JSON.pretty_generate", uplevel: 1, category: :deprecated - else - warn "JSON.pretty_unparse is deprecated and will be removed in json 3.0.0, just use JSON.pretty_generate", uplevel: 1 - end - pretty_generate(...) - end - module_function :fast_unparse - - def restore(...) - if RUBY_VERSION >= "3.0" - warn "JSON.restore is deprecated and will be removed in json 3.0.0, just use JSON.load", uplevel: 1, category: :deprecated - else - warn "JSON.restore is deprecated and will be removed in json 3.0.0, just use JSON.load", uplevel: 1 - end - load(...) - end - module_function :restore - - class << self - private - - def const_missing(const_name) - case const_name - when :PRETTY_STATE_PROTOTYPE - if RUBY_VERSION >= "3.0" - warn "JSON::PRETTY_STATE_PROTOTYPE is deprecated and will be removed in json 3.0.0, just use JSON.pretty_generate", uplevel: 1, category: :deprecated - else - warn "JSON::PRETTY_STATE_PROTOTYPE is deprecated and will be removed in json 3.0.0, just use JSON.pretty_generate", uplevel: 1 - end - state.new(PRETTY_GENERATE_OPTIONS) - else - super - end - end - end - # :startdoc: - # JSON::Coder holds a parser and generator configuration. # # module MyApp @@ -1136,36 +908,6 @@ def to_json(state = nil, *) module ::Kernel private - # Outputs _objs_ to STDOUT as JSON strings in the shortest form, that is in - # one line. - def j(*objs) - if RUBY_VERSION >= "3.0" - warn "Kernel#j is deprecated and will be removed in json 3.0.0", uplevel: 1, category: :deprecated - else - warn "Kernel#j is deprecated and will be removed in json 3.0.0", uplevel: 1 - end - - objs.each do |obj| - puts JSON.generate(obj, :allow_nan => true, :max_nesting => false) - end - nil - end - - # Outputs _objs_ to STDOUT as JSON strings in a pretty format, with - # indentation and over many lines. - def jj(*objs) - if RUBY_VERSION >= "3.0" - warn "Kernel#jj is deprecated and will be removed in json 3.0.0", uplevel: 1, category: :deprecated - else - warn "Kernel#jj is deprecated and will be removed in json 3.0.0", uplevel: 1 - end - - objs.each do |obj| - puts JSON.pretty_generate(obj, :allow_nan => true, :max_nesting => false) - end - nil - end - # If _object_ is string-like, parse the string and return the parsed result as # a Ruby data structure. Otherwise, generate a JSON text from the Ruby data # structure object and return it. diff --git a/lib/json/ext/generator/state.rb b/lib/json/ext/generator/state.rb index 3c1d2fb94..a9578b9bd 100644 --- a/lib/json/ext/generator/state.rb +++ b/lib/json/ext/generator/state.rb @@ -71,33 +71,6 @@ def to_h end alias_method :to_hash, :to_h - - # call-seq: [](name) - # - # Returns the value returned by method +name+. - def [](name) - ::JSON.deprecation_warning("JSON::State#[] is deprecated and will be removed in json 3.0.0") - - if respond_to?(name) - __send__(name) - else - instance_variable_get("@#{name}") if - instance_variables.include?("@#{name}".to_sym) # avoid warning - end - end - - # call-seq: []=(name, value) - # - # Sets the attribute name to value. - def []=(name, value) - ::JSON.deprecation_warning("JSON::State#[]= is deprecated and will be removed in json 3.0.0") - - if respond_to?(name_writer = "#{name}=") - __send__ name_writer, value - else - instance_variable_set "@#{name}", value - end - end end end end diff --git a/lib/json/generic_object.rb b/lib/json/generic_object.rb deleted file mode 100644 index 5c8ace354..000000000 --- a/lib/json/generic_object.rb +++ /dev/null @@ -1,67 +0,0 @@ -# frozen_string_literal: true -begin - require 'ostruct' -rescue LoadError - warn "JSON::GenericObject requires 'ostruct'. Please install it with `gem install ostruct`." -end - -module JSON - class GenericObject < OpenStruct - class << self - alias [] new - - def json_creatable? - @json_creatable - end - - attr_writer :json_creatable - - def json_create(data) - data = data.dup - data.delete JSON.create_id - self[data] - end - - def from_hash(object) - case - when object.respond_to?(:to_hash) - result = new - object.to_hash.each do |key, value| - result[key] = from_hash(value) - end - result - when object.respond_to?(:to_ary) - object.to_ary.map { |a| from_hash(a) } - else - object - end - end - - def load(source, proc = nil, opts = {}) - result = ::JSON.load(source, proc, opts.merge(:object_class => self)) - result.nil? ? new : result - end - - def dump(obj, *args) - ::JSON.dump(obj, *args) - end - end - self.json_creatable = false - - def to_hash - table - end - - def |(other) - self.class[other.to_hash.merge(to_hash)] - end - - def as_json(*) - { JSON.create_id => self.class.name }.merge to_hash - end - - def to_json(*a) - as_json.to_json(*a) - end - end if defined?(::OpenStruct) -end diff --git a/lib/json/truffle_ruby/generator.rb b/lib/json/truffle_ruby/generator.rb index ee6186fbe..495167be2 100644 --- a/lib/json/truffle_ruby/generator.rb +++ b/lib/json/truffle_ruby/generator.rb @@ -167,6 +167,7 @@ def initialize(opts = nil) @strict = false @max_nesting = 100 @sort_keys = false + @allow_duplicate_key = false configure(opts) if opts end @@ -318,6 +319,7 @@ def configure(opts) self.sort_keys = opts[:sort_keys] if opts.key?(:sort_keys) @depth = opts[:depth] || 0 @buffer_initial_length ||= opts[:buffer_initial_length] + @allow_duplicate_key = !!opts[:allow_duplicate_key] @script_safe = if opts.key?(:script_safe) !!opts[:script_safe] @@ -327,12 +329,6 @@ def configure(opts) false end - if opts.key?(:allow_duplicate_key) - @allow_duplicate_key = !!opts[:allow_duplicate_key] - else - @allow_duplicate_key = nil # nil is deprecation - end - @strict = !!opts[:strict] if opts.key?(:strict) if !opts.key?(:max_nesting) # defaults to 100 @@ -362,10 +358,6 @@ def to_h result[key.to_sym] = instance_variable_get(iv) end - if result[:allow_duplicate_key].nil? - result.delete(:allow_duplicate_key) - end - result end @@ -416,7 +408,7 @@ def generate(obj, anIO = nil) else if key_type && !@allow_duplicate_key && key_type != k.class key_type = nil # stop checking - JSON.send(:on_mixed_keys_hash, obj, !@allow_duplicate_key.nil?) + JSON.send(:on_mixed_keys_hash, obj) end buf << ',' end @@ -483,28 +475,6 @@ def generate(obj, anIO = nil) end buf << '"' end - - # Return the value returned by method +name+. - def [](name) - ::JSON.deprecation_warning("JSON::State#[] is deprecated and will be removed in json 3.0.0") - - if respond_to?(name) - __send__(name) - else - instance_variable_get("@#{name}") if - instance_variables.include?("@#{name}".to_sym) # avoid warning - end - end - - def []=(name, value) - ::JSON.deprecation_warning("JSON::State#[]= is deprecated and will be removed in json 3.0.0") - - if respond_to?(name_writer = "#{name}=") - __send__ name_writer, value - else - instance_variable_set "@#{name}", value - end - end end module GeneratorMethods @@ -569,7 +539,7 @@ def json_transform(state) else if key_type && !state.allow_duplicate_key? && key_type != key.class key_type = nil # stop checking - JSON.send(:on_mixed_keys_hash, self, state.allow_duplicate_key? == false) + JSON.send(:on_mixed_keys_hash, self) end result << delim end diff --git a/test/json/json_addition_test.rb b/test/json/json_addition_test.rb deleted file mode 100644 index 4d8d18687..000000000 --- a/test/json/json_addition_test.rb +++ /dev/null @@ -1,192 +0,0 @@ -# frozen_string_literal: true -require_relative 'test_helper' -require 'json/add/core' -require 'json/add/complex' -require 'json/add/rational' -require 'json/add/bigdecimal' -require 'json/add/ostruct' -require 'json/add/set' -require 'date' - -class JSONAdditionTest < Test::Unit::TestCase - include JSON - - class A - def initialize(a) - @a = a - end - - attr_reader :a - - def ==(other) - a == other.a - end - - def self.json_create(object) - new(*object['args']) - end - - def to_json(*args) - { - 'json_class' => self.class.name, - 'args' => [ @a ], - }.to_json(*args) - end - end - - class A2 < A - def to_json(*args) - { - 'json_class' => self.class.name, - 'args' => [ @a ], - }.to_json(*args) - end - end - - class B - def to_json(*args) - { - 'json_class' => self.class.name, - }.to_json(*args) - end - end - - class C - def to_json(*args) - { - 'json_class' => 'JSONAdditionTest::Nix', - }.to_json(*args) - end - end - - def test_extended_json - a = A.new(666) - json = generate(a) - a_again = parse(json, :create_additions => true) - assert_kind_of a.class, a_again - assert_equal a, a_again - end - - def test_extended_json_default - a = A.new(666) - assert A.respond_to?(:json_create) - json = generate(a) - a_hash = parse(json) - assert_kind_of Hash, a_hash - end - - def test_extended_json_disabled - a = A.new(666) - json = generate(a) - a_again = parse(json, :create_additions => true) - assert_kind_of a.class, a_again - assert_equal a, a_again - a_hash = parse(json, :create_additions => false) - assert_kind_of Hash, a_hash - assert_equal( - {"args"=>[666], "json_class"=>"JSONAdditionTest::A"}.sort_by { |k,| k }, - a_hash.sort_by { |k,| k } - ) - end - - def test_extended_json_fail1 - b = B.new - json = generate(b) - assert_equal({ "json_class"=>"JSONAdditionTest::B" }, parse(json)) - end - - def test_extended_json_fail2 - c = C.new - json = generate(c) - assert_raise(ArgumentError, NameError) { parse(json, :create_additions => true) } - end - - def test_raw_strings - raw = ''.b - raw_array = [] - for i in 0..255 - raw << i - raw_array << i - end - json = raw.to_json_raw - json_raw_object = raw.to_json_raw_object - hash = { 'json_class' => 'String', 'raw'=> raw_array } - assert_equal hash, json_raw_object - assert_match(/\A\{.*\}\z/, json) - assert_match(/"json_class":"String"/, json) - assert_match(/"raw":\[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255\]/, json) - raw_again = parse(json, :create_additions => true) - assert_equal raw, raw_again - end - - MyJsonStruct = Struct.new 'MyJsonStruct', :foo, :bar - - def test_core - t = Time.now - assert_equal t, JSON(JSON(t), :create_additions => true) - d = Date.today - assert_equal d, JSON(JSON(d), :create_additions => true) - d = DateTime.civil(2007, 6, 14, 14, 57, 10, Rational(1, 12), 2299161) - assert_equal d, JSON(JSON(d), :create_additions => true) - assert_equal 1..10, JSON(JSON(1..10), :create_additions => true) - assert_equal 1...10, JSON(JSON(1...10), :create_additions => true) - assert_equal "a".."c", JSON(JSON("a".."c"), :create_additions => true) - assert_equal "a"..."c", JSON(JSON("a"..."c"), :create_additions => true) - s = MyJsonStruct.new 4711, 'foot' - assert_equal s, JSON(JSON(s), :create_additions => true) - struct = Struct.new :foo, :bar - s = struct.new 4711, 'foot' - assert_raise(JSONError) { JSON(s) } - begin - raise TypeError, "test me" - rescue TypeError => e - e_json = JSON.generate e - e_again = JSON e_json, :create_additions => true - assert_kind_of TypeError, e_again - assert_equal e.message, e_again.message - assert_equal e.backtrace, e_again.backtrace - end - assert_equal(/foo/, JSON(JSON(/foo/), :create_additions => true)) - assert_equal(/foo/i, JSON(JSON(/foo/i), :create_additions => true)) - end - - def test_deprecated_load_create_additions - assert_deprecated_warning(/use JSON\.unsafe_load/) do - JSON.load(JSON.dump(Time.now)) - end - end - - def test_utc_datetime - now = Time.now - d = DateTime.parse(now.to_s) # usual case - assert_equal d, parse(d.to_json, :create_additions => true) - d = DateTime.parse(now.utc.to_s) # of = 0 - assert_equal d, parse(d.to_json, :create_additions => true) - d = DateTime.civil(2008, 6, 17, 11, 48, 32, Rational(1,24)) - assert_equal d, parse(d.to_json, :create_additions => true) - d = DateTime.civil(2008, 6, 17, 11, 48, 32, Rational(12,24)) - assert_equal d, parse(d.to_json, :create_additions => true) - end - - def test_rational_complex - assert_equal Rational(2, 9), parse(JSON(Rational(2, 9)), :create_additions => true) - assert_equal Complex(2, 9), parse(JSON(Complex(2, 9)), :create_additions => true) - end - - def test_bigdecimal - assert_equal BigDecimal('3.141', 23), JSON(JSON(BigDecimal('3.141', 23)), :create_additions => true) - assert_equal BigDecimal('3.141', 666), JSON(JSON(BigDecimal('3.141', 666)), :create_additions => true) - end if defined?(::BigDecimal) - - def test_ostruct - o = OpenStruct.new - # XXX this won't work; o.foo = { :bar => true } - o.foo = { 'bar' => true } - assert_equal o, parse(JSON(o), :create_additions => true) - end if defined?(::OpenStruct) - - def test_set - s = Set.new([:a, :b, :c, :a]) - assert_equal s, JSON.parse(JSON(s), :create_additions => true) - end -end diff --git a/test/json/json_common_interface_test.rb b/test/json/json_common_interface_test.rb index 37568b556..72b8f390a 100644 --- a/test/json/json_common_interface_test.rb +++ b/test/json/json_common_interface_test.rb @@ -271,12 +271,6 @@ def test_dump_in_io assert_equal JSON.dump(big_object), io.string end - def test_dump_should_modify_defaults - max_nesting = JSON._dump_default_options[:max_nesting] - dump([], StringIO.new, 10) - assert_equal max_nesting, JSON._dump_default_options[:max_nesting] - end - def test_JSON assert_equal @json, JSON(@hash) assert_equal @json, JSON(@hash_with_method_missing) @@ -315,12 +309,6 @@ def test_load_file_with_bad_default_external_encoding end end - def test_deprecated_dump_default_options - assert_deprecated_warning(/dump_default_options/) do - JSON.dump_default_options - end - end - private def with_external_encoding(encoding) diff --git a/test/json/json_generator_test.rb b/test/json/json_generator_test.rb index ab19704b1..f124e22b6 100755 --- a/test/json/json_generator_test.rb +++ b/test/json/json_generator_test.rb @@ -256,20 +256,6 @@ def test_generate_custom JSON end - def test_fast_generate - assert_deprecated_warning(/fast_generate/) do - json = fast_generate(@hash) - assert_equal(parse(@json2), parse(json)) - parsed_json = parse(json) - assert_equal(@hash, parsed_json) - json = fast_generate({1=>2}) - assert_equal('{"1":2}', json) - parsed_json = parse(json) - assert_equal({"1"=>2}, parsed_json) - assert_equal '666', fast_generate(666) - end - end - def test_own_state state = State.new json = generate(@hash, state) @@ -287,10 +273,6 @@ def test_states json = generate({1=>2}, nil) assert_equal('{"1":2}', json) s = JSON.state.new - assert s.check_circular? - assert_deprecated_warning(/JSON::State/) do - assert s[:check_circular?] - end h = { 1=>2 } h[3] = h assert_raise(JSON::NestingError) { generate(h) } @@ -299,10 +281,6 @@ def test_states a = [ 1, 2 ] a << a assert_raise(JSON::NestingError) { generate(a, s) } - assert s.check_circular? - assert_deprecated_warning(/JSON::State/) do - assert s[:check_circular?] - end end def test_falsy_state @@ -329,6 +307,7 @@ def test_falsy_state def test_state_defaults state = JSON::State.new assert_equal({ + :allow_duplicate_key => false, :allow_nan => false, :array_nl => "", :as_json => false, @@ -366,26 +345,24 @@ def test_state_defaults end def test_allow_nan - assert_deprecated_warning(/fast_generate/) do - error = assert_raise(GeneratorError) { generate([JSON::NaN]) } - assert_same JSON::NaN, error.invalid_object - assert_equal '[NaN]', generate([JSON::NaN], :allow_nan => true) - assert_raise(GeneratorError) { fast_generate([JSON::NaN]) } - assert_raise(GeneratorError) { pretty_generate([JSON::NaN]) } - assert_equal "[\n NaN\n]", pretty_generate([JSON::NaN], :allow_nan => true) - error = assert_raise(GeneratorError) { generate([JSON::Infinity]) } - assert_same JSON::Infinity, error.invalid_object - assert_equal '[Infinity]', generate([JSON::Infinity], :allow_nan => true) - assert_raise(GeneratorError) { fast_generate([JSON::Infinity]) } - assert_raise(GeneratorError) { pretty_generate([JSON::Infinity]) } - assert_equal "[\n Infinity\n]", pretty_generate([JSON::Infinity], :allow_nan => true) - error = assert_raise(GeneratorError) { generate([JSON::MinusInfinity]) } - assert_same JSON::MinusInfinity, error.invalid_object - assert_equal '[-Infinity]', generate([JSON::MinusInfinity], :allow_nan => true) - assert_raise(GeneratorError) { fast_generate([JSON::MinusInfinity]) } - assert_raise(GeneratorError) { pretty_generate([JSON::MinusInfinity]) } - assert_equal "[\n -Infinity\n]", pretty_generate([JSON::MinusInfinity], :allow_nan => true) - end + error = assert_raise(GeneratorError) { generate([JSON::NaN]) } + assert_same JSON::NaN, error.invalid_object + assert_equal '[NaN]', generate([JSON::NaN], :allow_nan => true) + assert_raise(GeneratorError) { generate([JSON::NaN]) } + assert_raise(GeneratorError) { pretty_generate([JSON::NaN]) } + assert_equal "[\n NaN\n]", pretty_generate([JSON::NaN], :allow_nan => true) + error = assert_raise(GeneratorError) { generate([JSON::Infinity]) } + assert_same JSON::Infinity, error.invalid_object + assert_equal '[Infinity]', generate([JSON::Infinity], :allow_nan => true) + assert_raise(GeneratorError) { generate([JSON::Infinity]) } + assert_raise(GeneratorError) { pretty_generate([JSON::Infinity]) } + assert_equal "[\n Infinity\n]", pretty_generate([JSON::Infinity], :allow_nan => true) + error = assert_raise(GeneratorError) { generate([JSON::MinusInfinity]) } + assert_same JSON::MinusInfinity, error.invalid_object + assert_equal '[-Infinity]', generate([JSON::MinusInfinity], :allow_nan => true) + assert_raise(GeneratorError) { generate([JSON::MinusInfinity]) } + assert_raise(GeneratorError) { pretty_generate([JSON::MinusInfinity]) } + assert_equal "[\n -Infinity\n]", pretty_generate([JSON::MinusInfinity], :allow_nan => true) end # An object that changes state.depth when it receives to_json(state) @@ -577,35 +554,6 @@ def to_s bignum.class.define_method(:to_s, original_to_s) if original_to_s end - def test_hash_likeness_set_symbol - assert_deprecated_warning(/JSON::State/) do - state = JSON.state.new - assert_equal nil, state[:foo] - assert_equal nil.class, state[:foo].class - assert_equal nil, state['foo'] - state[:foo] = :bar - assert_equal :bar, state[:foo] - assert_equal :bar, state['foo'] - state_hash = state.to_hash - assert_kind_of Hash, state_hash - assert_equal :bar, state_hash[:foo] - end - end - - def test_hash_likeness_set_string - assert_deprecated_warning(/JSON::State/) do - state = JSON.state.new - assert_equal nil, state[:foo] - assert_equal nil, state['foo'] - state['foo'] = :bar - assert_equal :bar, state[:foo] - assert_equal :bar, state['foo'] - state_hash = state.to_hash - assert_kind_of Hash, state_hash - assert_equal :bar, state_hash[:foo] - end - end - def test_json_state_to_h_roundtrip state = JSON.state.new assert_equal state.to_h, JSON.state.new(state.to_h).to_h @@ -1090,13 +1038,6 @@ def test_generate_duplicate_keys_allowed assert_equal %({"foo":1,"foo":2}), JSON.generate(hash, allow_duplicate_key: true) end - def test_generate_duplicate_keys_deprecated - hash = { foo: 1, "foo" => 2 } - assert_deprecated_warning(/allow_duplicate_key/) do - assert_equal %({"foo":1,"foo":2}), JSON.generate(hash) - end - end - def test_generate_duplicate_keys_disallowed hash = { foo: 1, "foo" => 2 } error = assert_raise JSON::GeneratorError do diff --git a/test/json/json_generic_object_test.rb b/test/json/json_generic_object_test.rb deleted file mode 100644 index 57e3bf3c5..000000000 --- a/test/json/json_generic_object_test.rb +++ /dev/null @@ -1,91 +0,0 @@ -# frozen_string_literal: true -require_relative 'test_helper' - -# ostruct is required to test JSON::GenericObject -begin - require "ostruct" -rescue LoadError - return -end - -class JSONGenericObjectTest < Test::Unit::TestCase - def setup - if defined?(JSON::GenericObject) - @go = JSON::GenericObject[ :a => 1, :b => 2 ] - else - omit("JSON::GenericObject is not available") - end - end - - def test_attributes - assert_equal 1, @go.a - assert_equal 1, @go[:a] - assert_equal 2, @go.b - assert_equal 2, @go[:b] - assert_nil @go.c - assert_nil @go[:c] - end - - def test_generate_json - switch_json_creatable do - assert_equal @go, JSON(JSON(@go), :create_additions => true) - end - end - - def test_parse_json - assert_kind_of Hash, - JSON( - '{ "json_class": "JSON::GenericObject", "a": 1, "b": 2 }', - :create_additions => true - ) - switch_json_creatable do - assert_equal @go, l = - JSON( - '{ "json_class": "JSON::GenericObject", "a": 1, "b": 2 }', - :create_additions => true - ) - assert_equal 1, l.a - assert_equal @go, - l = JSON('{ "a": 1, "b": 2 }', :object_class => JSON::GenericObject) - assert_equal 1, l.a - assert_equal JSON::GenericObject[:a => JSON::GenericObject[:b => 2]], - l = JSON('{ "a": { "b": 2 } }', :object_class => JSON::GenericObject) - assert_equal 2, l.a.b - end - end - - def test_from_hash - result = JSON::GenericObject.from_hash( - :foo => { :bar => { :baz => true }, :quux => [ { :foobar => true } ] }) - assert_kind_of JSON::GenericObject, result.foo - assert_kind_of JSON::GenericObject, result.foo.bar - assert_equal true, result.foo.bar.baz - assert_kind_of JSON::GenericObject, result.foo.quux.first - assert_equal true, result.foo.quux.first.foobar - assert_equal true, JSON::GenericObject.from_hash(true) - end - - def test_json_generic_object_load - empty = JSON::GenericObject.load(nil) - assert_kind_of JSON::GenericObject, empty - simple_json = '{"json_class":"JSON::GenericObject","hello":"world"}' - simple = JSON::GenericObject.load(simple_json) - assert_kind_of JSON::GenericObject, simple - assert_equal "world", simple.hello - converting = JSON::GenericObject.load('{ "hello": "world" }') - assert_kind_of JSON::GenericObject, converting - assert_equal "world", converting.hello - - json = JSON::GenericObject.dump(JSON::GenericObject[:hello => 'world']) - assert_equal JSON(json), JSON('{"json_class":"JSON::GenericObject","hello":"world"}') - end - - private - - def switch_json_creatable - JSON::GenericObject.json_creatable = true - yield - ensure - JSON::GenericObject.json_creatable = false - end -end diff --git a/test/json/json_parser_test.rb b/test/json/json_parser_test.rb index 2f79b87cc..1ce8031a8 100644 --- a/test/json/json_parser_test.rb +++ b/test/json/json_parser_test.rb @@ -405,35 +405,16 @@ def test_parse_escaped_key def test_parse_duplicate_key expected = {"a" => 2} - expected_sym = {a: 2} assert_equal expected, parse('{"a": 1, "a": 2}', allow_duplicate_key: true) assert_raise(ParserError) { parse('{"a": 1, "a": 2}', allow_duplicate_key: false) } assert_raise(ParserError) { parse('{"a": 1, "a": 2}', allow_duplicate_key: false, symbolize_names: true) } - assert_deprecated_warning(/duplicate key "a"/) do - assert_equal expected, parse('{"a": 1, "a": 2}') - end - assert_deprecated_warning(/duplicate key "a"/) do - assert_equal expected_sym, parse('{"a": 1, "a": 2}', symbolize_names: true) - end - - if RUBY_ENGINE == 'ruby' - assert_deprecated_warning(/#{File.basename(__FILE__)}\:#{__LINE__ + 1}/) do - assert_equal expected, parse('{"a": 1, "a": 2}') - end - end - unless RUBY_ENGINE == 'jruby' assert_raise(ParserError) do fake_key = Object.new JSON.load('{"a": 1, "a": 2}', -> (obj) { obj == "a" ? fake_key : obj }, allow_duplicate_key: false) end - - assert_deprecated_warning(/duplicate key # (obj) { obj == "a" ? fake_key : obj }) - end end end @@ -465,9 +446,6 @@ def test_symbolize_names parse('{"foo":"bar", "baz":"quux"}')) assert_equal({ :foo => "bar", :baz => "quux" }, parse('{"foo":"bar", "baz":"quux"}', :symbolize_names => true)) - assert_raise(ArgumentError) do - parse('{}', :symbolize_names => true, :create_additions => true) - end end def test_freeze @@ -531,16 +509,8 @@ def test_parse_comments assert_raise(ParserError) { parse('{} /*/', allow_comments: true) } assert_raise(ParserError) { parse('{} /x wrong comment', allow_comments: true) } assert_raise(ParserError) { parse('{} /', allow_comments: true) } - end - - def test_parse_comments_deprecation assert_equal({}, parse('/**/ {}', allow_comments: true)) assert_raise(ParserError) { parse('/**/ {}', allow_comments: false) } - if RUBY_ENGINE == 'ruby' - assert_deprecated_warning(/Encountered comment in JSON/) do - parse('/**/ {}') - end - end end def test_nesting @@ -809,18 +779,6 @@ def test_parse_generic_object end end - def test_generate_core_subclasses_with_new_to_json - obj = SubHash2["foo" => SubHash2["bar" => true]] - obj_json = JSON(obj) - obj_again = parse(obj_json, :create_additions => true) - assert_kind_of SubHash2, obj_again - assert_kind_of SubHash2, obj_again['foo'] - assert obj_again['foo']['bar'] - assert_equal obj, obj_again - assert_equal ["foo"], - JSON(JSON(SubArray2["foo"]), :create_additions => true) - end - def test_generate_core_subclasses_with_default_to_json assert_equal '{"foo":"bar"}', JSON(SubHash["foo" => "bar"]) assert_equal '["foo"]', JSON(SubArray["foo"]) diff --git a/test/json/json_string_matching_test.rb b/test/json/json_string_matching_test.rb deleted file mode 100644 index 21cd64902..000000000 --- a/test/json/json_string_matching_test.rb +++ /dev/null @@ -1,38 +0,0 @@ -# frozen_string_literal: true -require_relative 'test_helper' -require 'time' - -class JSONStringMatchingTest < Test::Unit::TestCase - include JSON - - class TestTime < ::Time - def self.json_create(string) - Time.parse(string) - end - - def to_json(*) - %{"#{strftime('%FT%T%z')}"} - end - - def ==(other) - to_i == other.to_i - end - end - - def test_match_date - t = TestTime.new - t_json = [ t ].to_json - time_regexp = /\A\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}[+-]\d{4}\z/ - assert_equal [ t ], - parse( - t_json, - :create_additions => true, - :match_string => { time_regexp => TestTime } - ) - assert_equal [ t.strftime('%FT%T%z') ], - parse( - t_json, - :match_string => { time_regexp => TestTime } - ) - end -end