json 2.13.1 → 2.14.0

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: 51746c89475207b2f61cd248e90092dfbc5ede1c57226e3809f972ba83155b03
4
- data.tar.gz: df39f6262a9eacd358a263b5ab00e0c30474115c9ef367132498f385eeb8fc03
3
+ metadata.gz: 3a69ef5582c620f7bfdf153a88827b4314d9d3dc01f2435a8c9ebe9d5821db91
4
+ data.tar.gz: 873e7ced8242e7cc715dbcd92c7d32970dbca037d0aa4ebf3bd37445461eb175
5
5
  SHA512:
6
- metadata.gz: f192e8bd1e2008570805de002e2ee1ff75f5f31929f5f3c719e7dc11a2cb754f97d70655308372275e4eb9c39d5ee16cb85f2fe7823d5a94f71879d87b11d705
7
- data.tar.gz: 69846485c78975cd38c91e5068f365bc8ee9b41ec761d964a8c2166e3e6e86e72d34634f26764a6532ec1e8a3537d669069774440742c2dec7003de09090041c
6
+ metadata.gz: 62aa3ce5d1660779ddcb14d4a0b082838de6f231358d5269c5055dd7bdbe114ad01aae6626e32c333c9c2085a17eef3841b4acf318ca4b1b269f54c2bf134163
7
+ data.tar.gz: a844976bd9734865ef55ee697f5fb1a87fcbfd86bc5c5931776c98af72a597c0ee8399089d2da64957dde012e5c2a328cd75fa186ebb09811ab3d8a5479143b2
data/CHANGES.md CHANGED
@@ -2,6 +2,26 @@
2
2
 
3
3
  ### Unreleased
4
4
 
5
+ * Add new `allow_duplicate_key` generator options. By default a warning is now emitted when a duplicated key is encountered.
6
+ In `json 3.0` an error will be raised.
7
+ ```ruby
8
+ >> Warning[:deprecated] = true
9
+ >> puts JSON.generate({ foo: 1, "foo" => 2 })
10
+ (irb):2: warning: detected duplicate key "foo" in {foo: 1, "foo" => 2}.
11
+ This will raise an error in json 3.0 unless enabled via `allow_duplicate_key: true`
12
+ {"foo":1,"foo":2}
13
+ >> JSON.generate({ foo: 1, "foo" => 2 }, allow_duplicate_key: false)
14
+ detected duplicate key "foo" in {foo: 1, "foo" => 2} (JSON::GeneratorError)
15
+ ```
16
+ * Fix `JSON.generate` `strict: true` mode to also restrict hash keys.
17
+ * Fix `JSON::Coder` to also invoke block for hash keys that aren't strings nor symbols.
18
+ * Fix `JSON.unsafe_load` usage with proc
19
+ * Fix the parser to more consistently reject invalid UTF-16 surogate pairs.
20
+
21
+ ### 2025-07-28 (2.13.2)
22
+
23
+ * Improve duplicate key warning and errors to include the key name and point to the right caller.
24
+
5
25
  ### 2025-07-24 (2.13.1)
6
26
 
7
27
  * Fix support for older compilers without `__builtin_cpu_supports`.
@@ -40,7 +60,7 @@
40
60
  ### 2025-04-24 (2.11.1)
41
61
 
42
62
  * Add back `JSON.restore`, `JSON.unparse`, `JSON.fast_unparse` and `JSON.pretty_unparse`.
43
- These were deprecated 16 years ago, but never emited warnings, only undocumented, so are
63
+ These were deprecated 16 years ago, but never emitted warnings, only undocumented, so are
44
64
  still used by a few gems.
45
65
 
46
66
  ### 2025-04-24 (2.11.0)
@@ -67,7 +87,7 @@
67
87
  ### 2025-03-12 (2.10.2)
68
88
 
69
89
  * Fix a potential crash in the C extension parser.
70
- * Raise a ParserError on all incomplete unicode escape sequence. This was the behavior until `2.10.0` unadvertently changed it.
90
+ * Raise a ParserError on all incomplete unicode escape sequence. This was the behavior until `2.10.0` inadvertently changed it.
71
91
  * Ensure document snippets that are included in parser errors don't include truncated multibyte characters.
72
92
  * Ensure parser error snippets are valid UTF-8.
73
93
  * Fix `JSON::GeneratorError#detailed_message` on Ruby < 3.2
@@ -98,7 +118,7 @@
98
118
 
99
119
  ### 2024-11-14 (2.8.2)
100
120
 
101
- * `JSON.load_file` explictly read the file as UTF-8.
121
+ * `JSON.load_file` explicitly read the file as UTF-8.
102
122
 
103
123
  ### 2024-11-06 (2.8.1)
104
124
 
@@ -106,7 +126,7 @@
106
126
 
107
127
  ### 2024-11-06 (2.8.0)
108
128
 
109
- * Emit a deprecation warning when `JSON.load` create custom types without the `create_additions` option being explictly enabled.
129
+ * Emit a deprecation warning when `JSON.load` create custom types without the `create_additions` option being explicitly enabled.
110
130
  * Prefer to use `JSON.unsafe_load(string)` or `JSON.load(string, create_additions: true)`.
111
131
  * Emit a deprecation warning when serializing valid UTF-8 strings encoded in `ASCII_8BIT` aka `BINARY`.
112
132
  * Bump required Ruby version to 2.7.
@@ -114,7 +134,7 @@
114
134
  pre-existing support for comments, make it suitable to parse `jsonc` documents.
115
135
  * Many performance improvements to `JSON.parse` and `JSON.load`, up to `1.7x` faster on real world documents.
116
136
  * Some minor performance improvements to `JSON.dump` and `JSON.generate`.
117
- * `JSON.pretty_generate` no longer include newline inside empty object and arrays.
137
+ * `JSON.pretty_generate` no longer includes newlines inside empty object and arrays.
118
138
 
119
139
  ### 2024-11-04 (2.7.6)
120
140
 
@@ -131,13 +151,13 @@
131
151
  * Workaround a bug in 3.4.8 and older https://github.com/rubygems/rubygems/pull/6490.
132
152
  This bug would cause some gems with native extension to fail during compilation.
133
153
  * Workaround different versions of `json` and `json_pure` being loaded (not officially supported).
134
- * Make `json_pure` Ractor compatible.
154
+ * Make `json_pure` Ractor compatible.
135
155
 
136
156
  ### 2024-10-24 (2.7.3)
137
157
 
138
158
  * Numerous performance optimizations in `JSON.generate` and `JSON.dump` (up to 2 times faster).
139
- * Limit the size of ParserError exception messages, only include up to 32 bytes of the unparseable source.
140
- * Fix json-pure's `Object#to_json` to accept non state arguments
159
+ * Limit the size of ParserError exception messages, only include up to 32 bytes of the unparsable source.
160
+ * Fix json-pure's `Object#to_json` to accept non-state arguments.
141
161
  * Fix multiline comment support in `json-pure`.
142
162
  * Fix `JSON.parse` to no longer mutate the argument encoding when passed an ASCII-8BIT string.
143
163
  * Fix `String#to_json` to raise on invalid encoding in `json-pure`.
@@ -282,6 +302,7 @@
282
302
  ## 2015-09-11 (2.0.0)
283
303
  * Now complies to newest JSON RFC 7159.
284
304
  * Implements compatibility to ruby 2.4 integer unification.
305
+ * Removed support for `quirks_mode` option.
285
306
  * Drops support for old rubies whose life has ended, that is rubies < 2.0.
286
307
  Also see https://www.ruby-lang.org/en/news/2014/07/01/eol-for-1-8-7-and-1-9-2/
287
308
  * There were still some mentions of dual GPL licensing in the source, but JSON
@@ -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,8 @@ 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
+
24
32
  bool allow_nan;
25
33
  bool ascii_only;
26
34
  bool script_safe;
@@ -31,10 +39,10 @@ typedef struct JSON_Generator_StateStruct {
31
39
  #define RB_UNLIKELY(cond) (cond)
32
40
  #endif
33
41
 
34
- static VALUE mJSON, cState, cFragment, mString_Extend, eGeneratorError, eNestingError, Encoding_UTF_8;
42
+ static VALUE mJSON, cState, cFragment, eGeneratorError, eNestingError, Encoding_UTF_8;
35
43
 
36
44
  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,
45
+ 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
46
  sym_ascii_only, sym_depth, sym_buffer_initial_length, sym_script_safe, sym_escape_slash, sym_strict, sym_as_json;
39
47
 
40
48
 
@@ -137,8 +145,8 @@ static inline FORCE_INLINE void search_flush(search_state *search)
137
145
  {
138
146
  // Do not remove this conditional without profiling, specifically escape-heavy text.
139
147
  // 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
148
+ // For back-to-back characters that need to be escaped, specifically for the SIMD code paths, this method
149
+ // will be called just before calling escape_UTF8_char_basic. There will be no characters to append for the
142
150
  // consecutive characters that need to be escaped. While the fbuffer_append is a no-op if
143
151
  // nothing needs to be flushed, we can save a few memory references with this conditional.
144
152
  if (search->ptr > search->cursor) {
@@ -835,18 +843,6 @@ static VALUE mFloat_to_json(int argc, VALUE *argv, VALUE self)
835
843
  return cState_partial_generate(Vstate, self, generate_json_float, Qfalse);
836
844
  }
837
845
 
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
846
  /*
851
847
  * call-seq: to_json(*)
852
848
  *
@@ -861,51 +857,6 @@ static VALUE mString_to_json(int argc, VALUE *argv, VALUE self)
861
857
  return cState_partial_generate(Vstate, self, generate_json_string, Qfalse);
862
858
  }
863
859
 
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
860
  /*
910
861
  * call-seq: to_json(*)
911
862
  *
@@ -1044,8 +995,11 @@ static inline VALUE vstate_get(struct generate_json_data *data)
1044
995
  }
1045
996
 
1046
997
  struct hash_foreach_arg {
998
+ VALUE hash;
1047
999
  struct generate_json_data *data;
1048
- int iter;
1000
+ int first_key_type;
1001
+ bool first;
1002
+ bool mixed_keys_encountered;
1049
1003
  };
1050
1004
 
1051
1005
  static VALUE
@@ -1063,6 +1017,22 @@ convert_string_subclass(VALUE key)
1063
1017
  return key_to_s;
1064
1018
  }
1065
1019
 
1020
+ NOINLINE()
1021
+ static void
1022
+ json_inspect_hash_with_mixed_keys(struct hash_foreach_arg *arg)
1023
+ {
1024
+ if (arg->mixed_keys_encountered) {
1025
+ return;
1026
+ }
1027
+ arg->mixed_keys_encountered = true;
1028
+
1029
+ JSON_Generator_State *state = arg->data->state;
1030
+ if (state->on_duplicate_key != JSON_IGNORE) {
1031
+ VALUE do_raise = state->on_duplicate_key == JSON_RAISE ? Qtrue : Qfalse;
1032
+ rb_funcall(mJSON, rb_intern("on_mixed_keys_hash"), 2, arg->hash, do_raise);
1033
+ }
1034
+ }
1035
+
1066
1036
  static int
1067
1037
  json_object_i(VALUE key, VALUE val, VALUE _arg)
1068
1038
  {
@@ -1073,21 +1043,33 @@ json_object_i(VALUE key, VALUE val, VALUE _arg)
1073
1043
  JSON_Generator_State *state = data->state;
1074
1044
 
1075
1045
  long depth = state->depth;
1076
- int j;
1046
+ int key_type = rb_type(key);
1047
+
1048
+ if (arg->first) {
1049
+ arg->first = false;
1050
+ arg->first_key_type = key_type;
1051
+ }
1052
+ else {
1053
+ fbuffer_append_char(buffer, ',');
1054
+ }
1077
1055
 
1078
- if (arg->iter > 0) fbuffer_append_char(buffer, ',');
1079
1056
  if (RB_UNLIKELY(data->state->object_nl)) {
1080
1057
  fbuffer_append_str(buffer, data->state->object_nl);
1081
1058
  }
1082
1059
  if (RB_UNLIKELY(data->state->indent)) {
1083
- for (j = 0; j < depth; j++) {
1084
- fbuffer_append_str(buffer, data->state->indent);
1085
- }
1060
+ fbuffer_append_str_repeat(buffer, data->state->indent, depth);
1086
1061
  }
1087
1062
 
1088
1063
  VALUE key_to_s;
1089
- switch (rb_type(key)) {
1064
+ bool as_json_called = false;
1065
+
1066
+ start:
1067
+ switch (key_type) {
1090
1068
  case T_STRING:
1069
+ if (RB_UNLIKELY(arg->first_key_type != T_STRING)) {
1070
+ json_inspect_hash_with_mixed_keys(arg);
1071
+ }
1072
+
1091
1073
  if (RB_LIKELY(RBASIC_CLASS(key) == rb_cString)) {
1092
1074
  key_to_s = key;
1093
1075
  } else {
@@ -1095,9 +1077,23 @@ json_object_i(VALUE key, VALUE val, VALUE _arg)
1095
1077
  }
1096
1078
  break;
1097
1079
  case T_SYMBOL:
1080
+ if (RB_UNLIKELY(arg->first_key_type != T_SYMBOL)) {
1081
+ json_inspect_hash_with_mixed_keys(arg);
1082
+ }
1083
+
1098
1084
  key_to_s = rb_sym2str(key);
1099
1085
  break;
1100
1086
  default:
1087
+ if (data->state->strict) {
1088
+ if (RTEST(data->state->as_json) && !as_json_called) {
1089
+ key = rb_proc_call_with_block(data->state->as_json, 1, &key, Qnil);
1090
+ key_type = rb_type(key);
1091
+ as_json_called = true;
1092
+ goto start;
1093
+ } else {
1094
+ raise_generator_error(key, "%"PRIsVALUE" not allowed as object key in JSON", CLASS_OF(key));
1095
+ }
1096
+ }
1101
1097
  key_to_s = rb_convert_type(key, T_STRING, "String", "to_s");
1102
1098
  break;
1103
1099
  }
@@ -1112,7 +1108,6 @@ json_object_i(VALUE key, VALUE val, VALUE _arg)
1112
1108
  if (RB_UNLIKELY(state->space)) fbuffer_append_str(buffer, data->state->space);
1113
1109
  generate_json(buffer, data, val);
1114
1110
 
1115
- arg->iter++;
1116
1111
  return ST_CONTINUE;
1117
1112
  }
1118
1113
 
@@ -1128,7 +1123,6 @@ static inline long increase_depth(struct generate_json_data *data)
1128
1123
 
1129
1124
  static void generate_json_object(FBuffer *buffer, struct generate_json_data *data, VALUE obj)
1130
1125
  {
1131
- int j;
1132
1126
  long depth = increase_depth(data);
1133
1127
 
1134
1128
  if (RHASH_SIZE(obj) == 0) {
@@ -1140,8 +1134,9 @@ static void generate_json_object(FBuffer *buffer, struct generate_json_data *dat
1140
1134
  fbuffer_append_char(buffer, '{');
1141
1135
 
1142
1136
  struct hash_foreach_arg arg = {
1137
+ .hash = obj,
1143
1138
  .data = data,
1144
- .iter = 0,
1139
+ .first = true,
1145
1140
  };
1146
1141
  rb_hash_foreach(obj, json_object_i, (VALUE)&arg);
1147
1142
 
@@ -1149,9 +1144,7 @@ static void generate_json_object(FBuffer *buffer, struct generate_json_data *dat
1149
1144
  if (RB_UNLIKELY(data->state->object_nl)) {
1150
1145
  fbuffer_append_str(buffer, data->state->object_nl);
1151
1146
  if (RB_UNLIKELY(data->state->indent)) {
1152
- for (j = 0; j < depth; j++) {
1153
- fbuffer_append_str(buffer, data->state->indent);
1154
- }
1147
+ fbuffer_append_str_repeat(buffer, data->state->indent, depth);
1155
1148
  }
1156
1149
  }
1157
1150
  fbuffer_append_char(buffer, '}');
@@ -1159,7 +1152,6 @@ static void generate_json_object(FBuffer *buffer, struct generate_json_data *dat
1159
1152
 
1160
1153
  static void generate_json_array(FBuffer *buffer, struct generate_json_data *data, VALUE obj)
1161
1154
  {
1162
- int i, j;
1163
1155
  long depth = increase_depth(data);
1164
1156
 
1165
1157
  if (RARRAY_LEN(obj) == 0) {
@@ -1170,15 +1162,13 @@ static void generate_json_array(FBuffer *buffer, struct generate_json_data *data
1170
1162
 
1171
1163
  fbuffer_append_char(buffer, '[');
1172
1164
  if (RB_UNLIKELY(data->state->array_nl)) fbuffer_append_str(buffer, data->state->array_nl);
1173
- for (i = 0; i < RARRAY_LEN(obj); i++) {
1165
+ for (int i = 0; i < RARRAY_LEN(obj); i++) {
1174
1166
  if (i > 0) {
1175
1167
  fbuffer_append_char(buffer, ',');
1176
1168
  if (RB_UNLIKELY(data->state->array_nl)) fbuffer_append_str(buffer, data->state->array_nl);
1177
1169
  }
1178
1170
  if (RB_UNLIKELY(data->state->indent)) {
1179
- for (j = 0; j < depth; j++) {
1180
- fbuffer_append_str(buffer, data->state->indent);
1181
- }
1171
+ fbuffer_append_str_repeat(buffer, data->state->indent, depth);
1182
1172
  }
1183
1173
  generate_json(buffer, data, RARRAY_AREF(obj, i));
1184
1174
  }
@@ -1186,9 +1176,7 @@ static void generate_json_array(FBuffer *buffer, struct generate_json_data *data
1186
1176
  if (RB_UNLIKELY(data->state->array_nl)) {
1187
1177
  fbuffer_append_str(buffer, data->state->array_nl);
1188
1178
  if (RB_UNLIKELY(data->state->indent)) {
1189
- for (j = 0; j < depth; j++) {
1190
- fbuffer_append_str(buffer, data->state->indent);
1191
- }
1179
+ fbuffer_append_str_repeat(buffer, data->state->indent, depth);
1192
1180
  }
1193
1181
  }
1194
1182
  fbuffer_append_char(buffer, ']');
@@ -1850,6 +1838,19 @@ static VALUE cState_ascii_only_set(VALUE self, VALUE enable)
1850
1838
  return Qnil;
1851
1839
  }
1852
1840
 
1841
+ static VALUE cState_allow_duplicate_key_p(VALUE self)
1842
+ {
1843
+ GET_STATE(self);
1844
+ switch (state->on_duplicate_key) {
1845
+ case JSON_IGNORE:
1846
+ return Qtrue;
1847
+ case JSON_DEPRECATED:
1848
+ return Qnil;
1849
+ default:
1850
+ return Qfalse;
1851
+ }
1852
+ }
1853
+
1853
1854
  /*
1854
1855
  * call-seq: depth
1855
1856
  *
@@ -1939,6 +1940,7 @@ static int configure_state_i(VALUE key, VALUE val, VALUE _arg)
1939
1940
  else if (key == sym_script_safe) { state->script_safe = RTEST(val); }
1940
1941
  else if (key == sym_escape_slash) { state->script_safe = RTEST(val); }
1941
1942
  else if (key == sym_strict) { state->strict = RTEST(val); }
1943
+ else if (key == sym_allow_duplicate_key) { state->on_duplicate_key = RTEST(val) ? JSON_IGNORE : JSON_RAISE; }
1942
1944
  else if (key == sym_as_json) {
1943
1945
  VALUE proc = RTEST(val) ? rb_convert_type(val, T_DATA, "Proc", "to_proc") : Qfalse;
1944
1946
  state_write_value(data, &state->as_json, proc);
@@ -2064,6 +2066,8 @@ void Init_generator(void)
2064
2066
  rb_define_method(cState, "generate", cState_generate, -1);
2065
2067
  rb_define_alias(cState, "generate_new", "generate"); // :nodoc:
2066
2068
 
2069
+ rb_define_private_method(cState, "allow_duplicate_key?", cState_allow_duplicate_key_p, 0);
2070
+
2067
2071
  rb_define_singleton_method(cState, "generate", cState_m_generate, 3);
2068
2072
 
2069
2073
  VALUE mGeneratorMethods = rb_define_module_under(mGenerator, "GeneratorMethods");
@@ -2091,13 +2095,7 @@ void Init_generator(void)
2091
2095
  rb_define_method(mFloat, "to_json", mFloat_to_json, -1);
2092
2096
 
2093
2097
  VALUE mString = rb_define_module_under(mGeneratorMethods, "String");
2094
- rb_define_singleton_method(mString, "included", mString_included_s, 1);
2095
2098
  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
2099
 
2102
2100
  VALUE mTrueClass = rb_define_module_under(mGeneratorMethods, "TrueClass");
2103
2101
  rb_define_method(mTrueClass, "to_json", mTrueClass_to_json, -1);
@@ -2134,6 +2132,7 @@ void Init_generator(void)
2134
2132
  sym_escape_slash = ID2SYM(rb_intern("escape_slash"));
2135
2133
  sym_strict = ID2SYM(rb_intern("strict"));
2136
2134
  sym_as_json = ID2SYM(rb_intern("as_json"));
2135
+ sym_allow_duplicate_key = ID2SYM(rb_intern("allow_duplicate_key"));
2137
2136
 
2138
2137
  usascii_encindex = rb_usascii_encindex();
2139
2138
  utf8_encindex = rb_utf8_encindex();
@@ -422,10 +422,12 @@ static void emit_parse_warning(const char *message, JSON_ParserState *state)
422
422
  long line, column;
423
423
  cursor_position(state, &line, &column);
424
424
 
425
- rb_warn("%s at line %ld column %ld", message, line, column);
425
+ VALUE warning = rb_sprintf("%s at line %ld column %ld", message, line, column);
426
+ rb_funcall(mJSON, rb_intern("deprecation_warning"), 1, warning);
426
427
  }
427
428
 
428
429
  #define PARSE_ERROR_FRAGMENT_LEN 32
430
+
429
431
  #ifdef RBIMPL_ATTR_NORETURN
430
432
  RBIMPL_ATTR_NORETURN()
431
433
  #endif
@@ -711,11 +713,16 @@ static VALUE json_string_unescape(JSON_ParserState *state, const char *string, c
711
713
  }
712
714
  if (pe[0] == '\\' && pe[1] == 'u') {
713
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
+
714
721
  ch = (((ch & 0x3F) << 10) | ((((ch >> 6) & 0xF) + 1) << 16)
715
722
  | (sur & 0x3FF));
716
723
  pe += 5;
717
724
  } else {
718
- unescape = (char *) "?";
725
+ raise_parse_error_at("incomplete surrogate pair at %s", state, p);
719
726
  break;
720
727
  }
721
728
  }
@@ -830,21 +837,64 @@ static inline VALUE json_decode_array(JSON_ParserState *state, JSON_ParserConfig
830
837
  return array;
831
838
  }
832
839
 
840
+ static VALUE json_find_duplicated_key(size_t count, const VALUE *pairs)
841
+ {
842
+ VALUE set = rb_hash_new_capa(count / 2);
843
+ for (size_t index = 0; index < count; index += 2) {
844
+ size_t before = RHASH_SIZE(set);
845
+ VALUE key = pairs[index];
846
+ rb_hash_aset(set, key, Qtrue);
847
+ if (RHASH_SIZE(set) == before) {
848
+ if (RB_SYMBOL_P(key)) {
849
+ return rb_sym2str(key);
850
+ }
851
+ return key;
852
+ }
853
+ }
854
+ return Qfalse;
855
+ }
856
+
857
+ static void emit_duplicate_key_warning(JSON_ParserState *state, VALUE duplicate_key)
858
+ {
859
+ VALUE message = rb_sprintf(
860
+ "detected duplicate key %"PRIsVALUE" in JSON object. This will raise an error in json 3.0 unless enabled via `allow_duplicate_key: true`",
861
+ rb_inspect(duplicate_key)
862
+ );
863
+
864
+ emit_parse_warning(RSTRING_PTR(message), state);
865
+ RB_GC_GUARD(message);
866
+ }
867
+
868
+ #ifdef RBIMPL_ATTR_NORETURN
869
+ RBIMPL_ATTR_NORETURN()
870
+ #endif
871
+ static void raise_duplicate_key_error(JSON_ParserState *state, VALUE duplicate_key)
872
+ {
873
+ VALUE message = rb_sprintf(
874
+ "duplicate key %"PRIsVALUE,
875
+ rb_inspect(duplicate_key)
876
+ );
877
+
878
+ raise_parse_error(RSTRING_PTR(message), state);
879
+ RB_GC_GUARD(message);
880
+ }
881
+
833
882
  static inline VALUE json_decode_object(JSON_ParserState *state, JSON_ParserConfig *config, size_t count)
834
883
  {
835
884
  size_t entries_count = count / 2;
836
885
  VALUE object = rb_hash_new_capa(entries_count);
837
- rb_hash_bulk_insert(count, rvalue_stack_peek(state->stack, count), object);
886
+ const VALUE *pairs = rvalue_stack_peek(state->stack, count);
887
+ rb_hash_bulk_insert(count, pairs, object);
838
888
 
839
889
  if (RB_UNLIKELY(RHASH_SIZE(object) < entries_count)) {
840
890
  switch (config->on_duplicate_key) {
841
891
  case JSON_IGNORE:
842
892
  break;
843
893
  case JSON_DEPRECATED:
844
- emit_parse_warning("detected duplicate keys in JSON object. This will raise an error in json 3.0 unless enabled via `allow_duplicate_key: true`", state);
894
+ emit_duplicate_key_warning(state, json_find_duplicated_key(count, pairs));
845
895
  break;
846
896
  case JSON_RAISE:
847
- raise_parse_error("duplicate key", state);
897
+ raise_duplicate_key_error(state, json_find_duplicated_key(count, pairs));
848
898
  break;
849
899
  }
850
900
  }
@@ -930,7 +980,7 @@ static inline bool FORCE_INLINE string_scan(JSON_ParserState *state)
930
980
  if (RB_UNLIKELY(string_scan_table[(unsigned char)*state->cursor])) {
931
981
  return 1;
932
982
  }
933
- *state->cursor++;
983
+ state->cursor++;
934
984
  }
935
985
  return 0;
936
986
  }
@@ -1220,7 +1270,7 @@ static VALUE json_parse_any(JSON_ParserState *state, JSON_ParserConfig *config)
1220
1270
  break;
1221
1271
  }
1222
1272
 
1223
- raise_parse_error("unreacheable: %s", state);
1273
+ raise_parse_error("unreachable: %s", state);
1224
1274
  }
1225
1275
 
1226
1276
  static void json_ensure_eof(JSON_ParserState *state)
@@ -1269,7 +1319,7 @@ static int parser_config_init_i(VALUE key, VALUE val, VALUE data)
1269
1319
  else if (key == sym_symbolize_names) { config->symbolize_names = RTEST(val); }
1270
1320
  else if (key == sym_freeze) { config->freeze = RTEST(val); }
1271
1321
  else if (key == sym_on_load) { config->on_load_proc = RTEST(val) ? val : Qfalse; }
1272
- 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; }
1273
1323
  else if (key == sym_decimal_class) {
1274
1324
  if (RTEST(val)) {
1275
1325
  if (rb_respond_to(val, i_try_convert)) {
@@ -7,45 +7,45 @@ typedef enum {
7
7
  #ifdef JSON_ENABLE_SIMD
8
8
 
9
9
  #ifdef __clang__
10
- #if __has_builtin(__builtin_ctzll)
11
- #define HAVE_BUILTIN_CTZLL 1
12
- #else
13
- #define HAVE_BUILTIN_CTZLL 0
14
- #endif
10
+ # if __has_builtin(__builtin_ctzll)
11
+ # define HAVE_BUILTIN_CTZLL 1
12
+ # else
13
+ # define HAVE_BUILTIN_CTZLL 0
14
+ # endif
15
15
  #elif defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 3))
16
- #define HAVE_BUILTIN_CTZLL 1
16
+ # define HAVE_BUILTIN_CTZLL 1
17
17
  #else
18
- #define HAVE_BUILTIN_CTZLL 0
18
+ # define HAVE_BUILTIN_CTZLL 0
19
19
  #endif
20
20
 
21
21
  static inline uint32_t trailing_zeros64(uint64_t input)
22
22
  {
23
23
  #if HAVE_BUILTIN_CTZLL
24
- return __builtin_ctzll(input);
24
+ return __builtin_ctzll(input);
25
25
  #else
26
- uint32_t trailing_zeros = 0;
27
- uint64_t temp = input;
28
- while ((temp & 1) == 0 && temp > 0) {
29
- trailing_zeros++;
30
- temp >>= 1;
31
- }
32
- return trailing_zeros;
26
+ uint32_t trailing_zeros = 0;
27
+ uint64_t temp = input;
28
+ while ((temp & 1) == 0 && temp > 0) {
29
+ trailing_zeros++;
30
+ temp >>= 1;
31
+ }
32
+ return trailing_zeros;
33
33
  #endif
34
34
  }
35
35
 
36
36
  static inline int trailing_zeros(int input)
37
37
  {
38
- #if HAVE_BUILTIN_CTZLL
38
+ #if HAVE_BUILTIN_CTZLL
39
39
  return __builtin_ctz(input);
40
- #else
40
+ #else
41
41
  int trailing_zeros = 0;
42
42
  int temp = input;
43
43
  while ((temp & 1) == 0 && temp > 0) {
44
- trailing_zeros++;
45
- temp >>= 1;
44
+ trailing_zeros++;
45
+ temp >>= 1;
46
46
  }
47
47
  return trailing_zeros;
48
- #endif
48
+ #endif
49
49
  }
50
50
 
51
51
  #if (defined(__GNUC__ ) || defined(__clang__))
@@ -79,37 +79,38 @@ static inline FORCE_INLINE uint64_t neon_match_mask(uint8x16_t matches)
79
79
 
80
80
  static inline FORCE_INLINE uint64_t compute_chunk_mask_neon(const char *ptr)
81
81
  {
82
- uint8x16_t chunk = vld1q_u8((const unsigned char *)ptr);
82
+ uint8x16_t chunk = vld1q_u8((const unsigned char *)ptr);
83
83
 
84
- // Trick: c < 32 || c == 34 can be factored as c ^ 2 < 33
85
- // https://lemire.me/blog/2025/04/13/detect-control-characters-quotes-and-backslashes-efficiently-using-swar/
86
- const uint8x16_t too_low_or_dbl_quote = vcltq_u8(veorq_u8(chunk, vdupq_n_u8(2)), vdupq_n_u8(33));
84
+ // Trick: c < 32 || c == 34 can be factored as c ^ 2 < 33
85
+ // https://lemire.me/blog/2025/04/13/detect-control-characters-quotes-and-backslashes-efficiently-using-swar/
86
+ const uint8x16_t too_low_or_dbl_quote = vcltq_u8(veorq_u8(chunk, vdupq_n_u8(2)), vdupq_n_u8(33));
87
87
 
88
- uint8x16_t has_backslash = vceqq_u8(chunk, vdupq_n_u8('\\'));
89
- uint8x16_t needs_escape = vorrq_u8(too_low_or_dbl_quote, has_backslash);
90
- return neon_match_mask(needs_escape);
88
+ uint8x16_t has_backslash = vceqq_u8(chunk, vdupq_n_u8('\\'));
89
+ uint8x16_t needs_escape = vorrq_u8(too_low_or_dbl_quote, has_backslash);
90
+ return neon_match_mask(needs_escape);
91
91
  }
92
92
 
93
93
  static inline FORCE_INLINE int string_scan_simd_neon(const char **ptr, const char *end, uint64_t *mask)
94
94
  {
95
95
  while (*ptr + sizeof(uint8x16_t) <= end) {
96
- uint64_t chunk_mask = compute_chunk_mask_neon(*ptr);
97
- if (chunk_mask) {
98
- *mask = chunk_mask;
99
- return 1;
100
- }
101
- *ptr += sizeof(uint8x16_t);
96
+ uint64_t chunk_mask = compute_chunk_mask_neon(*ptr);
97
+ if (chunk_mask) {
98
+ *mask = chunk_mask;
99
+ return 1;
100
+ }
101
+ *ptr += sizeof(uint8x16_t);
102
102
  }
103
103
  return 0;
104
104
  }
105
105
 
106
- uint8x16x4_t load_uint8x16_4(const unsigned char *table) {
107
- uint8x16x4_t tab;
108
- tab.val[0] = vld1q_u8(table);
109
- tab.val[1] = vld1q_u8(table+16);
110
- tab.val[2] = vld1q_u8(table+32);
111
- tab.val[3] = vld1q_u8(table+48);
112
- return tab;
106
+ static inline uint8x16x4_t load_uint8x16_4(const unsigned char *table)
107
+ {
108
+ uint8x16x4_t tab;
109
+ tab.val[0] = vld1q_u8(table);
110
+ tab.val[1] = vld1q_u8(table+16);
111
+ tab.val[2] = vld1q_u8(table+32);
112
+ tab.val[3] = vld1q_u8(table+48);
113
+ return tab;
113
114
  }
114
115
 
115
116
  #endif /* ARM Neon Support.*/
@@ -150,12 +151,12 @@ static inline TARGET_SSE2 FORCE_INLINE int compute_chunk_mask_sse2(const char *p
150
151
  static inline TARGET_SSE2 FORCE_INLINE int string_scan_simd_sse2(const char **ptr, const char *end, int *mask)
151
152
  {
152
153
  while (*ptr + sizeof(__m128i) <= end) {
153
- int chunk_mask = compute_chunk_mask_sse2(*ptr);
154
- if (chunk_mask) {
155
- *mask = chunk_mask;
156
- return 1;
157
- }
158
- *ptr += sizeof(__m128i);
154
+ int chunk_mask = compute_chunk_mask_sse2(*ptr);
155
+ if (chunk_mask) {
156
+ *mask = chunk_mask;
157
+ return 1;
158
+ }
159
+ *ptr += sizeof(__m128i);
159
160
  }
160
161
 
161
162
  return 0;
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
@@ -48,7 +48,7 @@ module JSON
48
48
  end
49
49
  end
50
50
 
51
- # TODO: exctract :create_additions support to another gem for version 3.0
51
+ # TODO: extract :create_additions support to another gem for version 3.0
52
52
  def create_additions_proc(opts)
53
53
  if opts[:symbolize_names]
54
54
  raise ArgumentError, "options :symbolize_names and :create_additions cannot be used in conjunction"
@@ -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
@@ -87,31 +87,32 @@ module JSON
87
87
  opts
88
88
  end
89
89
 
90
- GEM_ROOT = File.expand_path("../../../", __FILE__) + "/"
91
90
  def create_additions_warning
92
- message = "JSON.load implicit support for `create_additions: true` is deprecated " \
91
+ JSON.deprecation_warning "JSON.load implicit support for `create_additions: true` is deprecated " \
93
92
  "and will be removed in 3.0, use JSON.unsafe_load or explicitly " \
94
93
  "pass `create_additions: true`"
94
+ end
95
+ end
96
+ end
95
97
 
96
- uplevel = 4
97
- caller_locations(uplevel, 10).each do |frame|
98
- if frame.path.nil? || frame.path.start_with?(GEM_ROOT) || frame.path.end_with?("/truffle/cext_ruby.rb", ".c")
99
- uplevel += 1
100
- else
101
- break
102
- end
103
- end
104
-
105
- if RUBY_VERSION >= "3.0"
106
- warn(message, uplevel: uplevel - 1, category: :deprecated)
98
+ class << self
99
+ def deprecation_warning(message, uplevel = 3) # :nodoc:
100
+ gem_root = File.expand_path("..", __dir__) + "/"
101
+ caller_locations(uplevel, 10).each do |frame|
102
+ if frame.path.nil? || frame.path.start_with?(gem_root) || frame.path.end_with?("/truffle/cext_ruby.rb", ".c")
103
+ uplevel += 1
107
104
  else
108
- warn(message, uplevel: uplevel - 1)
105
+ break
109
106
  end
110
107
  end
108
+
109
+ if RUBY_VERSION >= "3.0"
110
+ warn(message, uplevel: uplevel, category: :deprecated)
111
+ else
112
+ warn(message, uplevel: uplevel)
113
+ end
111
114
  end
112
- end
113
115
 
114
- class << self
115
116
  # :call-seq:
116
117
  # JSON[object] -> new_array or new_string
117
118
  #
@@ -185,6 +186,25 @@ module JSON
185
186
 
186
187
  private
187
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
+
188
208
  def deprecated_singleton_attr_accessor(*attrs)
189
209
  args = RUBY_VERSION >= "3.0" ? ", category: :deprecated" : ""
190
210
  attrs.each do |attr|
@@ -390,7 +410,7 @@ module JSON
390
410
  #
391
411
  # Returns a \String containing the generated \JSON data.
392
412
  #
393
- # See also JSON.fast_generate, JSON.pretty_generate.
413
+ # See also JSON.pretty_generate.
394
414
  #
395
415
  # Argument +obj+ is the Ruby object to be converted to \JSON.
396
416
  #
@@ -642,6 +662,7 @@ module JSON
642
662
  # when Array
643
663
  # obj.map! {|v| deserialize_obj v }
644
664
  # end
665
+ # obj
645
666
  # })
646
667
  # pp ruby
647
668
  # Output:
@@ -683,9 +704,13 @@ module JSON
683
704
  if opts[:allow_blank] && (source.nil? || source.empty?)
684
705
  source = 'null'
685
706
  end
686
- result = parse(source, opts)
687
- recurse_proc(result, &proc) if proc
688
- result
707
+
708
+ if proc
709
+ opts = opts.dup
710
+ opts[:on_load] = proc.to_proc
711
+ end
712
+
713
+ parse(source, opts)
689
714
  end
690
715
 
691
716
  # :call-seq:
@@ -802,6 +827,7 @@ module JSON
802
827
  # when Array
803
828
  # obj.map! {|v| deserialize_obj v }
804
829
  # end
830
+ # obj
805
831
  # })
806
832
  # pp ruby
807
833
  # Output:
@@ -1001,7 +1027,7 @@ module JSON
1001
1027
  # See {Parsing Options}[#module-JSON-label-Parsing+Options], and {Generating Options}[#module-JSON-label-Generating+Options].
1002
1028
  #
1003
1029
  # For generation, the <tt>strict: true</tt> option is always set. When a Ruby object with no native \JSON counterpart is
1004
- # 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
1005
1031
  # \JSON counterpart:
1006
1032
  #
1007
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
@@ -271,6 +271,12 @@ module JSON
271
271
  false
272
272
  end
273
273
 
274
+ if opts.key?(:allow_duplicate_key)
275
+ @allow_duplicate_key = !!opts[:allow_duplicate_key]
276
+ else
277
+ @allow_duplicate_key = nil # nil is deprecation
278
+ end
279
+
274
280
  @strict = !!opts[:strict] if opts.key?(:strict)
275
281
 
276
282
  if !opts.key?(:max_nesting) # defaults to 100
@@ -284,6 +290,10 @@ module JSON
284
290
  end
285
291
  alias merge configure
286
292
 
293
+ def allow_duplicate_key? # :nodoc:
294
+ @allow_duplicate_key
295
+ end
296
+
287
297
  # Returns the configuration instance variables as a hash, that can be
288
298
  # passed to the configure method.
289
299
  def to_h
@@ -292,6 +302,11 @@ module JSON
292
302
  iv = iv.to_s[1..-1]
293
303
  result[iv.to_sym] = self[iv]
294
304
  end
305
+
306
+ if result[:allow_duplicate_key].nil?
307
+ result.delete(:allow_duplicate_key)
308
+ end
309
+
295
310
  result
296
311
  end
297
312
 
@@ -330,8 +345,17 @@ module JSON
330
345
  when Hash
331
346
  buf << '{'
332
347
  first = true
348
+ key_type = nil
333
349
  obj.each_pair do |k,v|
334
- buf << ',' unless first
350
+ if first
351
+ key_type = k.class
352
+ else
353
+ if key_type && !@allow_duplicate_key && key_type != k.class
354
+ key_type = nil # stop checking
355
+ JSON.send(:on_mixed_keys_hash, obj, !@allow_duplicate_key.nil?)
356
+ end
357
+ buf << ','
358
+ end
335
359
 
336
360
  key_str = k.to_s
337
361
  if key_str.class == String
@@ -471,11 +495,30 @@ module JSON
471
495
  delim = ",#{state.object_nl}"
472
496
  result = +"{#{state.object_nl}"
473
497
  first = true
498
+ key_type = nil
474
499
  indent = !state.object_nl.empty?
475
500
  each { |key, value|
476
- result << delim unless first
501
+ if first
502
+ key_type = key.class
503
+ else
504
+ if key_type && !state.allow_duplicate_key? && key_type != key.class
505
+ key_type = nil # stop checking
506
+ JSON.send(:on_mixed_keys_hash, self, state.allow_duplicate_key? == false)
507
+ end
508
+ result << delim
509
+ end
477
510
  result << state.indent * depth if indent
478
511
 
512
+ if state.strict? && !(Symbol === key || String === key)
513
+ if state.as_json
514
+ key = state.as_json.call(key)
515
+ end
516
+
517
+ unless Symbol === key || String === key
518
+ raise GeneratorError.new("#{key.class} not allowed as object key in JSON", value)
519
+ end
520
+ end
521
+
479
522
  key_str = key.to_s
480
523
  if key_str.is_a?(String)
481
524
  key_json = key_str.to_json(state)
@@ -635,39 +678,6 @@ module JSON
635
678
  rescue Encoding::UndefinedConversionError => error
636
679
  raise ::JSON::GeneratorError.new(error.message, self)
637
680
  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
681
  end
672
682
 
673
683
  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.1'
4
+ VERSION = '2.14.0'
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.1
4
+ version: 2.14.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Florian Frank
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-07-24 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: []