json 2.14.1 → 2.15.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: fcefafe300fa1ffea1f43b8485f01e6203074c961073cd58908043f7cfb8764f
4
- data.tar.gz: fd1125be522c34646e5e9e790812ab476c6ce3b0535771272756720d2c012188
3
+ metadata.gz: ff78e67c786dd7f165bdd12e945ad6cb9de9c15c491b7423a6a7b9cc176c4c56
4
+ data.tar.gz: '068efe1a1652768b10fb4da5c0fe8e0d4296147efcf0a75a02ca667e66bb60f7'
5
5
  SHA512:
6
- metadata.gz: c539e410ba97460ee935d56611c6005d70979e9ef87c2af06cd99221ad14e6391851c869a3ffec9f2d08c9a0898ce47589b30f6bfd484d2d9e5183a03bf5dead
7
- data.tar.gz: 025fb31b8156e809f1bd145f8a8c1ed740fc70ab5a5623f4588df8618e26b243b06e85e7c52756dcbc03c17e8c0ec41ad7ef1ee53761423a570d7cfeb680a26b
6
+ metadata.gz: f326bc37bf1a00e3c379bbdf66591b3cd37a1b768f7432fcafe771eeede97b50a45151012d1e946c0c72038c7dc34cd0a935a685a9211a64699de235334d0a1a
7
+ data.tar.gz: ea2a1bfb35384feb19f9c16764490bbd57a772bbf2bfe44c206a3bc6f45e86d452cecb30830900e0f0e12e9ece5b9fa18aba25b2bee221b4982f805ec0df32a0
data/CHANGES.md CHANGED
@@ -2,6 +2,11 @@
2
2
 
3
3
  ### Unreleased
4
4
 
5
+ ### 2025-09-22 (2.15.0)
6
+
7
+ * `JSON::Coder` callback now receive a second argument to convey whether the object is a hash key.
8
+ * Tuned the floating point number generator to not use scientific notation as agressively.
9
+
5
10
  ### 2025-09-18 (2.14.1)
6
11
 
7
12
  * Fix `IndexOutOfBoundsException` in the JRuby extension when encoding shared strings.
@@ -23,6 +28,7 @@
23
28
  * Fix `JSON::Coder` to also invoke block for hash keys that aren't strings nor symbols.
24
29
  * Fix `JSON.unsafe_load` usage with proc
25
30
  * Fix the parser to more consistently reject invalid UTF-16 surogate pairs.
31
+ * Stop defining `String.json_create`, `String#to_json_raw`, `String#to_json_raw_object` when `json/add` isn't loaded.
26
32
 
27
33
  ### 2025-07-28 (2.13.2)
28
34
 
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`:
@@ -29,6 +29,7 @@ typedef struct JSON_Generator_StateStruct {
29
29
 
30
30
  enum duplicate_key_action on_duplicate_key;
31
31
 
32
+ bool as_json_single_arg;
32
33
  bool allow_nan;
33
34
  bool ascii_only;
34
35
  bool script_safe;
@@ -1033,6 +1034,13 @@ json_inspect_hash_with_mixed_keys(struct hash_foreach_arg *arg)
1033
1034
  }
1034
1035
  }
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
+
1036
1044
  static int
1037
1045
  json_object_i(VALUE key, VALUE val, VALUE _arg)
1038
1046
  {
@@ -1086,7 +1094,7 @@ json_object_i(VALUE key, VALUE val, VALUE _arg)
1086
1094
  default:
1087
1095
  if (data->state->strict) {
1088
1096
  if (RTEST(data->state->as_json) && !as_json_called) {
1089
- key = rb_proc_call_with_block(data->state->as_json, 1, &key, Qnil);
1097
+ key = json_call_as_json(data->state, key, Qtrue);
1090
1098
  key_type = rb_type(key);
1091
1099
  as_json_called = true;
1092
1100
  goto start;
@@ -1328,7 +1336,7 @@ static void generate_json_float(FBuffer *buffer, struct generate_json_data *data
1328
1336
  /* for NaN and Infinity values we either raise an error or rely on Float#to_s. */
1329
1337
  if (!allow_nan) {
1330
1338
  if (data->state->strict && data->state->as_json) {
1331
- 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);
1332
1340
  if (casted_obj != obj) {
1333
1341
  increase_depth(data);
1334
1342
  generate_json(buffer, data, casted_obj);
@@ -1345,12 +1353,11 @@ static void generate_json_float(FBuffer *buffer, struct generate_json_data *data
1345
1353
  }
1346
1354
 
1347
1355
  /* This implementation writes directly into the buffer. We reserve
1348
- * the 28 characters that fpconv_dtoa states as its maximum.
1356
+ * the 32 characters that fpconv_dtoa states as its maximum.
1349
1357
  */
1350
- fbuffer_inc_capa(buffer, 28);
1358
+ fbuffer_inc_capa(buffer, 32);
1351
1359
  char* d = buffer->ptr + buffer->len;
1352
1360
  int len = fpconv_dtoa(value, d);
1353
-
1354
1361
  /* fpconv_dtoa converts a float to its shortest string representation,
1355
1362
  * but it adds a ".0" if this is a plain integer.
1356
1363
  */
@@ -1417,7 +1424,7 @@ start:
1417
1424
  general:
1418
1425
  if (data->state->strict) {
1419
1426
  if (RTEST(data->state->as_json) && !as_json_called) {
1420
- obj = rb_proc_call_with_block(data->state->as_json, 1, &obj, Qnil);
1427
+ obj = json_call_as_json(data->state, obj, Qfalse);
1421
1428
  as_json_called = true;
1422
1429
  goto start;
1423
1430
  } else {
@@ -1943,6 +1950,7 @@ static int configure_state_i(VALUE key, VALUE val, VALUE _arg)
1943
1950
  else if (key == sym_allow_duplicate_key) { state->on_duplicate_key = RTEST(val) ? JSON_IGNORE : JSON_RAISE; }
1944
1951
  else if (key == sym_as_json) {
1945
1952
  VALUE proc = RTEST(val) ? rb_convert_type(val, T_DATA, "Proc", "to_proc") : Qfalse;
1953
+ state->as_json_single_arg = proc && rb_proc_arity(proc) == 1;
1946
1954
  state_write_value(data, &state->as_json, proc);
1947
1955
  }
1948
1956
  return ST_CONTINUE;
@@ -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
  }
@@ -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:
@@ -448,10 +456,10 @@ module JSON
448
456
  state = State.from_state(state) if state
449
457
  if state&.strict?
450
458
  value = self
451
- if state.strict? && !(false == value || true == value || nil == value || String === value || Array === value || Hash === value || Integer === value || Float === value || Fragment === value)
459
+ if state.strict? && !Generator.native_type?(value)
452
460
  if state.as_json
453
- value = state.as_json.call(value)
454
- unless false == value || true == value || nil == value || String === value || Array === value || Hash === value || Integer === value || Float === value || Fragment === value
461
+ value = state.as_json.call(value, false)
462
+ unless Generator.native_type?(value)
455
463
  raise GeneratorError.new("#{value.class} returned by #{state.as_json} not allowed in JSON", value)
456
464
  end
457
465
  value.to_json(state)
@@ -509,12 +517,12 @@ module JSON
509
517
  end
510
518
  result << state.indent * depth if indent
511
519
 
512
- if state.strict? && !(Symbol === key || String === key)
520
+ if state.strict? && !Generator.native_key?(key)
513
521
  if state.as_json
514
- key = state.as_json.call(key)
522
+ key = state.as_json.call(key, true)
515
523
  end
516
524
 
517
- unless Symbol === key || String === key
525
+ unless Generator.native_key?(key)
518
526
  raise GeneratorError.new("#{key.class} not allowed as object key in JSON", value)
519
527
  end
520
528
  end
@@ -527,10 +535,10 @@ module JSON
527
535
  end
528
536
 
529
537
  result = +"#{result}#{key_json}#{state.space_before}:#{state.space}"
530
- if state.strict? && !(false == value || true == value || nil == value || String === value || Array === value || Hash === value || Integer === value || Float === value || Fragment === value)
538
+ if state.strict? && !Generator.native_type?(value)
531
539
  if state.as_json
532
- value = state.as_json.call(value)
533
- unless false == value || true == value || nil == value || String === value || Array === value || Hash === value || Integer === value || Float === value || Fragment === value
540
+ value = state.as_json.call(value, false)
541
+ unless Generator.native_type?(value)
534
542
  raise GeneratorError.new("#{value.class} returned by #{state.as_json} not allowed in JSON", value)
535
543
  end
536
544
  result << value.to_json(state)
@@ -588,10 +596,10 @@ module JSON
588
596
  each { |value|
589
597
  result << delim unless first
590
598
  result << state.indent * depth if indent
591
- if state.strict? && !(false == value || true == value || nil == value || String === value || Array === value || Hash === value || Integer === value || Float === value || Fragment === value || Symbol == value)
599
+ if state.strict? && !Generator.native_type?(value)
592
600
  if state.as_json
593
- value = state.as_json.call(value)
594
- unless false == value || true == value || nil == value || String === value || Array === value || Hash === value || Integer === value || Float === value || Fragment === value || Symbol === value
601
+ value = state.as_json.call(value, false)
602
+ unless Generator.native_type?(value)
595
603
  raise GeneratorError.new("#{value.class} returned by #{state.as_json} not allowed in JSON", value)
596
604
  end
597
605
  result << value.to_json(state)
@@ -625,7 +633,7 @@ module JSON
625
633
  if state.allow_nan?
626
634
  to_s
627
635
  elsif state.strict? && state.as_json
628
- casted_value = state.as_json.call(self)
636
+ casted_value = state.as_json.call(self, false)
629
637
 
630
638
  if casted_value.equal?(self)
631
639
  raise GeneratorError.new("#{self} not allowed in JSON", self)
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.14.1'
4
+ VERSION = '2.15.0'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: json
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.14.1
4
+ version: 2.15.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Florian Frank