json 2.13.2 → 2.15.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 71499860706a6f27871853ec88fe26d5bd6a53f85fba2e9764b9ef0aadc170d9
4
- data.tar.gz: c07cd26190c4f36864490465890c162e1b9ce0ae1f262069e51c94d7cf74b117
3
+ metadata.gz: b883349b3a8a8c1ccb003e74779a577e3a16e8c3d8541693fb477a78aeac3a68
4
+ data.tar.gz: efb11cf9e69ece0ebb11d33aec23401b0c1ed1d7a39c351f82998ba82cbd47b8
5
5
  SHA512:
6
- metadata.gz: f1c626d30c67e99c56d9f411b7944b6263261676567c02f6d57ba7566087743b26835d3c6c4c1636a0dc76cc2b8c4e2739a1130f286cd069406c38c66138df97
7
- data.tar.gz: eb4fd62079fc730962359e4128f7abd36ca04c17b580506c3e8e3ebdd0e8e0c54c3cf75d1ae899dc53e13697bfa0c11c6c04277d701cfc83fa9ee611785ac85e
6
+ metadata.gz: f78e23c6bd8b8dcddaaf051d6ae60253fd6c96e108395b475d825d9daf3fba4f754f280ae095f456ee67917dd991c8477caa86038f378f23d49e17744467cb7a
7
+ data.tar.gz: 64946a58a223efb1e333ff15eba3776d44505cfb78ba0102350177b6351f4643107db5f37ddb65c41569b4d58b5147176c8043b182abd152c74c63351be958fd
data/CHANGES.md CHANGED
@@ -2,6 +2,43 @@
2
2
 
3
3
  ### Unreleased
4
4
 
5
+ ### 2025-10-25 (2.15.2)
6
+
7
+ * Fix `JSON::Coder` to have one dedicated depth counter per invocation.
8
+ After encountering a circular reference in `JSON::Coder#dump`, any further `#dump` call would raise `JSON::NestingError`.
9
+
10
+ ### 2025-10-07 (2.15.1)
11
+
12
+ * Fix incorrect escaping in the JRuby extension when encoding shared strings.
13
+
14
+ ### 2025-09-22 (2.15.0)
15
+
16
+ * `JSON::Coder` callback now receive a second argument to convey whether the object is a hash key.
17
+ * Tuned the floating point number generator to not use scientific notation as aggressively.
18
+
19
+ ### 2025-09-18 (2.14.1)
20
+
21
+ * Fix `IndexOutOfBoundsException` in the JRuby extension when encoding shared strings.
22
+
23
+ ### 2025-09-18 (2.14.0)
24
+
25
+ * Add new `allow_duplicate_key` generator options. By default a warning is now emitted when a duplicated key is encountered.
26
+ In `json 3.0` an error will be raised.
27
+ ```ruby
28
+ >> Warning[:deprecated] = true
29
+ >> puts JSON.generate({ foo: 1, "foo" => 2 })
30
+ (irb):2: warning: detected duplicate key "foo" in {foo: 1, "foo" => 2}.
31
+ This will raise an error in json 3.0 unless enabled via `allow_duplicate_key: true`
32
+ {"foo":1,"foo":2}
33
+ >> JSON.generate({ foo: 1, "foo" => 2 }, allow_duplicate_key: false)
34
+ detected duplicate key "foo" in {foo: 1, "foo" => 2} (JSON::GeneratorError)
35
+ ```
36
+ * Fix `JSON.generate` `strict: true` mode to also restrict hash keys.
37
+ * Fix `JSON::Coder` to also invoke block for hash keys that aren't strings nor symbols.
38
+ * Fix `JSON.unsafe_load` usage with proc
39
+ * Fix the parser to more consistently reject invalid UTF-16 surogate pairs.
40
+ * Stop defining `String.json_create`, `String#to_json_raw`, `String#to_json_raw_object` when `json/add` isn't loaded.
41
+
5
42
  ### 2025-07-28 (2.13.2)
6
43
 
7
44
  * Improve duplicate key warning and errors to include the key name and point to the right caller.
@@ -44,7 +81,7 @@
44
81
  ### 2025-04-24 (2.11.1)
45
82
 
46
83
  * Add back `JSON.restore`, `JSON.unparse`, `JSON.fast_unparse` and `JSON.pretty_unparse`.
47
- These were deprecated 16 years ago, but never emited warnings, only undocumented, so are
84
+ These were deprecated 16 years ago, but never emitted warnings, only undocumented, so are
48
85
  still used by a few gems.
49
86
 
50
87
  ### 2025-04-24 (2.11.0)
@@ -71,7 +108,7 @@
71
108
  ### 2025-03-12 (2.10.2)
72
109
 
73
110
  * Fix a potential crash in the C extension parser.
74
- * Raise a ParserError on all incomplete unicode escape sequence. This was the behavior until `2.10.0` unadvertently changed it.
111
+ * Raise a ParserError on all incomplete unicode escape sequence. This was the behavior until `2.10.0` inadvertently changed it.
75
112
  * Ensure document snippets that are included in parser errors don't include truncated multibyte characters.
76
113
  * Ensure parser error snippets are valid UTF-8.
77
114
  * Fix `JSON::GeneratorError#detailed_message` on Ruby < 3.2
@@ -102,7 +139,7 @@
102
139
 
103
140
  ### 2024-11-14 (2.8.2)
104
141
 
105
- * `JSON.load_file` explictly read the file as UTF-8.
142
+ * `JSON.load_file` explicitly read the file as UTF-8.
106
143
 
107
144
  ### 2024-11-06 (2.8.1)
108
145
 
@@ -110,7 +147,7 @@
110
147
 
111
148
  ### 2024-11-06 (2.8.0)
112
149
 
113
- * Emit a deprecation warning when `JSON.load` create custom types without the `create_additions` option being explictly enabled.
150
+ * Emit a deprecation warning when `JSON.load` create custom types without the `create_additions` option being explicitly enabled.
114
151
  * Prefer to use `JSON.unsafe_load(string)` or `JSON.load(string, create_additions: true)`.
115
152
  * Emit a deprecation warning when serializing valid UTF-8 strings encoded in `ASCII_8BIT` aka `BINARY`.
116
153
  * Bump required Ruby version to 2.7.
@@ -118,7 +155,7 @@
118
155
  pre-existing support for comments, make it suitable to parse `jsonc` documents.
119
156
  * Many performance improvements to `JSON.parse` and `JSON.load`, up to `1.7x` faster on real world documents.
120
157
  * Some minor performance improvements to `JSON.dump` and `JSON.generate`.
121
- * `JSON.pretty_generate` no longer include newline inside empty object and arrays.
158
+ * `JSON.pretty_generate` no longer includes newlines inside empty object and arrays.
122
159
 
123
160
  ### 2024-11-04 (2.7.6)
124
161
 
@@ -135,13 +172,13 @@
135
172
  * Workaround a bug in 3.4.8 and older https://github.com/rubygems/rubygems/pull/6490.
136
173
  This bug would cause some gems with native extension to fail during compilation.
137
174
  * Workaround different versions of `json` and `json_pure` being loaded (not officially supported).
138
- * Make `json_pure` Ractor compatible.
175
+ * Make `json_pure` Ractor compatible.
139
176
 
140
177
  ### 2024-10-24 (2.7.3)
141
178
 
142
179
  * Numerous performance optimizations in `JSON.generate` and `JSON.dump` (up to 2 times faster).
143
- * Limit the size of ParserError exception messages, only include up to 32 bytes of the unparseable source.
144
- * Fix json-pure's `Object#to_json` to accept non state arguments
180
+ * Limit the size of ParserError exception messages, only include up to 32 bytes of the unparsable source.
181
+ * Fix json-pure's `Object#to_json` to accept non-state arguments.
145
182
  * Fix multiline comment support in `json-pure`.
146
183
  * Fix `JSON.parse` to no longer mutate the argument encoding when passed an ASCII-8BIT string.
147
184
  * Fix `String#to_json` to raise on invalid encoding in `json-pure`.
@@ -286,6 +323,7 @@
286
323
  ## 2015-09-11 (2.0.0)
287
324
  * Now complies to newest JSON RFC 7159.
288
325
  * Implements compatibility to ruby 2.4 integer unification.
326
+ * Removed support for `quirks_mode` option.
289
327
  * Drops support for old rubies whose life has ended, that is rubies < 2.0.
290
328
  Also see https://www.ruby-lang.org/en/news/2014/07/01/eol-for-1-8-7-and-1-9-2/
291
329
  * There were still some mentions of dual GPL licensing in the source, but JSON
data/README.md CHANGED
@@ -97,7 +97,7 @@ Instead it is recommended to use the newer `JSON::Coder` API:
97
97
 
98
98
  ```ruby
99
99
  module MyApp
100
- API_JSON_CODER = JSON::Coder.new do |object|
100
+ API_JSON_CODER = JSON::Coder.new do |object, is_object_key|
101
101
  case object
102
102
  when Time
103
103
  object.iso8601(3)
@@ -113,6 +113,8 @@ puts MyApp::API_JSON_CODER.dump(Time.now.utc) # => "2025-01-21T08:41:44.286Z"
113
113
  The provided block is called for all objects that don't have a native JSON equivalent, and
114
114
  must return a Ruby object that has a native JSON equivalent.
115
115
 
116
+ It is also called for objects that do have a JSON equivalent, but are used as Hash keys, for instance `{ 1 => 2}`.
117
+
116
118
  ## Combining JSON fragments
117
119
 
118
120
  To combine JSON fragments into a bigger JSON document, you can use `JSON::Fragment`:
@@ -24,6 +24,14 @@ typedef unsigned char _Bool;
24
24
  #endif
25
25
  #endif
26
26
 
27
+ #ifndef NOINLINE
28
+ #if defined(__has_attribute) && __has_attribute(noinline)
29
+ #define NOINLINE() __attribute__((noinline))
30
+ #else
31
+ #define NOINLINE()
32
+ #endif
33
+ #endif
34
+
27
35
  #ifndef RB_UNLIKELY
28
36
  #define RB_UNLIKELY(expr) expr
29
37
  #endif
@@ -169,12 +177,17 @@ static inline void fbuffer_inc_capa(FBuffer *fb, unsigned long requested)
169
177
  }
170
178
  }
171
179
 
172
- static void fbuffer_append(FBuffer *fb, const char *newstr, unsigned long len)
180
+ static inline void fbuffer_append_reserved(FBuffer *fb, const char *newstr, unsigned long len)
181
+ {
182
+ MEMCPY(fb->ptr + fb->len, newstr, char, len);
183
+ fbuffer_consumed(fb, len);
184
+ }
185
+
186
+ static inline void fbuffer_append(FBuffer *fb, const char *newstr, unsigned long len)
173
187
  {
174
188
  if (len > 0) {
175
189
  fbuffer_inc_capa(fb, len);
176
- MEMCPY(fb->ptr + fb->len, newstr, char, len);
177
- fbuffer_consumed(fb, len);
190
+ fbuffer_append_reserved(fb, newstr, len);
178
191
  }
179
192
  }
180
193
 
@@ -197,11 +210,24 @@ static void fbuffer_append_str(FBuffer *fb, VALUE str)
197
210
  const char *newstr = StringValuePtr(str);
198
211
  unsigned long len = RSTRING_LEN(str);
199
212
 
200
- RB_GC_GUARD(str);
201
-
202
213
  fbuffer_append(fb, newstr, len);
203
214
  }
204
215
 
216
+ static void fbuffer_append_str_repeat(FBuffer *fb, VALUE str, size_t repeat)
217
+ {
218
+ const char *newstr = StringValuePtr(str);
219
+ unsigned long len = RSTRING_LEN(str);
220
+
221
+ fbuffer_inc_capa(fb, repeat * len);
222
+ while (repeat) {
223
+ #ifdef JSON_DEBUG
224
+ fb->requested = len;
225
+ #endif
226
+ fbuffer_append_reserved(fb, newstr, len);
227
+ repeat--;
228
+ }
229
+ }
230
+
205
231
  static inline void fbuffer_append_char(FBuffer *fb, char newchr)
206
232
  {
207
233
  fbuffer_inc_capa(fb, 1);
@@ -9,6 +9,12 @@
9
9
 
10
10
  /* ruby api and some helpers */
11
11
 
12
+ enum duplicate_key_action {
13
+ JSON_DEPRECATED = 0,
14
+ JSON_IGNORE,
15
+ JSON_RAISE,
16
+ };
17
+
12
18
  typedef struct JSON_Generator_StateStruct {
13
19
  VALUE indent;
14
20
  VALUE space;
@@ -21,6 +27,9 @@ typedef struct JSON_Generator_StateStruct {
21
27
  long depth;
22
28
  long buffer_initial_length;
23
29
 
30
+ enum duplicate_key_action on_duplicate_key;
31
+
32
+ bool as_json_single_arg;
24
33
  bool allow_nan;
25
34
  bool ascii_only;
26
35
  bool script_safe;
@@ -31,10 +40,10 @@ typedef struct JSON_Generator_StateStruct {
31
40
  #define RB_UNLIKELY(cond) (cond)
32
41
  #endif
33
42
 
34
- static VALUE mJSON, cState, cFragment, mString_Extend, eGeneratorError, eNestingError, Encoding_UTF_8;
43
+ static VALUE mJSON, cState, cFragment, eGeneratorError, eNestingError, Encoding_UTF_8;
35
44
 
36
45
  static ID i_to_s, i_to_json, i_new, i_pack, i_unpack, i_create_id, i_extend, i_encode;
37
- static VALUE sym_indent, sym_space, sym_space_before, sym_object_nl, sym_array_nl, sym_max_nesting, sym_allow_nan,
46
+ 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,
38
47
  sym_ascii_only, sym_depth, sym_buffer_initial_length, sym_script_safe, sym_escape_slash, sym_strict, sym_as_json;
39
48
 
40
49
 
@@ -137,8 +146,8 @@ static inline FORCE_INLINE void search_flush(search_state *search)
137
146
  {
138
147
  // Do not remove this conditional without profiling, specifically escape-heavy text.
139
148
  // escape_UTF8_char_basic will advance search->ptr and search->cursor (effectively a search_flush).
140
- // For back-to-back characters that need to be escaped, specifcally for the SIMD code paths, this method
141
- // will be called just before calling escape_UTF8_char_basic. There will be no characers to append for the
149
+ // For back-to-back characters that need to be escaped, specifically for the SIMD code paths, this method
150
+ // will be called just before calling escape_UTF8_char_basic. There will be no characters to append for the
142
151
  // consecutive characters that need to be escaped. While the fbuffer_append is a no-op if
143
152
  // nothing needs to be flushed, we can save a few memory references with this conditional.
144
153
  if (search->ptr > search->cursor) {
@@ -835,18 +844,6 @@ static VALUE mFloat_to_json(int argc, VALUE *argv, VALUE self)
835
844
  return cState_partial_generate(Vstate, self, generate_json_float, Qfalse);
836
845
  }
837
846
 
838
- /*
839
- * call-seq: String.included(modul)
840
- *
841
- * Extends _modul_ with the String::Extend module.
842
- */
843
- static VALUE mString_included_s(VALUE self, VALUE modul)
844
- {
845
- VALUE result = rb_funcall(modul, i_extend, 1, mString_Extend);
846
- rb_call_super(1, &modul);
847
- return result;
848
- }
849
-
850
847
  /*
851
848
  * call-seq: to_json(*)
852
849
  *
@@ -861,51 +858,6 @@ static VALUE mString_to_json(int argc, VALUE *argv, VALUE self)
861
858
  return cState_partial_generate(Vstate, self, generate_json_string, Qfalse);
862
859
  }
863
860
 
864
- /*
865
- * call-seq: to_json_raw_object()
866
- *
867
- * This method creates a raw object hash, that can be nested into
868
- * other data structures and will be generated as a raw string. This
869
- * method should be used, if you want to convert raw strings to JSON
870
- * instead of UTF-8 strings, e. g. binary data.
871
- */
872
- static VALUE mString_to_json_raw_object(VALUE self)
873
- {
874
- VALUE ary;
875
- VALUE result = rb_hash_new();
876
- rb_hash_aset(result, rb_funcall(mJSON, i_create_id, 0), rb_class_name(rb_obj_class(self)));
877
- ary = rb_funcall(self, i_unpack, 1, rb_str_new2("C*"));
878
- rb_hash_aset(result, rb_utf8_str_new_lit("raw"), ary);
879
- return result;
880
- }
881
-
882
- /*
883
- * call-seq: to_json_raw(*args)
884
- *
885
- * This method creates a JSON text from the result of a call to
886
- * to_json_raw_object of this String.
887
- */
888
- static VALUE mString_to_json_raw(int argc, VALUE *argv, VALUE self)
889
- {
890
- VALUE obj = mString_to_json_raw_object(self);
891
- Check_Type(obj, T_HASH);
892
- return mHash_to_json(argc, argv, obj);
893
- }
894
-
895
- /*
896
- * call-seq: json_create(o)
897
- *
898
- * Raw Strings are JSON Objects (the raw bytes are stored in an array for the
899
- * key "raw"). The Ruby String can be created by this module method.
900
- */
901
- static VALUE mString_Extend_json_create(VALUE self, VALUE o)
902
- {
903
- VALUE ary;
904
- Check_Type(o, T_HASH);
905
- ary = rb_hash_aref(o, rb_str_new2("raw"));
906
- return rb_funcall(ary, i_pack, 1, rb_str_new2("C*"));
907
- }
908
-
909
861
  /*
910
862
  * call-seq: to_json(*)
911
863
  *
@@ -1044,8 +996,11 @@ static inline VALUE vstate_get(struct generate_json_data *data)
1044
996
  }
1045
997
 
1046
998
  struct hash_foreach_arg {
999
+ VALUE hash;
1047
1000
  struct generate_json_data *data;
1048
- int iter;
1001
+ int first_key_type;
1002
+ bool first;
1003
+ bool mixed_keys_encountered;
1049
1004
  };
1050
1005
 
1051
1006
  static VALUE
@@ -1063,6 +1018,29 @@ convert_string_subclass(VALUE key)
1063
1018
  return key_to_s;
1064
1019
  }
1065
1020
 
1021
+ NOINLINE()
1022
+ static void
1023
+ json_inspect_hash_with_mixed_keys(struct hash_foreach_arg *arg)
1024
+ {
1025
+ if (arg->mixed_keys_encountered) {
1026
+ return;
1027
+ }
1028
+ arg->mixed_keys_encountered = true;
1029
+
1030
+ JSON_Generator_State *state = arg->data->state;
1031
+ if (state->on_duplicate_key != JSON_IGNORE) {
1032
+ VALUE do_raise = state->on_duplicate_key == JSON_RAISE ? Qtrue : Qfalse;
1033
+ rb_funcall(mJSON, rb_intern("on_mixed_keys_hash"), 2, arg->hash, do_raise);
1034
+ }
1035
+ }
1036
+
1037
+ static VALUE
1038
+ json_call_as_json(JSON_Generator_State *state, VALUE object, VALUE is_key)
1039
+ {
1040
+ VALUE proc_args[2] = {object, is_key};
1041
+ return rb_proc_call_with_block(state->as_json, 2, proc_args, Qnil);
1042
+ }
1043
+
1066
1044
  static int
1067
1045
  json_object_i(VALUE key, VALUE val, VALUE _arg)
1068
1046
  {
@@ -1073,21 +1051,33 @@ json_object_i(VALUE key, VALUE val, VALUE _arg)
1073
1051
  JSON_Generator_State *state = data->state;
1074
1052
 
1075
1053
  long depth = state->depth;
1076
- int j;
1054
+ int key_type = rb_type(key);
1055
+
1056
+ if (arg->first) {
1057
+ arg->first = false;
1058
+ arg->first_key_type = key_type;
1059
+ }
1060
+ else {
1061
+ fbuffer_append_char(buffer, ',');
1062
+ }
1077
1063
 
1078
- if (arg->iter > 0) fbuffer_append_char(buffer, ',');
1079
1064
  if (RB_UNLIKELY(data->state->object_nl)) {
1080
1065
  fbuffer_append_str(buffer, data->state->object_nl);
1081
1066
  }
1082
1067
  if (RB_UNLIKELY(data->state->indent)) {
1083
- for (j = 0; j < depth; j++) {
1084
- fbuffer_append_str(buffer, data->state->indent);
1085
- }
1068
+ fbuffer_append_str_repeat(buffer, data->state->indent, depth);
1086
1069
  }
1087
1070
 
1088
1071
  VALUE key_to_s;
1089
- switch (rb_type(key)) {
1072
+ bool as_json_called = false;
1073
+
1074
+ start:
1075
+ switch (key_type) {
1090
1076
  case T_STRING:
1077
+ if (RB_UNLIKELY(arg->first_key_type != T_STRING)) {
1078
+ json_inspect_hash_with_mixed_keys(arg);
1079
+ }
1080
+
1091
1081
  if (RB_LIKELY(RBASIC_CLASS(key) == rb_cString)) {
1092
1082
  key_to_s = key;
1093
1083
  } else {
@@ -1095,9 +1085,23 @@ json_object_i(VALUE key, VALUE val, VALUE _arg)
1095
1085
  }
1096
1086
  break;
1097
1087
  case T_SYMBOL:
1088
+ if (RB_UNLIKELY(arg->first_key_type != T_SYMBOL)) {
1089
+ json_inspect_hash_with_mixed_keys(arg);
1090
+ }
1091
+
1098
1092
  key_to_s = rb_sym2str(key);
1099
1093
  break;
1100
1094
  default:
1095
+ if (data->state->strict) {
1096
+ if (RTEST(data->state->as_json) && !as_json_called) {
1097
+ key = json_call_as_json(data->state, key, Qtrue);
1098
+ key_type = rb_type(key);
1099
+ as_json_called = true;
1100
+ goto start;
1101
+ } else {
1102
+ raise_generator_error(key, "%"PRIsVALUE" not allowed as object key in JSON", CLASS_OF(key));
1103
+ }
1104
+ }
1101
1105
  key_to_s = rb_convert_type(key, T_STRING, "String", "to_s");
1102
1106
  break;
1103
1107
  }
@@ -1112,7 +1116,6 @@ json_object_i(VALUE key, VALUE val, VALUE _arg)
1112
1116
  if (RB_UNLIKELY(state->space)) fbuffer_append_str(buffer, data->state->space);
1113
1117
  generate_json(buffer, data, val);
1114
1118
 
1115
- arg->iter++;
1116
1119
  return ST_CONTINUE;
1117
1120
  }
1118
1121
 
@@ -1121,14 +1124,13 @@ static inline long increase_depth(struct generate_json_data *data)
1121
1124
  JSON_Generator_State *state = data->state;
1122
1125
  long depth = ++state->depth;
1123
1126
  if (RB_UNLIKELY(depth > state->max_nesting && state->max_nesting)) {
1124
- rb_raise(eNestingError, "nesting of %ld is too deep", --state->depth);
1127
+ rb_raise(eNestingError, "nesting of %ld is too deep. Did you try to serialize objects with circular references?", --state->depth);
1125
1128
  }
1126
1129
  return depth;
1127
1130
  }
1128
1131
 
1129
1132
  static void generate_json_object(FBuffer *buffer, struct generate_json_data *data, VALUE obj)
1130
1133
  {
1131
- int j;
1132
1134
  long depth = increase_depth(data);
1133
1135
 
1134
1136
  if (RHASH_SIZE(obj) == 0) {
@@ -1140,8 +1142,9 @@ static void generate_json_object(FBuffer *buffer, struct generate_json_data *dat
1140
1142
  fbuffer_append_char(buffer, '{');
1141
1143
 
1142
1144
  struct hash_foreach_arg arg = {
1145
+ .hash = obj,
1143
1146
  .data = data,
1144
- .iter = 0,
1147
+ .first = true,
1145
1148
  };
1146
1149
  rb_hash_foreach(obj, json_object_i, (VALUE)&arg);
1147
1150
 
@@ -1149,9 +1152,7 @@ static void generate_json_object(FBuffer *buffer, struct generate_json_data *dat
1149
1152
  if (RB_UNLIKELY(data->state->object_nl)) {
1150
1153
  fbuffer_append_str(buffer, data->state->object_nl);
1151
1154
  if (RB_UNLIKELY(data->state->indent)) {
1152
- for (j = 0; j < depth; j++) {
1153
- fbuffer_append_str(buffer, data->state->indent);
1154
- }
1155
+ fbuffer_append_str_repeat(buffer, data->state->indent, depth);
1155
1156
  }
1156
1157
  }
1157
1158
  fbuffer_append_char(buffer, '}');
@@ -1159,7 +1160,6 @@ static void generate_json_object(FBuffer *buffer, struct generate_json_data *dat
1159
1160
 
1160
1161
  static void generate_json_array(FBuffer *buffer, struct generate_json_data *data, VALUE obj)
1161
1162
  {
1162
- int i, j;
1163
1163
  long depth = increase_depth(data);
1164
1164
 
1165
1165
  if (RARRAY_LEN(obj) == 0) {
@@ -1170,15 +1170,13 @@ static void generate_json_array(FBuffer *buffer, struct generate_json_data *data
1170
1170
 
1171
1171
  fbuffer_append_char(buffer, '[');
1172
1172
  if (RB_UNLIKELY(data->state->array_nl)) fbuffer_append_str(buffer, data->state->array_nl);
1173
- for (i = 0; i < RARRAY_LEN(obj); i++) {
1173
+ for (int i = 0; i < RARRAY_LEN(obj); i++) {
1174
1174
  if (i > 0) {
1175
1175
  fbuffer_append_char(buffer, ',');
1176
1176
  if (RB_UNLIKELY(data->state->array_nl)) fbuffer_append_str(buffer, data->state->array_nl);
1177
1177
  }
1178
1178
  if (RB_UNLIKELY(data->state->indent)) {
1179
- for (j = 0; j < depth; j++) {
1180
- fbuffer_append_str(buffer, data->state->indent);
1181
- }
1179
+ fbuffer_append_str_repeat(buffer, data->state->indent, depth);
1182
1180
  }
1183
1181
  generate_json(buffer, data, RARRAY_AREF(obj, i));
1184
1182
  }
@@ -1186,9 +1184,7 @@ static void generate_json_array(FBuffer *buffer, struct generate_json_data *data
1186
1184
  if (RB_UNLIKELY(data->state->array_nl)) {
1187
1185
  fbuffer_append_str(buffer, data->state->array_nl);
1188
1186
  if (RB_UNLIKELY(data->state->indent)) {
1189
- for (j = 0; j < depth; j++) {
1190
- fbuffer_append_str(buffer, data->state->indent);
1191
- }
1187
+ fbuffer_append_str_repeat(buffer, data->state->indent, depth);
1192
1188
  }
1193
1189
  }
1194
1190
  fbuffer_append_char(buffer, ']');
@@ -1340,7 +1336,7 @@ static void generate_json_float(FBuffer *buffer, struct generate_json_data *data
1340
1336
  /* for NaN and Infinity values we either raise an error or rely on Float#to_s. */
1341
1337
  if (!allow_nan) {
1342
1338
  if (data->state->strict && data->state->as_json) {
1343
- VALUE casted_obj = rb_proc_call_with_block(data->state->as_json, 1, &obj, Qnil);
1339
+ VALUE casted_obj = json_call_as_json(data->state, obj, Qfalse);
1344
1340
  if (casted_obj != obj) {
1345
1341
  increase_depth(data);
1346
1342
  generate_json(buffer, data, casted_obj);
@@ -1357,12 +1353,11 @@ static void generate_json_float(FBuffer *buffer, struct generate_json_data *data
1357
1353
  }
1358
1354
 
1359
1355
  /* This implementation writes directly into the buffer. We reserve
1360
- * the 28 characters that fpconv_dtoa states as its maximum.
1356
+ * the 32 characters that fpconv_dtoa states as its maximum.
1361
1357
  */
1362
- fbuffer_inc_capa(buffer, 28);
1358
+ fbuffer_inc_capa(buffer, 32);
1363
1359
  char* d = buffer->ptr + buffer->len;
1364
1360
  int len = fpconv_dtoa(value, d);
1365
-
1366
1361
  /* fpconv_dtoa converts a float to its shortest string representation,
1367
1362
  * but it adds a ".0" if this is a plain integer.
1368
1363
  */
@@ -1429,7 +1424,7 @@ start:
1429
1424
  general:
1430
1425
  if (data->state->strict) {
1431
1426
  if (RTEST(data->state->as_json) && !as_json_called) {
1432
- obj = rb_proc_call_with_block(data->state->as_json, 1, &obj, Qnil);
1427
+ obj = json_call_as_json(data->state, obj, Qfalse);
1433
1428
  as_json_called = true;
1434
1429
  goto start;
1435
1430
  } else {
@@ -1496,10 +1491,39 @@ static VALUE cState_generate(int argc, VALUE *argv, VALUE self)
1496
1491
  rb_check_arity(argc, 1, 2);
1497
1492
  VALUE obj = argv[0];
1498
1493
  VALUE io = argc > 1 ? argv[1] : Qnil;
1499
- VALUE result = cState_partial_generate(self, obj, generate_json, io);
1494
+ return cState_partial_generate(self, obj, generate_json, io);
1495
+ }
1496
+
1497
+ static VALUE cState_generate_new(int argc, VALUE *argv, VALUE self)
1498
+ {
1499
+ rb_check_arity(argc, 1, 2);
1500
+ VALUE obj = argv[0];
1501
+ VALUE io = argc > 1 ? argv[1] : Qnil;
1502
+
1500
1503
  GET_STATE(self);
1501
- (void)state;
1502
- return result;
1504
+
1505
+ JSON_Generator_State new_state;
1506
+ MEMCPY(&new_state, state, JSON_Generator_State, 1);
1507
+
1508
+ // FIXME: depth shouldn't be part of JSON_Generator_State, as that prevents it from being used concurrently.
1509
+ new_state.depth = 0;
1510
+
1511
+ char stack_buffer[FBUFFER_STACK_SIZE];
1512
+ FBuffer buffer = {
1513
+ .io = RTEST(io) ? io : Qfalse,
1514
+ };
1515
+ fbuffer_stack_init(&buffer, state->buffer_initial_length, stack_buffer, FBUFFER_STACK_SIZE);
1516
+
1517
+ struct generate_json_data data = {
1518
+ .buffer = &buffer,
1519
+ .vstate = Qfalse,
1520
+ .state = &new_state,
1521
+ .obj = obj,
1522
+ .func = generate_json
1523
+ };
1524
+ rb_rescue(generate_json_try, (VALUE)&data, generate_json_rescue, (VALUE)&data);
1525
+
1526
+ return fbuffer_finalize(&buffer);
1503
1527
  }
1504
1528
 
1505
1529
  static VALUE cState_initialize(int argc, VALUE *argv, VALUE self)
@@ -1850,6 +1874,19 @@ static VALUE cState_ascii_only_set(VALUE self, VALUE enable)
1850
1874
  return Qnil;
1851
1875
  }
1852
1876
 
1877
+ static VALUE cState_allow_duplicate_key_p(VALUE self)
1878
+ {
1879
+ GET_STATE(self);
1880
+ switch (state->on_duplicate_key) {
1881
+ case JSON_IGNORE:
1882
+ return Qtrue;
1883
+ case JSON_DEPRECATED:
1884
+ return Qnil;
1885
+ default:
1886
+ return Qfalse;
1887
+ }
1888
+ }
1889
+
1853
1890
  /*
1854
1891
  * call-seq: depth
1855
1892
  *
@@ -1939,8 +1976,10 @@ static int configure_state_i(VALUE key, VALUE val, VALUE _arg)
1939
1976
  else if (key == sym_script_safe) { state->script_safe = RTEST(val); }
1940
1977
  else if (key == sym_escape_slash) { state->script_safe = RTEST(val); }
1941
1978
  else if (key == sym_strict) { state->strict = RTEST(val); }
1979
+ else if (key == sym_allow_duplicate_key) { state->on_duplicate_key = RTEST(val) ? JSON_IGNORE : JSON_RAISE; }
1942
1980
  else if (key == sym_as_json) {
1943
1981
  VALUE proc = RTEST(val) ? rb_convert_type(val, T_DATA, "Proc", "to_proc") : Qfalse;
1982
+ state->as_json_single_arg = proc && rb_proc_arity(proc) == 1;
1944
1983
  state_write_value(data, &state->as_json, proc);
1945
1984
  }
1946
1985
  return ST_CONTINUE;
@@ -2062,7 +2101,9 @@ void Init_generator(void)
2062
2101
  rb_define_method(cState, "buffer_initial_length", cState_buffer_initial_length, 0);
2063
2102
  rb_define_method(cState, "buffer_initial_length=", cState_buffer_initial_length_set, 1);
2064
2103
  rb_define_method(cState, "generate", cState_generate, -1);
2065
- rb_define_alias(cState, "generate_new", "generate"); // :nodoc:
2104
+ rb_define_method(cState, "generate_new", cState_generate_new, -1); // :nodoc:
2105
+
2106
+ rb_define_private_method(cState, "allow_duplicate_key?", cState_allow_duplicate_key_p, 0);
2066
2107
 
2067
2108
  rb_define_singleton_method(cState, "generate", cState_m_generate, 3);
2068
2109
 
@@ -2091,13 +2132,7 @@ void Init_generator(void)
2091
2132
  rb_define_method(mFloat, "to_json", mFloat_to_json, -1);
2092
2133
 
2093
2134
  VALUE mString = rb_define_module_under(mGeneratorMethods, "String");
2094
- rb_define_singleton_method(mString, "included", mString_included_s, 1);
2095
2135
  rb_define_method(mString, "to_json", mString_to_json, -1);
2096
- rb_define_method(mString, "to_json_raw", mString_to_json_raw, -1);
2097
- rb_define_method(mString, "to_json_raw_object", mString_to_json_raw_object, 0);
2098
-
2099
- mString_Extend = rb_define_module_under(mString, "Extend");
2100
- rb_define_method(mString_Extend, "json_create", mString_Extend_json_create, 1);
2101
2136
 
2102
2137
  VALUE mTrueClass = rb_define_module_under(mGeneratorMethods, "TrueClass");
2103
2138
  rb_define_method(mTrueClass, "to_json", mTrueClass_to_json, -1);
@@ -2134,6 +2169,7 @@ void Init_generator(void)
2134
2169
  sym_escape_slash = ID2SYM(rb_intern("escape_slash"));
2135
2170
  sym_strict = ID2SYM(rb_intern("strict"));
2136
2171
  sym_as_json = ID2SYM(rb_intern("as_json"));
2172
+ sym_allow_duplicate_key = ID2SYM(rb_intern("allow_duplicate_key"));
2137
2173
 
2138
2174
  usascii_encindex = rb_usascii_encindex();
2139
2175
  utf8_encindex = rb_utf8_encindex();
@@ -713,11 +713,16 @@ static VALUE json_string_unescape(JSON_ParserState *state, const char *string, c
713
713
  }
714
714
  if (pe[0] == '\\' && pe[1] == 'u') {
715
715
  uint32_t sur = unescape_unicode(state, (unsigned char *) pe + 2);
716
+
717
+ if ((sur & 0xFC00) != 0xDC00) {
718
+ raise_parse_error_at("invalid surrogate pair at %s", state, p);
719
+ }
720
+
716
721
  ch = (((ch & 0x3F) << 10) | ((((ch >> 6) & 0xF) + 1) << 16)
717
722
  | (sur & 0x3FF));
718
723
  pe += 5;
719
724
  } else {
720
- unescape = (char *) "?";
725
+ raise_parse_error_at("incomplete surrogate pair at %s", state, p);
721
726
  break;
722
727
  }
723
728
  }
@@ -975,7 +980,7 @@ static inline bool FORCE_INLINE string_scan(JSON_ParserState *state)
975
980
  if (RB_UNLIKELY(string_scan_table[(unsigned char)*state->cursor])) {
976
981
  return 1;
977
982
  }
978
- *state->cursor++;
983
+ state->cursor++;
979
984
  }
980
985
  return 0;
981
986
  }
@@ -1265,7 +1270,7 @@ static VALUE json_parse_any(JSON_ParserState *state, JSON_ParserConfig *config)
1265
1270
  break;
1266
1271
  }
1267
1272
 
1268
- raise_parse_error("unreacheable: %s", state);
1273
+ raise_parse_error("unreachable: %s", state);
1269
1274
  }
1270
1275
 
1271
1276
  static void json_ensure_eof(JSON_ParserState *state)
@@ -1314,7 +1319,7 @@ static int parser_config_init_i(VALUE key, VALUE val, VALUE data)
1314
1319
  else if (key == sym_symbolize_names) { config->symbolize_names = RTEST(val); }
1315
1320
  else if (key == sym_freeze) { config->freeze = RTEST(val); }
1316
1321
  else if (key == sym_on_load) { config->on_load_proc = RTEST(val) ? val : Qfalse; }
1317
- else if (key == sym_allow_duplicate_key) { config->on_duplicate_key = RTEST(val) ? JSON_IGNORE : JSON_RAISE; }
1322
+ else if (key == sym_allow_duplicate_key) { config->on_duplicate_key = RTEST(val) ? JSON_IGNORE : JSON_RAISE; }
1318
1323
  else if (key == sym_decimal_class) {
1319
1324
  if (RTEST(val)) {
1320
1325
  if (rb_respond_to(val, i_try_convert)) {
@@ -29,6 +29,10 @@
29
29
  #include <string.h>
30
30
  #include <stdint.h>
31
31
 
32
+ #ifdef JSON_DEBUG
33
+ #include <assert.h>
34
+ #endif
35
+
32
36
  #define npowers 87
33
37
  #define steppowers 8
34
38
  #define firstpower -348 /* 10 ^ -348 */
@@ -320,15 +324,7 @@ static int emit_digits(char* digits, int ndigits, char* dest, int K, bool neg)
320
324
  {
321
325
  int exp = absv(K + ndigits - 1);
322
326
 
323
- int max_trailing_zeros = 7;
324
-
325
- if(neg) {
326
- max_trailing_zeros -= 1;
327
- }
328
-
329
- /* write plain integer */
330
- if(K >= 0 && (exp < (ndigits + max_trailing_zeros))) {
331
-
327
+ if(K >= 0 && exp < 15) {
332
328
  memcpy(dest, digits, ndigits);
333
329
  memset(dest + ndigits, '0', K);
334
330
 
@@ -432,10 +428,12 @@ static int filter_special(double fp, char* dest)
432
428
  *
433
429
  * Input:
434
430
  * fp -> the double to convert, dest -> destination buffer.
435
- * The generated string will never be longer than 28 characters.
436
- * Make sure to pass a pointer to at least 28 bytes of memory.
431
+ * The generated string will never be longer than 32 characters.
432
+ * Make sure to pass a pointer to at least 32 bytes of memory.
437
433
  * The emitted string will not be null terminated.
438
434
  *
435
+ *
436
+ *
439
437
  * Output:
440
438
  * The number of written characters.
441
439
  *
@@ -474,6 +472,9 @@ static int fpconv_dtoa(double d, char dest[28])
474
472
  int ndigits = grisu2(d, digits, &K);
475
473
 
476
474
  str_len += emit_digits(digits, ndigits, dest + str_len, K, neg);
475
+ #ifdef JSON_DEBUG
476
+ assert(str_len <= 32);
477
+ #endif
477
478
 
478
479
  return str_len;
479
480
  }
data/lib/json/add/core.rb CHANGED
@@ -7,6 +7,7 @@ require 'json/add/date_time'
7
7
  require 'json/add/exception'
8
8
  require 'json/add/range'
9
9
  require 'json/add/regexp'
10
+ require 'json/add/string'
10
11
  require 'json/add/struct'
11
12
  require 'json/add/symbol'
12
13
  require 'json/add/time'
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+ unless defined?(::JSON::JSON_LOADED) and ::JSON::JSON_LOADED
3
+ require 'json'
4
+ end
5
+
6
+ class String
7
+ # call-seq: json_create(o)
8
+ #
9
+ # Raw Strings are JSON Objects (the raw bytes are stored in an array for the
10
+ # key "raw"). The Ruby String can be created by this class method.
11
+ def self.json_create(object)
12
+ object["raw"].pack("C*")
13
+ end
14
+
15
+ # call-seq: to_json_raw_object()
16
+ #
17
+ # This method creates a raw object hash, that can be nested into
18
+ # other data structures and will be generated as a raw string. This
19
+ # method should be used, if you want to convert raw strings to JSON
20
+ # instead of UTF-8 strings, e. g. binary data.
21
+ def to_json_raw_object
22
+ {
23
+ JSON.create_id => self.class.name,
24
+ "raw" => unpack("C*"),
25
+ }
26
+ end
27
+
28
+ # call-seq: to_json_raw(*args)
29
+ #
30
+ # This method creates a JSON text from the result of a call to
31
+ # to_json_raw_object of this String.
32
+ def to_json_raw(...)
33
+ to_json_raw_object.to_json(...)
34
+ end
35
+ end
data/lib/json/common.rb CHANGED
@@ -73,7 +73,7 @@ module JSON
73
73
  if opts[:create_additions] != false
74
74
  if class_name = object[JSON.create_id]
75
75
  klass = JSON.deep_const_get(class_name)
76
- if (klass.respond_to?(:json_creatable?) && klass.json_creatable?) || klass.respond_to?(:json_create)
76
+ if klass.respond_to?(:json_creatable?) ? klass.json_creatable? : klass.respond_to?(:json_create)
77
77
  create_additions_warning if create_additions.nil?
78
78
  object = klass.json_create(object)
79
79
  end
@@ -97,7 +97,7 @@ module JSON
97
97
 
98
98
  class << self
99
99
  def deprecation_warning(message, uplevel = 3) # :nodoc:
100
- gem_root = File.expand_path("../../../", __FILE__) + "/"
100
+ gem_root = File.expand_path("..", __dir__) + "/"
101
101
  caller_locations(uplevel, 10).each do |frame|
102
102
  if frame.path.nil? || frame.path.start_with?(gem_root) || frame.path.end_with?("/truffle/cext_ruby.rb", ".c")
103
103
  uplevel += 1
@@ -186,6 +186,25 @@ module JSON
186
186
 
187
187
  private
188
188
 
189
+ # Called from the extension when a hash has both string and symbol keys
190
+ def on_mixed_keys_hash(hash, do_raise)
191
+ set = {}
192
+ hash.each_key do |key|
193
+ key_str = key.to_s
194
+
195
+ if set[key_str]
196
+ message = "detected duplicate key #{key_str.inspect} in #{hash.inspect}"
197
+ if do_raise
198
+ raise GeneratorError, message
199
+ else
200
+ deprecation_warning("#{message}.\nThis will raise an error in json 3.0 unless enabled via `allow_duplicate_key: true`")
201
+ end
202
+ else
203
+ set[key_str] = true
204
+ end
205
+ end
206
+ end
207
+
189
208
  def deprecated_singleton_attr_accessor(*attrs)
190
209
  args = RUBY_VERSION >= "3.0" ? ", category: :deprecated" : ""
191
210
  attrs.each do |attr|
@@ -391,7 +410,7 @@ module JSON
391
410
  #
392
411
  # Returns a \String containing the generated \JSON data.
393
412
  #
394
- # See also JSON.fast_generate, JSON.pretty_generate.
413
+ # See also JSON.pretty_generate.
395
414
  #
396
415
  # Argument +obj+ is the Ruby object to be converted to \JSON.
397
416
  #
@@ -643,6 +662,7 @@ module JSON
643
662
  # when Array
644
663
  # obj.map! {|v| deserialize_obj v }
645
664
  # end
665
+ # obj
646
666
  # })
647
667
  # pp ruby
648
668
  # Output:
@@ -684,9 +704,13 @@ module JSON
684
704
  if opts[:allow_blank] && (source.nil? || source.empty?)
685
705
  source = 'null'
686
706
  end
687
- result = parse(source, opts)
688
- recurse_proc(result, &proc) if proc
689
- result
707
+
708
+ if proc
709
+ opts = opts.dup
710
+ opts[:on_load] = proc.to_proc
711
+ end
712
+
713
+ parse(source, opts)
690
714
  end
691
715
 
692
716
  # :call-seq:
@@ -803,6 +827,7 @@ module JSON
803
827
  # when Array
804
828
  # obj.map! {|v| deserialize_obj v }
805
829
  # end
830
+ # obj
806
831
  # })
807
832
  # pp ruby
808
833
  # Output:
@@ -1002,7 +1027,7 @@ module JSON
1002
1027
  # See {Parsing Options}[#module-JSON-label-Parsing+Options], and {Generating Options}[#module-JSON-label-Generating+Options].
1003
1028
  #
1004
1029
  # For generation, the <tt>strict: true</tt> option is always set. When a Ruby object with no native \JSON counterpart is
1005
- # encoutered, the block provided to the initialize method is invoked, and must return a Ruby object that has a native
1030
+ # encountered, the block provided to the initialize method is invoked, and must return a Ruby object that has a native
1006
1031
  # \JSON counterpart:
1007
1032
  #
1008
1033
  # module MyApp
@@ -8,20 +8,8 @@ module JSON
8
8
  #
9
9
  # Instantiates a new State object, configured by _opts_.
10
10
  #
11
- # _opts_ can have the following keys:
12
- #
13
- # * *indent*: a string used to indent levels (default: ''),
14
- # * *space*: a string that is put after, a : or , delimiter (default: ''),
15
- # * *space_before*: a string that is put before a : pair delimiter (default: ''),
16
- # * *object_nl*: a string that is put at the end of a JSON object (default: ''),
17
- # * *array_nl*: a string that is put at the end of a JSON array (default: ''),
18
- # * *allow_nan*: true if NaN, Infinity, and -Infinity should be
19
- # generated, otherwise an exception is thrown, if these values are
20
- # encountered. This options defaults to false.
21
- # * *ascii_only*: true if only ASCII characters should be generated. This
22
- # option defaults to false.
23
- # * *buffer_initial_length*: sets the initial length of the generator's
24
- # internal buffer.
11
+ # Argument +opts+, if given, contains a \Hash of options for the generation.
12
+ # See {Generating Options}[#module-JSON-label-Generating+Options].
25
13
  def initialize(opts = nil)
26
14
  if opts && !opts.empty?
27
15
  configure(opts)
@@ -68,6 +56,11 @@ module JSON
68
56
  buffer_initial_length: buffer_initial_length,
69
57
  }
70
58
 
59
+ allow_duplicate_key = allow_duplicate_key?
60
+ unless allow_duplicate_key.nil?
61
+ result[:allow_duplicate_key] = allow_duplicate_key
62
+ end
63
+
71
64
  instance_variables.each do |iv|
72
65
  iv = iv.to_s[1..-1]
73
66
  result[iv.to_sym] = self[iv]
@@ -52,14 +52,6 @@ module JSON
52
52
  table
53
53
  end
54
54
 
55
- def [](name)
56
- __send__(name)
57
- end unless method_defined?(:[])
58
-
59
- def []=(name, value)
60
- __send__("#{name}=", value)
61
- end unless method_defined?(:[]=)
62
-
63
55
  def |(other)
64
56
  self.class[other.to_hash.merge(to_hash)]
65
57
  end
@@ -47,6 +47,14 @@ module JSON
47
47
 
48
48
  SCRIPT_SAFE_ESCAPE_PATTERN = /[\/"\\\x0-\x1f\u2028-\u2029]/
49
49
 
50
+ def self.native_type?(value) # :nodoc:
51
+ (false == value || true == value || nil == value || String === value || Array === value || Hash === value || Integer === value || Float === value || Fragment === value)
52
+ end
53
+
54
+ def self.native_key?(key) # :nodoc:
55
+ (Symbol === key || String === key)
56
+ end
57
+
50
58
  # Convert a UTF8 encoded Ruby string _string_ to a JSON string, encoded with
51
59
  # UTF16 big endian characters as \u????, and return it.
52
60
  def self.utf8_to_json(string, script_safe = false) # :nodoc:
@@ -204,7 +212,7 @@ module JSON
204
212
  return if @max_nesting.zero?
205
213
  current_nesting = depth + 1
206
214
  current_nesting > @max_nesting and
207
- raise NestingError, "nesting of #{current_nesting} is too deep"
215
+ raise NestingError, "nesting of #{current_nesting} is too deep. Did you try to serialize objects with circular references?"
208
216
  end
209
217
 
210
218
  # Returns true, if circular data structures are checked,
@@ -271,6 +279,12 @@ module JSON
271
279
  false
272
280
  end
273
281
 
282
+ if opts.key?(:allow_duplicate_key)
283
+ @allow_duplicate_key = !!opts[:allow_duplicate_key]
284
+ else
285
+ @allow_duplicate_key = nil # nil is deprecation
286
+ end
287
+
274
288
  @strict = !!opts[:strict] if opts.key?(:strict)
275
289
 
276
290
  if !opts.key?(:max_nesting) # defaults to 100
@@ -284,6 +298,10 @@ module JSON
284
298
  end
285
299
  alias merge configure
286
300
 
301
+ def allow_duplicate_key? # :nodoc:
302
+ @allow_duplicate_key
303
+ end
304
+
287
305
  # Returns the configuration instance variables as a hash, that can be
288
306
  # passed to the configure method.
289
307
  def to_h
@@ -292,6 +310,11 @@ module JSON
292
310
  iv = iv.to_s[1..-1]
293
311
  result[iv.to_sym] = self[iv]
294
312
  end
313
+
314
+ if result[:allow_duplicate_key].nil?
315
+ result.delete(:allow_duplicate_key)
316
+ end
317
+
295
318
  result
296
319
  end
297
320
 
@@ -324,14 +347,27 @@ module JSON
324
347
  dup.generate(obj, anIO)
325
348
  end
326
349
 
350
+ private def initialize_copy(_orig)
351
+ @depth = 0
352
+ end
353
+
327
354
  # Handles @allow_nan, @buffer_initial_length, other ivars must be the default value (see above)
328
355
  private def generate_json(obj, buf)
329
356
  case obj
330
357
  when Hash
331
358
  buf << '{'
332
359
  first = true
360
+ key_type = nil
333
361
  obj.each_pair do |k,v|
334
- buf << ',' unless first
362
+ if first
363
+ key_type = k.class
364
+ else
365
+ if key_type && !@allow_duplicate_key && key_type != k.class
366
+ key_type = nil # stop checking
367
+ JSON.send(:on_mixed_keys_hash, obj, !@allow_duplicate_key.nil?)
368
+ end
369
+ buf << ','
370
+ end
335
371
 
336
372
  key_str = k.to_s
337
373
  if key_str.class == String
@@ -424,10 +460,10 @@ module JSON
424
460
  state = State.from_state(state) if state
425
461
  if state&.strict?
426
462
  value = self
427
- if state.strict? && !(false == value || true == value || nil == value || String === value || Array === value || Hash === value || Integer === value || Float === value || Fragment === value)
463
+ if state.strict? && !Generator.native_type?(value)
428
464
  if state.as_json
429
- value = state.as_json.call(value)
430
- unless false == value || true == value || nil == value || String === value || Array === value || Hash === value || Integer === value || Float === value || Fragment === value
465
+ value = state.as_json.call(value, false)
466
+ unless Generator.native_type?(value)
431
467
  raise GeneratorError.new("#{value.class} returned by #{state.as_json} not allowed in JSON", value)
432
468
  end
433
469
  value.to_json(state)
@@ -471,11 +507,30 @@ module JSON
471
507
  delim = ",#{state.object_nl}"
472
508
  result = +"{#{state.object_nl}"
473
509
  first = true
510
+ key_type = nil
474
511
  indent = !state.object_nl.empty?
475
512
  each { |key, value|
476
- result << delim unless first
513
+ if first
514
+ key_type = key.class
515
+ else
516
+ if key_type && !state.allow_duplicate_key? && key_type != key.class
517
+ key_type = nil # stop checking
518
+ JSON.send(:on_mixed_keys_hash, self, state.allow_duplicate_key? == false)
519
+ end
520
+ result << delim
521
+ end
477
522
  result << state.indent * depth if indent
478
523
 
524
+ if state.strict? && !Generator.native_key?(key)
525
+ if state.as_json
526
+ key = state.as_json.call(key, true)
527
+ end
528
+
529
+ unless Generator.native_key?(key)
530
+ raise GeneratorError.new("#{key.class} not allowed as object key in JSON", value)
531
+ end
532
+ end
533
+
479
534
  key_str = key.to_s
480
535
  if key_str.is_a?(String)
481
536
  key_json = key_str.to_json(state)
@@ -484,10 +539,10 @@ module JSON
484
539
  end
485
540
 
486
541
  result = +"#{result}#{key_json}#{state.space_before}:#{state.space}"
487
- if state.strict? && !(false == value || true == value || nil == value || String === value || Array === value || Hash === value || Integer === value || Float === value || Fragment === value)
542
+ if state.strict? && !Generator.native_type?(value)
488
543
  if state.as_json
489
- value = state.as_json.call(value)
490
- unless false == value || true == value || nil == value || String === value || Array === value || Hash === value || Integer === value || Float === value || Fragment === value
544
+ value = state.as_json.call(value, false)
545
+ unless Generator.native_type?(value)
491
546
  raise GeneratorError.new("#{value.class} returned by #{state.as_json} not allowed in JSON", value)
492
547
  end
493
548
  result << value.to_json(state)
@@ -545,10 +600,10 @@ module JSON
545
600
  each { |value|
546
601
  result << delim unless first
547
602
  result << state.indent * depth if indent
548
- if state.strict? && !(false == value || true == value || nil == value || String === value || Array === value || Hash === value || Integer === value || Float === value || Fragment === value || Symbol == value)
603
+ if state.strict? && !Generator.native_type?(value)
549
604
  if state.as_json
550
- value = state.as_json.call(value)
551
- unless false == value || true == value || nil == value || String === value || Array === value || Hash === value || Integer === value || Float === value || Fragment === value || Symbol === value
605
+ value = state.as_json.call(value, false)
606
+ unless Generator.native_type?(value)
552
607
  raise GeneratorError.new("#{value.class} returned by #{state.as_json} not allowed in JSON", value)
553
608
  end
554
609
  result << value.to_json(state)
@@ -582,7 +637,7 @@ module JSON
582
637
  if state.allow_nan?
583
638
  to_s
584
639
  elsif state.strict? && state.as_json
585
- casted_value = state.as_json.call(self)
640
+ casted_value = state.as_json.call(self, false)
586
641
 
587
642
  if casted_value.equal?(self)
588
643
  raise GeneratorError.new("#{self} not allowed in JSON", self)
@@ -635,39 +690,6 @@ module JSON
635
690
  rescue Encoding::UndefinedConversionError => error
636
691
  raise ::JSON::GeneratorError.new(error.message, self)
637
692
  end
638
-
639
- # Module that holds the extending methods if, the String module is
640
- # included.
641
- module Extend
642
- # Raw Strings are JSON Objects (the raw bytes are stored in an
643
- # array for the key "raw"). The Ruby String can be created by this
644
- # module method.
645
- def json_create(o)
646
- o['raw'].pack('C*')
647
- end
648
- end
649
-
650
- # Extends _modul_ with the String::Extend module.
651
- def self.included(modul)
652
- modul.extend Extend
653
- end
654
-
655
- # This method creates a raw object hash, that can be nested into
656
- # other data structures and will be unparsed as a raw string. This
657
- # method should be used, if you want to convert raw strings to JSON
658
- # instead of UTF-8 strings, e. g. binary data.
659
- def to_json_raw_object
660
- {
661
- JSON.create_id => self.class.name,
662
- 'raw' => self.unpack('C*'),
663
- }
664
- end
665
-
666
- # This method creates a JSON text from the result of
667
- # a call to to_json_raw_object of this String.
668
- def to_json_raw(*args)
669
- to_json_raw_object.to_json(*args)
670
- end
671
693
  end
672
694
 
673
695
  module TrueClass
data/lib/json/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module JSON
4
- VERSION = '2.13.2'
4
+ VERSION = '2.15.2'
5
5
  end
data/lib/json.rb CHANGED
@@ -133,7 +133,7 @@ require 'json/common'
133
133
  # When not specified:
134
134
  # # The last value is used and a deprecation warning emitted.
135
135
  # JSON.parse('{"a": 1, "a":2}') => {"a" => 2}
136
- # # waring: detected duplicate keys in JSON object.
136
+ # # warning: detected duplicate keys in JSON object.
137
137
  # # This will raise an error in json 3.0 unless enabled via `allow_duplicate_key: true`
138
138
  #
139
139
  # When set to `+true+`
@@ -307,6 +307,25 @@ require 'json/common'
307
307
  #
308
308
  # ---
309
309
  #
310
+ # Option +allow_duplicate_key+ (boolean) specifies whether
311
+ # hashes with duplicate keys should be allowed or produce an error.
312
+ # defaults to emit a deprecation warning.
313
+ #
314
+ # With the default, (not set):
315
+ # Warning[:deprecated] = true
316
+ # JSON.generate({ foo: 1, "foo" => 2 })
317
+ # # warning: detected duplicate key "foo" in {foo: 1, "foo" => 2}.
318
+ # # This will raise an error in json 3.0 unless enabled via `allow_duplicate_key: true`
319
+ # # => '{"foo":1,"foo":2}'
320
+ #
321
+ # With <tt>false</tt>
322
+ # JSON.generate({ foo: 1, "foo" => 2 }, allow_duplicate_key: false)
323
+ # # detected duplicate key "foo" in {foo: 1, "foo" => 2} (JSON::GeneratorError)
324
+ #
325
+ # In version 3.0, <tt>false</tt> will become the default.
326
+ #
327
+ # ---
328
+ #
310
329
  # Option +max_nesting+ (\Integer) specifies the maximum nesting depth
311
330
  # in +obj+; defaults to +100+.
312
331
  #
@@ -384,6 +403,9 @@ require 'json/common'
384
403
  #
385
404
  # == \JSON Additions
386
405
  #
406
+ # Note that JSON Additions must only be used with trusted data, and is
407
+ # deprecated.
408
+ #
387
409
  # When you "round trip" a non-\String object from Ruby to \JSON and back,
388
410
  # you have a new \String, instead of the object you began with:
389
411
  # ruby0 = Range.new(0, 2)
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: json
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.13.2
4
+ version: 2.15.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Florian Frank
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-07-28 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies: []
12
12
  description: This is a JSON implementation as a Ruby extension in C.
13
13
  email: flori@ping.de
@@ -45,6 +45,7 @@ files:
45
45
  - lib/json/add/rational.rb
46
46
  - lib/json/add/regexp.rb
47
47
  - lib/json/add/set.rb
48
+ - lib/json/add/string.rb
48
49
  - lib/json/add/struct.rb
49
50
  - lib/json/add/symbol.rb
50
51
  - lib/json/add/time.rb
@@ -81,7 +82,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
81
82
  - !ruby/object:Gem::Version
82
83
  version: '0'
83
84
  requirements: []
84
- rubygems_version: 3.6.2
85
+ rubygems_version: 3.6.9
85
86
  specification_version: 4
86
87
  summary: JSON Implementation for Ruby
87
88
  test_files: []