diff --git a/CHANGES.md b/CHANGES.md
index 3f455f7c4..b842cb86d 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -2,6 +2,47 @@
### Unreleased
+### 2026-08-11 (3.0.0.rc1)
+
+With the removal of the insecure `create_additions` option, `JSON.load` and `JSON.dump` are
+now safe to use. Them being unsafe by default caused multiple security vulnerabilites in the past.
+
+If you did depend on `create_additions`, the recommended migration is to [implement a custom serializer using
+`JSON::Coder`](https://byroot.github.io/ruby/json/2025/08/02/whats-wrong-with-the-json-gem-api.html#the-create_additions-option).
+
+All the mutable default options, such as `JSON.load_default_options` have been removed.
+They were preventing Ractor compatiblity, and causing bug in libraries using JSON expecting the default behavior.
+`JSON` methods now always behave the same unless monkey patched.
+
+All methods options are now either keyword arguments or checked like keyword arguments, meaning
+unknown options such as typos raise `ArgumentError`.
+
+Duplicated keys are now rejected by default.
+
+JavaScript comments in documents are no longer supported by default.
+
+Numerous rarely used aliases have been removed.
+
+* `JSON.load` defaults are now safe to use.
+* All unknown options are now cause an `ArgumentError` rather than to be ignored.
+* 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 the `limit` positional argument of `JSON.dump`.
+* Removed the `escape_slash` alias of `script_safe`.
+* 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-31 (2.21.2)
* Fix a use-after-free bug in `JSON::ResumableParser`. [GHSA-9hj4-r449-hfvc][CVE-2026-71847].
diff --git a/Gemfile b/Gemfile
index 7956c2dc2..407a3f9b0 100644
--- a/Gemfile
+++ b/Gemfile
@@ -5,7 +5,6 @@ gemspec
group :development do
gem "ruby_memcheck" if RUBY_PLATFORM =~ /linux/i
gem "bigdecimal"
- gem "ostruct"
gem "rake"
gem "rake-compiler"
gem "test-unit"
diff --git a/README.md b/README.md
index 5e6ffe48f..ab550ad7c 100644
--- a/README.md
+++ b/README.md
@@ -14,10 +14,7 @@ UTF-16 surrogate pairs in order to be able to generate the whole range of
unicode code points.
All strings, that are to be encoded as JSON strings, should be UTF-8 byte
-sequences on the Ruby side. To encode raw binary strings, that aren't UTF-8
-encoded, please use the to\_json\_raw\_object method of String (which produces
-an object, that contains a byte array) and decode the result on the receiving
-endpoint.
+sequences on the Ruby side.
## Installation
@@ -49,8 +46,7 @@ JSON.generate(data)
```
You can also use the `pretty_generate` method (which formats the output more
-verbosely and nicely) or `fast_generate` (which doesn't do any of the security
-checks generate performs, e. g. nesting deepness checks).
+verbosely and nicely).
## Casting non native types
@@ -143,84 +139,12 @@ 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
protocols, but not so good for humans to read. Fortunately there's also
-`JSON.pretty_generate` (or `JSON.pretty_generate`) that creates a more readable
+`JSON.pretty_generate` that creates a more readable
output:
```ruby
@@ -245,10 +169,6 @@ output:
]
```
-There are also the methods `Kernel#j` for generate, and `Kernel#jj` for
-`pretty_generate` output to the console, that work analogous to Core Ruby's `p` and
-the `pp` library's `pp` methods.
-
## Security
When parsing or serializing untrusted input, parser and generator options should never be user controlled.
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..38607cf4e 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;
@@ -41,7 +34,7 @@ static VALUE mJSON, cState, cFragment, eGeneratorError, eNestingError, Encoding_
static ID i_to_s, i_to_json, i_new, i_encode;
static VALUE sym_indent, sym_space, sym_space_before, sym_object_nl, sym_array_nl, sym_max_nesting, sym_allow_nan, sym_allow_duplicate_key,
- sym_ascii_only, sym_depth, sym_buffer_initial_length, sym_script_safe, sym_escape_slash, sym_strict, sym_as_json, sym_sort_keys;
+ sym_ascii_only, sym_depth, sym_buffer_initial_length, sym_script_safe, sym_strict, sym_as_json, sym_sort_keys;
#define GET_STATE_TO(self, state) \
@@ -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;
}
/*
@@ -1856,6 +1841,7 @@ static VALUE cState_buffer_initial_length_set(VALUE self, VALUE buffer_initial_l
struct configure_state_data {
JSON_Generator_State *state;
VALUE vstate; // Ruby object that owns the state, or Qfalse if stack-allocated
+ VALUE unknown_keywords;
};
static inline void state_write_value(struct configure_state_data *data, VALUE *field, VALUE value)
@@ -1883,9 +1869,8 @@ static int configure_state_i(VALUE key, VALUE val, VALUE _arg)
else if (key == sym_depth) { state->depth = depth_config(val); }
else if (key == sym_buffer_initial_length) { buffer_initial_length_set(state, val); }
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;
@@ -1894,6 +1879,12 @@ static int configure_state_i(VALUE key, VALUE val, VALUE _arg)
else if (key == sym_sort_keys) {
state_write_value(data, &state->sort_keys, normalize_sort_keys(val));
}
+ else {
+ if (!data->unknown_keywords) {
+ data->unknown_keywords = rb_obj_hide(rb_ary_new());
+ }
+ rb_ary_push(data->unknown_keywords, key);
+ }
return ST_CONTINUE;
}
@@ -1907,12 +1898,15 @@ static void configure_state(JSON_Generator_State *state, VALUE vstate, VALUE con
struct configure_state_data data = {
.state = state,
- .vstate = vstate
+ .vstate = vstate,
+ .unknown_keywords = Qfalse,
};
// 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(config, configure_state_i, (VALUE)&data);
+
+ raise_argument_error_on_unknown_keywords(data.unknown_keywords);
}
static VALUE cState_configure(VALUE self, VALUE opts)
@@ -2006,9 +2000,6 @@ void Init_generator(void)
rb_define_method(cState, "script_safe", cState_script_safe, 0);
rb_define_method(cState, "script_safe?", cState_script_safe, 0);
rb_define_method(cState, "script_safe=", cState_script_safe_set, 1);
- rb_define_alias(cState, "escape_slash", "script_safe");
- rb_define_alias(cState, "escape_slash?", "script_safe?");
- rb_define_alias(cState, "escape_slash=", "script_safe=");
rb_define_method(cState, "strict", cState_strict, 0);
rb_define_method(cState, "strict?", cState_strict, 0);
rb_define_method(cState, "strict=", cState_strict_set, 1);
@@ -2050,7 +2041,6 @@ void Init_generator(void)
sym_depth = ID2SYM(rb_intern("depth"));
sym_buffer_initial_length = ID2SYM(rb_intern("buffer_initial_length"));
sym_script_safe = ID2SYM(rb_intern("script_safe"));
- sym_escape_slash = ID2SYM(rb_intern("escape_slash"));
sym_strict = ID2SYM(rb_intern("strict"));
sym_as_json = ID2SYM(rb_intern("as_json"));
sym_allow_duplicate_key = ID2SYM(rb_intern("allow_duplicate_key"));
diff --git a/ext/json/ext/json.h b/ext/json/ext/json.h
index afddbe2a2..be69bf589 100644
--- a/ext/json/ext/json.h
+++ b/ext/json/ext/json.h
@@ -180,4 +180,17 @@ static inline VALUE json_rb_catch_obj(VALUE tag, VALUE (*func)(VALUE args), VALU
#endif // JSON_TRUFFLERUBY_RB_CATCH_BUG
+static inline void raise_argument_error_on_unknown_keywords(VALUE unknown_keywords)
+{
+ if (RB_UNLIKELY(unknown_keywords)) {
+ if (RARRAY_LEN(unknown_keywords) == 1) {
+ rb_raise(rb_eArgError, "unknown keyword: %" PRIsVALUE, RARRAY_AREF(unknown_keywords, 0));
+ }
+ else {
+ VALUE keywords = rb_ary_join(unknown_keywords, rb_utf8_str_new_cstr(", "));
+ rb_raise(rb_eArgError, "unknown keywords: %" PRIsVALUE, keywords);
+ }
+ }
+}
+
#endif // _JSON_H_
diff --git a/ext/json/ext/parser/parser.c b/ext/json/ext/parser/parser.c
index 1443095b6..f10e2260a 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;
@@ -619,21 +612,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)
-{
- VALUE warning;
- if (state->parser) { // line and columns can't be accurate in resumable
- warning = rb_utf8_str_new_cstr(message);
- } else {
- long line, column;
- cursor_position(state, &line, &column);
- 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)
@@ -772,11 +750,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);
}
@@ -826,11 +803,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
@@ -1219,17 +1191,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(
@@ -1250,23 +1211,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)
@@ -2032,13 +1979,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)) {
@@ -2068,7 +2015,7 @@ static int parser_config_init_i(VALUE key, VALUE val, VALUE data)
}
}
}
- else if (args->strict) {
+ else {
if (!args->unknown_keywords) {
args->unknown_keywords = rb_obj_hide(rb_ary_new());
}
@@ -2096,15 +2043,7 @@ static void parser_config_init(JSON_ParserConfig *config, VALUE opts, VALUE self
// the provided keys than to check all possible keys.
rb_hash_foreach(opts, parser_config_init_i, (VALUE)&args);
- if (RB_UNLIKELY(args.unknown_keywords)) {
- if (RARRAY_LEN(args.unknown_keywords) == 1) {
- rb_raise(rb_eArgError, "unknown keyword: %" PRIsVALUE, RARRAY_AREF(args.unknown_keywords, 0));
- }
- else {
- VALUE keywords = rb_ary_join(args.unknown_keywords, rb_utf8_str_new_cstr(", "));
- rb_raise(rb_eArgError, "unknown keywords: %" PRIsVALUE, keywords);
- }
- }
+ raise_argument_error_on_unknown_keywords(args.unknown_keywords);
}
/*
@@ -2666,7 +2605,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..dabc9e255 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;
}
@@ -423,19 +397,19 @@ public boolean scriptSafe() {
return scriptSafe;
}
- @JRubyMethod(name="script_safe", alias="escape_slash")
+ @JRubyMethod(name="script_safe")
public RubyBoolean script_safe_get(ThreadContext context) {
return RubyBoolean.newBoolean(context, scriptSafe);
}
- @JRubyMethod(name="script_safe=", alias="escape_slash=")
+ @JRubyMethod(name="script_safe=")
public IRubyObject script_safe_set(IRubyObject script_safe) {
checkFrozen();
scriptSafe = script_safe.isTrue();
return script_safe.getRuntime().newBoolean(scriptSafe);
}
- @JRubyMethod(name="script_safe?", alias="escape_slash?")
+ @JRubyMethod(name="script_safe?")
public RubyBoolean script_safe_p(ThreadContext context) {
return RubyBoolean.newBoolean(context, scriptSafe);
}
@@ -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)
*
@@ -598,9 +566,6 @@ public IRubyObject _configure(ThreadContext context, IRubyObject vOpts) {
allowNaN = opts.getBool("allow_nan", DEFAULT_ALLOW_NAN);
asciiOnly = opts.getBool("ascii_only", DEFAULT_ASCII_ONLY);
scriptSafe = opts.getBool("script_safe", DEFAULT_SCRIPT_SAFE);
- if (!scriptSafe) {
- scriptSafe = opts.getBool("escape_slash", DEFAULT_SCRIPT_SAFE);
- }
strict = opts.getBool("strict", DEFAULT_STRICT);
bufferInitialLength = opts.getInt("buffer_initial_length", DEFAULT_BUFFER_INITIAL_LENGTH);
@@ -608,14 +573,12 @@ 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"));
+ opts.ensureEmpty();
+
return this;
}
@@ -645,15 +608,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/OptionsReader.java b/java/src/json/ext/OptionsReader.java
index 829e36b31..d93d3cd79 100644
--- a/java/src/json/ext/OptionsReader.java
+++ b/java/src/json/ext/OptionsReader.java
@@ -26,15 +26,19 @@ final class OptionsReader {
OptionsReader(ThreadContext context, IRubyObject vOpts) {
this.context = context;
this.runtime = context.runtime;
+
+ RubyHash options;
if (vOpts == null || vOpts.isNil()) {
- opts = null;
+ options = null;
} else if (vOpts.respondsTo("to_hash")) {
- opts = vOpts.convertToHash();
+ options = vOpts.convertToHash();
} else if (vOpts.respondsTo("to_h")) {
- opts = vOpts.callMethod(context, "to_h").convertToHash();
+ options = vOpts.callMethod(context, "to_h").convertToHash();
} else {
- opts = vOpts.convertToHash(); /* Should just raise the correct TypeError */
+ options = vOpts.convertToHash(); /* Should just raise the correct TypeError */
}
+
+ this.opts = options == null ? null : (RubyHash)options.dup();
}
private RuntimeInfo getRuntimeInfo() {
@@ -43,6 +47,19 @@ private RuntimeInfo getRuntimeInfo() {
return info;
}
+
+ void ensureEmpty() {
+ if (opts == null) return;
+
+ if (opts.size() == 1) {
+ throw context.runtime.newArgumentError("unknown keyword: " + opts.keys().first());
+ }
+
+ if (opts.size() > 1) {
+ throw context.runtime.newArgumentError("unknown keywords: " + opts.keys().join(context, runtime.newString(", ")));
+ }
+ }
+
/**
* Efficiently looks up items with a {@link org.jruby.RubySymbol Symbol} key
* @param key The Symbol name to look up for
@@ -50,7 +67,10 @@ private RuntimeInfo getRuntimeInfo() {
* if not found
*/
IRubyObject get(String key) {
- return opts == null ? null : opts.fastARef(runtime.newSymbol(key));
+ if (opts == null) {
+ return null;
+ }
+ return opts.delete(runtime.newSymbol(key));
}
boolean hasKey(String key) {
diff --git a/java/src/json/ext/Parser.java b/java/src/json/ext/Parser.java
index c54d1a7f9..59ebda179 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");
@@ -130,6 +115,8 @@ public IRubyObject initialize(ThreadContext context, IRubyObject options) {
this.decimalFactory = this::createCustomDecimal;
}
+ opts.ensureEmpty();
+
return this;
}
@@ -585,16 +572,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 +837,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 74e258424..1e7573df9 100644
--- a/lib/json.rb
+++ b/lib/json.rb
@@ -141,18 +141,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}
#
# ---
#
@@ -190,13 +184,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}
#
# ---
#
@@ -267,11 +259,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,
@@ -360,18 +347,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}'
#
# ---
#
@@ -392,8 +374,9 @@
# # Raises SystemStackError: stack level too deep
# JSON.generate(obj, max_nesting: false)
#
-# Setting +max_nesting+ to +false+ can lead to a stackoverflow and may leave the program
-# in an unrecoverable state. It is discouraged.
+# Setting +max_nesting+ to +false+ or a very large number can lead to a stack overflow
+# which may leave the process in an unrecoverable state.
+# It is highly discouraged.
#
# ====== Escaping Options
#
@@ -463,241 +446,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..0512d4aef 100644
--- a/lib/json/common.rb
+++ b/lib/json/common.rb
@@ -3,25 +3,12 @@
require 'json/version'
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
+ def on_load(on_load, object_class, array_class)
+ on_load = object_class_proc(object_class, on_load) if object_class
+ on_load = array_class_proc(array_class, on_load) if array_class
+ on_load
end
private
@@ -47,77 +34,12 @@ 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
+ private_constant :ParserOptions
+ class << self
# :call-seq:
# JSON[object] -> new_array or new_string
#
@@ -130,12 +52,14 @@ def deprecation_warning(message, uplevel = 3) # :nodoc:
# ruby = [0, 1, nil]
# JSON[ruby] # => '[0,1,null]'
def [](object, opts = nil)
+ opts ||= {}.freeze
+
if object.is_a?(String)
- return JSON.parse(object, opts)
+ return JSON.parse(object, **opts)
elsif object.respond_to?(:to_str)
str = object.to_str
if str.is_a?(String)
- return JSON.parse(str, opts)
+ return JSON.parse(str, **opts)
end
end
@@ -193,57 +117,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_
- # hook of a class should be called; initial value is +json_class+:
- # JSON.create_id # => 'json_class'
- def self.create_id=(new_value)
- Thread.current[:"JSON.create_id"] = new_value.dup.freeze
- end
-
- # Returns the current create identifier.
- # See also JSON.create_id=.
- def self.create_id
- Thread.current[:"JSON.create_id"] || 'json_class'
end
NaN = Float::NAN
@@ -356,19 +241,17 @@ def to_json(state = nil, *)
# ---
#
# Raises an exception if +source+ is not valid JSON:
- # # Raises JSON::ParserError (783: unexpected token at ''):
- # JSON.parse('')
+ # # Raises JSON::ParserError unexpected character: 'invalid' at line 1 column 1 :
+ # JSON.parse('invalid')
#
- def parse(source, opts = nil)
- opts = ParserOptions.prepare(opts) unless opts.nil?
- Parser.parse(source, opts)
- end
+ def parse(source, on_load: nil, object_class: nil, array_class: nil, **options)
+ if object_class || array_class
+ on_load = ParserOptions.on_load(on_load, object_class, array_class)
+ end
- PARSE_L_OPTIONS = {
- max_nesting: false,
- allow_nan: true,
- }.freeze
- private_constant :PARSE_L_OPTIONS
+ options[:on_load] = on_load if on_load
+ Parser.parse(source, options)
+ end
# :call-seq:
# JSON.parse!(source, opts) -> object
@@ -381,34 +264,30 @@ def parse(source, opts = nil)
# - Option +max_nesting+, if not provided, defaults to +false+,
# which disables checking for nesting depth.
# - Option +allow_nan+, if not provided, defaults to +true+.
- def parse!(source, opts = nil)
- if opts.nil?
- parse(source, PARSE_L_OPTIONS)
- else
- parse(source, PARSE_L_OPTIONS.merge(opts))
- end
+ def parse!(source, **options)
+ parse(source, max_nesting: false, allow_nan: true, **options)
end
# :call-seq:
- # JSON.load_file(path, opts={}) -> object
+ # JSON.load_file(path, **) -> object
#
# Calls:
- # parse(File.read(path), opts)
+ # parse(File.read(path), **)
#
# See method #parse.
- def load_file(filespec, opts = nil)
- parse(File.read(filespec, encoding: Encoding::UTF_8), opts)
+ def load_file(filespec, ...)
+ parse(File.read(filespec, encoding: Encoding::UTF_8), ...)
end
# :call-seq:
- # JSON.load_file!(path, opts = {})
+ # JSON.load_file!(path, **)
#
# Calls:
- # JSON.parse!(File.read(path, opts))
+ # JSON.parse!(File.read(path), **)
#
# See method #parse!
- def load_file!(filespec, opts = nil)
- parse!(File.read(filespec, encoding: Encoding::UTF_8), opts)
+ def load_file!(filespec, ...)
+ parse!(File.read(filespec, encoding: Encoding::UTF_8), ...)
end
# :call-seq:
@@ -451,30 +330,8 @@ def generate(obj, opts = nil)
if State === opts
opts.generate(obj)
else
- State.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
+ State.generate(obj, opts.frozen? ? opts : opts.dup, nil)
end
- generate(obj, opts)
end
PRETTY_GENERATE_OPTIONS = {
@@ -530,43 +387,20 @@ def pretty_generate(obj, opts = nil)
raise TypeError, "can't convert #{opts.class} into Hash"
end
end
+
options = options.merge(opts)
end
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
#
# Returns the Ruby objects created by parsing the given +source+.
#
- # BEWARE: This method is meant to serialise data from trusted user input,
+ # BEWARE: This method is meant to deserialise 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.
#
@@ -586,7 +420,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=.
#
# ---
#
@@ -691,38 +524,8 @@ def pretty_generate(obj, opts = nil)
# #"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)
- end
-
- unless source.is_a?(String)
- if source.respond_to? :to_str
- source = source.to_str
- elsif source.respond_to? :to_io
- source = source.to_io.read
- elsif source.respond_to?(:read)
- source = source.read
- end
- end
-
- if opts[:allow_blank] && (source.nil? || source.empty?)
- source = 'null'
- end
-
- if proc
- opts = opts.dup
- opts[:on_load] = proc.to_proc
- end
-
- parse(source, opts)
+ def unsafe_load(source, proc = nil, **options)
+ load(source, proc, max_nesting: false, **options)
end
# :call-seq:
@@ -731,16 +534,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 +550,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=.
#
# ---
#
@@ -862,23 +654,7 @@ def unsafe_load(source, proc = nil, options = nil)
# #"Admin", "password"=>"0wn3d"}>}
#
- def load(source, proc = nil, options = nil)
- if proc && options.nil? && proc.is_a?(Hash)
- options = proc
- 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
-
+ def load(source, proc = nil, allow_blank: true, **options)
unless source.is_a?(String)
if source.respond_to? :to_str
source = source.to_str
@@ -889,30 +665,19 @@ def load(source, proc = nil, options = nil)
end
end
- if opts[:allow_blank] && (source.nil? || (String === source && source.empty?))
+ if allow_blank && (source.nil? || (String === source && source.empty?))
source = 'null'
end
if proc
- opts = opts.dup
- opts[:on_load] = proc.to_proc
+ parse(source, allow_nan: true, on_load: proc.to_proc, **options)
+ else
+ parse(source, allow_nan: true, **options)
end
-
- 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)
+ # JSON.dump(obj, io = nil, options = nil)
#
# Dumps +obj+ as a \JSON string, i.e. calls generate on the object and returns the result.
#
@@ -921,7 +686,6 @@ def load(source, proc = nil, options = nil)
# - Argument +io+, if given, should respond to method +write+;
# the \JSON \String is written to +io+, and +io+ is returned.
# If +io+ is not given, the \JSON \String is returned.
- # - Argument +limit+, if given, is passed to JSON.generate as option +max_nesting+.
#
# ---
#
@@ -938,99 +702,25 @@ def load(source, proc = nil, options = nil)
# puts File.read(path)
# Output:
# {"foo":[0,1],"bar":{"baz":2,"bat":3},"bam":"bad"}
- def dump(obj, anIO = nil, limit = nil, kwargs = nil)
+ def dump(obj, anIO = nil, kwargs = nil)
if kwargs.nil?
- if limit.nil?
- if anIO.is_a?(Hash)
- kwargs = anIO
- anIO = nil
- end
- elsif limit.is_a?(Hash)
- kwargs = limit
- limit = nil
+ if anIO.is_a?(Hash)
+ kwargs = anIO
+ anIO = nil
end
end
- unless anIO.nil?
- if anIO.respond_to?(:to_io)
- anIO = anIO.to_io
- elsif limit.nil? && !anIO.respond_to?(:write)
- anIO, limit = nil, anIO
- end
- end
-
- opts = JSON._dump_default_options
- opts = opts.merge(:max_nesting => limit) if limit
- opts = opts.merge(kwargs) if kwargs
-
- begin
- State.generate(obj, opts, anIO)
- rescue JSON::NestingError
- raise ArgumentError, "exceed depth limit"
- 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
+ if anIO&.respond_to?(:to_io)
+ anIO = anIO.to_io
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
+ opts = {
+ allow_nan: true,
+ }
+ opts.merge!(kwargs) if kwargs
- 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
+ State.generate(obj, opts, anIO)
end
- # :startdoc:
# JSON::Coder holds a parser and generator configuration.
#
@@ -1043,6 +733,27 @@ def const_missing(const_name)
# MyApp::JSONC_CODER.load(document)
#
class Coder
+ PARSER_OPTIONS = %i(
+ max_nesting
+ allow_nan
+ allow_trailing_comma
+ allow_comments
+ allow_control_characters
+ allow_invalid_escape
+ symbolize_names
+ freeze
+ allow_duplicate_key
+ decimal_class
+ ).freeze
+ private_constant :PARSER_OPTIONS
+
+ EXCLUDED_GENERATOR_OPTIONS = (PARSER_OPTIONS - %i(
+ max_nesting
+ allow_nan
+ allow_duplicate_key
+ )).freeze
+ private_constant :EXCLUDED_GENERATOR_OPTIONS
+
# :call-seq:
# JSON.new(options = nil, &block)
#
@@ -1067,17 +778,20 @@ class Coder
#
# puts MyApp::API_JSON_CODER.dump(Time.now.utc) # => "2025-01-21T08:41:44.286Z"
#
- def initialize(options = nil, &as_json)
- if options.nil?
- options = { strict: true }
- else
- options = options.dup
- options[:strict] = true
+ def initialize(object_class: nil, array_class: nil, on_load: nil, **options, &as_json)
+ if object_class || array_class
+ on_load = ParserOptions.on_load(on_load, object_class, array_class)
end
- options[:as_json] = as_json if as_json
+ parser_options = options.slice(*PARSER_OPTIONS)
+ parser_options[:on_load] = on_load if on_load
+ @parser_config = Ext::Parser::Config.new(parser_options).freeze
- @state = State.new(options).freeze
- @parser_config = Ext::Parser::Config.new(ParserOptions.prepare(options)).freeze
+ generator_options = options.reject { |k, _| EXCLUDED_GENERATOR_OPTIONS.include?(k) }
+ @state = State.new(
+ **generator_options,
+ strict: true,
+ as_json: as_json,
+ ).freeze
end
# call-seq:
@@ -1136,36 +850,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.rb b/lib/json/ext.rb
index 28500f149..da17df1e8 100644
--- a/lib/json/ext.rb
+++ b/lib/json/ext.rb
@@ -66,6 +66,4 @@ def empty?
end
end
end
-
- JSON_LOADED = true unless defined?(JSON::JSON_LOADED)
end
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..6dad5e7f4 100644
--- a/lib/json/truffle_ruby/generator.rb
+++ b/lib/json/truffle_ruby/generator.rb
@@ -167,7 +167,8 @@ def initialize(opts = nil)
@strict = false
@max_nesting = 100
@sort_keys = false
- configure(opts) if opts
+ @allow_duplicate_key = false
+ _configure(**opts) if opts
end
# This string is used to indent levels in the JSON text.
@@ -289,65 +290,48 @@ def strict?
# Configure this State instance with the Hash _opts_, and return
# itself.
- def configure(opts)
- if opts.respond_to?(:to_hash)
- opts = opts.to_hash
- elsif opts.respond_to?(:to_h)
- opts = opts.to_h
- else
- raise TypeError, "can't convert #{opts.class} into Hash"
- end
-
- if opts[:depth]&.negative?
- raise ArgumentError, "depth must be >= 0 (got #{opts[:depth]})"
- end
-
- opts.each do |key, value|
- instance_variable_set "@#{key}", value
+ def configure(options)
+ unless Hash == options
+ if options.respond_to?(:to_hash)
+ options = options.to_hash
+ elsif options.respond_to?(:to_h)
+ options = options.to_h
+ end
end
+ _configure(**options)
+ end
+ alias merge configure
- # NOTE: If adding new instance variables here, check whether #generate should check them for #generate_json
- @indent = opts[:indent] || '' if opts.key?(:indent)
- @space = opts[:space] || '' if opts.key?(:space)
- @space_before = opts[:space_before] || '' if opts.key?(:space_before)
- @object_nl = opts[:object_nl] || '' if opts.key?(:object_nl)
- @array_nl = opts[:array_nl] || '' if opts.key?(:array_nl)
- @allow_nan = !!opts[:allow_nan] if opts.key?(:allow_nan)
- @as_json = opts[:as_json].to_proc if opts[:as_json]
- @ascii_only = opts[:ascii_only] if opts.key?(:ascii_only)
- self.sort_keys = opts[:sort_keys] if opts.key?(:sort_keys)
- @depth = opts[:depth] || 0
- @buffer_initial_length ||= opts[:buffer_initial_length]
-
- @script_safe = if opts.key?(:script_safe)
- !!opts[:script_safe]
- elsif opts.key?(:escape_slash)
- !!opts[:escape_slash]
- else
- false
+ private def _configure(
+ indent: '', space: '', space_before: '', object_nl: '', array_nl: '', allow_nan: false,
+ as_json: false, ascii_only: false, sort_keys: false, depth: 0, buffer_initial_length: 1024,
+ allow_duplicate_key: false, script_safe: false, strict: false, max_nesting: 100
+ )
+ if depth.negative?
+ raise ArgumentError, "depth must be >= 0 (got #{depth})"
end
- if opts.key?(:allow_duplicate_key)
- @allow_duplicate_key = !!opts[:allow_duplicate_key]
- else
- @allow_duplicate_key = nil # nil is deprecation
+ if max_nesting && !(Integer === max_nesting)
+ raise TypeError, ":max_nesting must be `false` or an Integer, got: #{max_nesting.class}"
end
- @strict = !!opts[:strict] if opts.key?(:strict)
-
- if !opts.key?(:max_nesting) # defaults to 100
- @max_nesting = 100
- elsif opts[:max_nesting]
- unless opts[:max_nesting].is_a?(Integer)
- raise TypeError, ":max_nesting must be an Integer, got: #{opts[:max_nesting].class}"
- end
- @max_nesting = opts[:max_nesting]
- else
- @max_nesting = 0
- end
+ @indent = indent || ''
+ @space = space || ''
+ @space_before = space_before || ''
+ @object_nl = object_nl || ''
+ @array_nl = array_nl || ''
+ @allow_nan = allow_nan || false
+ @as_json = as_json || false
+ @ascii_only = ascii_only || false
+ self.sort_keys = sort_keys
+ @depth = depth
+ @buffer_initial_length = buffer_initial_length
+ @allow_duplicate_key = allow_duplicate_key
+ @script_safe = script_safe
+ @strict = strict
+ @max_nesting = max_nesting || 0
self
end
- alias merge configure
def allow_duplicate_key? # :nodoc:
@allow_duplicate_key
@@ -362,10 +346,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 +396,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 +463,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 +527,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/lib/json/version.rb b/lib/json/version.rb
index 5c66a9d70..8e7e31755 100644
--- a/lib/json/version.rb
+++ b/lib/json/version.rb
@@ -1,5 +1,5 @@
# frozen_string_literal: true
module JSON
- VERSION = '2.21.2'
+ VERSION = '3.0.0.rc1'
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..1acfc9ba5 100644
--- a/test/json/json_common_interface_test.rb
+++ b/test/json/json_common_interface_test.rb
@@ -54,18 +54,22 @@ def test_state
assert_match(/::(TruffleRuby)?Generator::State\z/, JSON.state.name)
end
- def test_create_id
- assert_equal 'json_class', JSON.create_id
- JSON.create_id = 'foo_bar'
- assert_equal 'foo_bar', JSON.create_id
- ensure
- JSON.create_id = 'json_class'
- end
-
def test_parse
assert_equal [ 1, 2, 3, ], JSON.parse('[ 1, 2, 3 ]')
end
+ def test_parse_unknown_option
+ error = assert_raise(ArgumentError) do
+ JSON.parse('[]', quirks_mode: true)
+ end
+ assert_match "quirks_mode", error.message
+
+ error = assert_raise(ArgumentError) do
+ JSON.parse('[]', a: 1, b: 2)
+ end
+ assert_match "a, b", error.message
+ end
+
def test_parse_bang
assert_equal [ 1, Infinity, 3, ], JSON.parse!('[ 1, Infinity, 3 ]')
end
@@ -74,6 +78,18 @@ def test_generate
assert_equal '[1,2,3]', JSON.generate([ 1, 2, 3 ])
end
+ def test_generate_unknown_option
+ error = assert_raise(ArgumentError) do
+ JSON.generate([], quirks_mode: true)
+ end
+ assert_match "quirks_mode", error.message
+
+ error = assert_raise(ArgumentError) do
+ JSON.generate([], a: 1, b: 2)
+ end
+ assert_match(/unknown keywords: :?a, :?b/, error.message)
+ end
+
def test_fast_generate
assert_equal '[1,2,3]', JSON.generate([ 1, 2, 3 ])
end
@@ -237,27 +253,23 @@ def test_unsafe_load_null
end
def test_dump
- too_deep = '[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]'
+ too_deep = '[' * 101 + ']' * 101
obj = eval(too_deep)
- assert_equal too_deep, dump(obj)
- assert_kind_of String, Marshal.dump(obj)
- assert_raise(ArgumentError) { dump(obj, 100) }
- assert_raise(ArgumentError) { Marshal.dump(obj, 100) }
- assert_equal too_deep, dump(obj, 101)
- assert_kind_of String, Marshal.dump(obj, 101)
-
- assert_equal too_deep, JSON.dump(obj, StringIO.new, 101, strict: false).string
- assert_equal too_deep, dump(obj, StringIO.new, 101, strict: false).string
- assert_raise(JSON::GeneratorError) { JSON.dump(Object.new, StringIO.new, 101, strict: true).string }
- assert_raise(JSON::GeneratorError) { dump(Object.new, StringIO.new, 101, strict: true).string }
-
- assert_equal too_deep, dump(obj, nil, nil, strict: false)
- assert_equal too_deep, dump(obj, nil, 101, strict: false)
- assert_equal too_deep, dump(obj, StringIO.new, nil, strict: false).string
- assert_equal too_deep, dump(obj, nil, strict: false)
- assert_equal too_deep, dump(obj, 101, strict: false)
- assert_equal too_deep, dump(obj, StringIO.new, strict: false).string
- assert_equal too_deep, dump(obj, strict: false)
+ assert_raise(JSON::NestingError) { dump(obj) }
+ assert_equal too_deep, dump(obj, max_nesting: 101)
+
+ assert_equal too_deep, JSON.dump(obj, StringIO.new, max_nesting: 101, strict: false).string
+ assert_equal too_deep, dump(obj, StringIO.new, max_nesting: 101, strict: false).string
+ assert_raise(JSON::GeneratorError) { JSON.dump(Object.new, StringIO.new, max_nesting: 101, strict: true).string }
+ assert_raise(JSON::GeneratorError) { dump(Object.new, StringIO.new, max_nesting: 101, strict: true).string }
+
+ assert_raise(JSON::NestingError) { dump(obj, nil, strict: false) }
+ assert_equal too_deep, dump(obj, nil, max_nesting: 101, strict: false)
+ assert_raise(JSON::NestingError) { dump(obj, StringIO.new, strict: false) }
+ assert_raise(JSON::NestingError) { dump(obj, strict: false) }
+ assert_equal too_deep, dump(obj, max_nesting: 101, strict: false)
+ assert_raise(JSON::NestingError) { dump(obj, StringIO.new, strict: false) }
+ assert_raise(JSON::NestingError) { dump(obj, strict: false) }
end
def test_dump_in_io
@@ -271,12 +283,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 +321,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..27882458a 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,
@@ -343,7 +322,7 @@ def test_state_defaults
:space => "",
:space_before => "",
:sort_keys => false,
- }.sort_by { |n,| n.to_s }, state.to_h.sort_by { |n,| n.to_s })
+ }.sort_by { |n,| n.to_s }.to_h, state.to_h.sort_by { |n,| n.to_s }.to_h)
state = JSON::State.new(allow_duplicate_key: true)
assert_equal({
@@ -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
@@ -703,7 +651,7 @@ def test_backslash
#
data = [ "/\u2028\u2029" ]
json = '["\/\u2028\u2029"]'
- assert_equal json, generate(data, :escape_slash => true)
+ assert_equal json, generate(data, :script_safe => true)
#
data = ['"']
json = '["\""]'
@@ -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 f75741fd6..1819d0abf 100644
--- a/test/json/json_parser_test.rb
+++ b/test/json/json_parser_test.rb
@@ -2,10 +2,6 @@
require_relative 'test_helper'
require 'stringio'
require 'tempfile'
-begin
- require 'ostruct'
-rescue LoadError
-end
begin
require 'bigdecimal'
rescue LoadError
@@ -436,35 +432,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
@@ -496,9 +473,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
@@ -562,16 +536,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
@@ -714,20 +680,6 @@ def shifted?
end
end
- class SubArray2 < Array
- def to_json(*a)
- {
- JSON.create_id => self.class.name,
- 'ary' => to_a,
- }.to_json(*a)
- end
-
- def self.json_create(o)
- o.delete JSON.create_id
- o['ary']
- end
- end
-
class SubArrayWrapper
def initialize
@data = []
@@ -802,54 +754,31 @@ def test_parse_object_custom_hash_derived_class
assert res.item_set?
end
- if defined?(::OpenStruct)
- class SubOpenStruct < OpenStruct
- def [](k)
- __send__(k)
- end
-
- def []=(k, v)
- @item_set = true
- __send__("#{k}=", v)
- end
+ class OpenStructLike
+ def initialize
+ @attrs = {}
+ end
- def item_set?
- @item_set
- end
+ def [](k)
+ @attrs[k.to_sym]
end
- def test_parse_object_custom_non_hash_derived_class
- res = parse('{"foo":"bar"}', :object_class => SubOpenStruct)
- assert_equal "bar", res.foo
- assert_equal "bar", res[:foo]
- assert_equal(SubOpenStruct, res.class)
- assert res.item_set?
+ def []=(k, v)
+ @attrs[k.to_sym] = v
end
- def test_parse_generic_object
- res = parse(
- '{"foo":"bar", "baz":{}}',
- :object_class => JSON::GenericObject
- )
- assert_equal(JSON::GenericObject, res.class)
- assert_equal "bar", res.foo
- assert_equal "bar", res["foo"]
- assert_equal "bar", res[:foo]
- assert_equal "bar", res.to_hash[:foo]
- assert_equal(JSON::GenericObject, res.baz.class)
+ def method_missing(name, ...)
+ @attrs.fetch(name) do
+ super
+ end
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)
+ def test_parse_object_custom_non_hash_derived_class
+ res = parse('{"foo":"bar"}', :object_class => OpenStructLike)
+ assert_equal "bar", res.foo
+ assert_equal "bar", res[:foo]
+ assert_equal(OpenStructLike, res.class)
end
def test_generate_core_subclasses_with_default_to_json
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
diff --git a/test/json/ractor_test.rb b/test/json/ractor_test.rb
index e53c405a7..9e28f13c1 100644
--- a/test/json/ractor_test.rb
+++ b/test/json/ractor_test.rb
@@ -19,10 +19,18 @@ module RactorBackport
end
def test_generate
+ roundtrip_test(:generate, :parse)
+ end
+
+ def test_dump
+ roundtrip_test(:dump, :load)
+ end
+
+ def roundtrip_test(generator_method, parser_method)
pid = fork do
Warning[:experimental] = false
- r = Ractor.new do
- json = JSON.generate({
+ generator = Ractor.new(generator_method) do |generator_method|
+ json = JSON.public_send(generator_method, {
'a' => 2,
'b' => 3.141,
'c' => 'c',
@@ -34,10 +42,14 @@ def test_generate
})
JSON.parse(json)
end
- expected_json = JSON.parse('{"a":2,"b":3.141,"c":"c","d":[1,"b",3.14],"e":{"foo":"bar"},' +
+
+ parser = Ractor.new(parser_method) do |parser_method|
+ JSON.public_send(parser_method, '{"a":2,"b":3.141,"c":"c","d":[1,"b",3.14],"e":{"foo":"bar"},' +
'"g":"\\"\\u0000\\u001f","h":1000.0,"i":0.001}')
- actual_json = r.value
+ end
+ actual_json = generator.value
+ expected_json = parser.value
if expected_json == actual_json
exit 0
else
diff --git a/test/json/resumable_parser_test.rb b/test/json/resumable_parser_test.rb
index 0b8e13560..289da7547 100644
--- a/test/json/resumable_parser_test.rb
+++ b/test/json/resumable_parser_test.rb
@@ -689,19 +689,6 @@ def test_parse_error_message
assert_equal 0, error.column
end
- def test_duplicate_key_warning_message
- parser = new_parser
- parser << (%q({"a":1,"a":2,"pad":") + ("x" * (4 * 1024 * 1024)))
- refute parser.parse
-
- parser << %q(",)
- refute parser.parse
-
- assert_deprecated_warning(/duplicate key "a"/) do
- parser.partial_value
- end
- end
-
private
def assert_parse_error(json, expected_error_message = nil)