json 3.0.1 → 3.1.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.
@@ -20,9 +20,9 @@ static int utf8_encindex;
20
20
  #ifndef HAVE_RB_HASH_BULK_INSERT
21
21
  // For TruffleRuby
22
22
  static void
23
- rb_hash_bulk_insert(long count, const VALUE *pairs, VALUE hash)
23
+ rb_hash_bulk_insert(rb_len_t count, const VALUE *pairs, VALUE hash)
24
24
  {
25
- long index = 0;
25
+ rb_len_t index = 0;
26
26
  while (index < count) {
27
27
  VALUE name = pairs[index++];
28
28
  VALUE value = pairs[index++];
@@ -79,7 +79,7 @@ static rb_encoding *enc_utf8;
79
79
 
80
80
  #define JSON_RVALUE_CACHE_MAX_ENTRY_LENGTH 55
81
81
 
82
- static inline VALUE build_interned_string(const char *str, const long length)
82
+ static inline VALUE build_interned_string(const char *str, const rb_len_t length)
83
83
  {
84
84
  # ifdef HAVE_RB_ENC_INTERNED_STR
85
85
  return rb_enc_interned_str(str, length, enc_utf8);
@@ -89,7 +89,7 @@ static inline VALUE build_interned_string(const char *str, const long length)
89
89
  # endif
90
90
  }
91
91
 
92
- static inline VALUE build_symbol(const char *str, const long length)
92
+ static inline VALUE build_symbol(const char *str, const rb_len_t length)
93
93
  {
94
94
  return rb_str_intern(build_interned_string(str, length));
95
95
  }
@@ -138,7 +138,7 @@ ALWAYS_INLINE(static) int rstring_cache_memcmp(const char *str, const char *rptr
138
138
  ALWAYS_INLINE(static) int rstring_cache_cmp(const char *str, const long length, VALUE rstring)
139
139
  {
140
140
  const char *rstring_ptr;
141
- long rstring_length;
141
+ rb_len_t rstring_length;
142
142
 
143
143
  RSTRING_GETMEM(rstring, rstring_ptr, rstring_length);
144
144
 
@@ -323,14 +323,17 @@ static const rb_data_type_t JSON_Parser_rvalue_stack_type = {
323
323
  static rvalue_stack *rvalue_stack_spill(rvalue_stack *old_stack, VALUE *handle, rvalue_stack **stack_ref)
324
324
  {
325
325
  rvalue_stack *stack;
326
- *handle = TypedData_Make_Struct(0, rvalue_stack, &JSON_Parser_rvalue_stack_type, stack);
327
- *stack_ref = stack;
326
+ VALUE stack_handle = TypedData_Make_Struct(0, rvalue_stack, &JSON_Parser_rvalue_stack_type, stack);
328
327
  MEMCPY(stack, old_stack, rvalue_stack, 1);
329
328
 
330
329
  stack->capa = old_stack->capa << 1;
331
330
  stack->ptr = ALLOC_N(VALUE, stack->capa);
332
331
  stack->type = RVALUE_STACK_HEAP_ALLOCATED;
333
332
  MEMCPY(stack->ptr, old_stack->ptr, VALUE, old_stack->head);
333
+
334
+ *handle = stack_handle;
335
+ *stack_ref = stack;
336
+
334
337
  return stack;
335
338
  }
336
339
 
@@ -580,7 +583,7 @@ static inline char peek(JSON_ParserState *state)
580
583
  return *state->cursor;
581
584
  }
582
585
 
583
- static void cursor_position(JSON_ParserState *state, long *line_out, long *column_out)
586
+ static void cursor_position(JSON_ParserState *state, long *line_out, rb_len_t *column_out)
584
587
  {
585
588
  JSON_ASSERT(!state->parser);
586
589
  JSON_ASSERT(state->cursor);
@@ -592,19 +595,15 @@ static void cursor_position(JSON_ParserState *state, long *line_out, long *colum
592
595
  }
593
596
 
594
597
  const char *cursor = state->cursor;
595
- long column = 0;
596
598
  long line = 1;
597
599
 
598
- while (cursor >= state->start) {
599
- if (*cursor-- == '\n') {
600
- line++;
601
- break;
602
- }
603
- column++;
600
+ while (cursor > state->start && cursor[-1] != '\n') {
601
+ cursor--;
604
602
  }
603
+ rb_len_t column = rb_enc_strlen(cursor, state->cursor, enc_utf8) + 1;
605
604
 
606
- while (cursor >= state->start) {
607
- if (*cursor-- == '\n') {
605
+ while (cursor > state->start) {
606
+ if (*--cursor == '\n') {
608
607
  line++;
609
608
  }
610
609
  }
@@ -631,17 +630,15 @@ static VALUE build_parse_error_message(const char *format, JSON_ParserState *sta
631
630
  }
632
631
 
633
632
  if (len) {
634
- buffer[0] = '\'';
635
- MEMCPY(buffer + 1, ptr, char, len);
636
-
637
- while (buffer[len] >= 0x80 && buffer[len] < 0xC0) { // Is continuation byte
638
- len--;
639
- }
640
-
641
- if (buffer[len] >= 0xC0) { // multibyte character start
642
- len--;
633
+ if (len == PARSE_ERROR_FRAGMENT_LEN) {
634
+ // Only trim when the byte limit splits a multibyte character.
635
+ while (len && (unsigned char)ptr[len] >= 0x80 && (unsigned char)ptr[len] < 0xC0) {
636
+ len--;
637
+ }
643
638
  }
644
639
 
640
+ buffer[0] = '\'';
641
+ MEMCPY(buffer + 1, ptr, char, len);
645
642
  buffer[len + 1] = '\'';
646
643
  buffer[len + 2] = '\0';
647
644
  ptr = (const char *)buffer;
@@ -681,11 +678,11 @@ static VALUE json_path_new(JSON_ParserState *state, VALUE duplicate_key)
681
678
  return path;
682
679
  }
683
680
 
684
- static VALUE parse_error_new(JSON_ParserState *state, VALUE message, long line, long column, bool eos)
681
+ static VALUE parse_error_new(JSON_ParserState *state, VALUE message, long line, rb_len_t column, bool eos)
685
682
  {
686
683
  VALUE exc = rb_exc_new_str(eParserError, message);
687
684
  rb_ivar_set(exc, i_at_line, LONG2NUM(line));
688
- rb_ivar_set(exc, i_at_column, LONG2NUM(column));
685
+ rb_ivar_set(exc, i_at_column, LEN2NUM(column));
689
686
  rb_ivar_set(exc, i_at_json_path, json_path_new(state, Qundef));
690
687
  return exc;
691
688
  }
@@ -703,9 +700,10 @@ NORETURN(static) void raise_parse_error(const char *format, JSON_ParserState *st
703
700
  }
704
701
  } else {
705
702
  VALUE message = build_parse_error_message(format, state);
706
- long line, column;
703
+ long line;
704
+ rb_len_t column;
707
705
  cursor_position(state, &line, &column);
708
- rb_str_catf(message, " at line %ld column %ld", line, column);
706
+ rb_str_catf(message, " at line %ld column %"PRIdLEN, line, column);
709
707
  rb_exc_raise(parse_error_new(state, message, line, column, eos));
710
708
  }
711
709
  }
@@ -758,7 +756,7 @@ static const signed char digit_values[256] = {
758
756
  static uint32_t unescape_unicode(JSON_ParserState *state, const char *sp, const char *spe)
759
757
  {
760
758
  if (RB_UNLIKELY(sp > spe - 4)) {
761
- raise_eos_error_at("incomplete unicode character escape sequence at %s", state, sp - 2);
759
+ raise_syntax_error_at("incomplete unicode character escape sequence at %s", state, sp - 2);
762
760
  }
763
761
 
764
762
  const unsigned char *p = (const unsigned char *)sp;
@@ -895,12 +893,12 @@ static inline VALUE build_string(const char *start, const char *end, bool intern
895
893
  VALUE result;
896
894
  # ifdef HAVE_RB_ENC_INTERNED_STR
897
895
  if (intern) {
898
- result = rb_enc_interned_str(start, (long)(end - start), enc_utf8);
896
+ result = rb_enc_interned_str(start, (rb_len_t)(end - start), enc_utf8);
899
897
  } else {
900
- result = rb_utf8_str_new(start, (long)(end - start));
898
+ result = rb_utf8_str_new(start, (rb_len_t)(end - start));
901
899
  }
902
900
  # else
903
- result = rb_utf8_str_new(start, (long)(end - start));
901
+ result = rb_utf8_str_new(start, (rb_len_t)(end - start));
904
902
  if (intern) {
905
903
  result = rb_funcall(rb_str_freeze(result), i_uminus, 0);
906
904
  }
@@ -1051,13 +1049,13 @@ NOINLINE(static) VALUE json_string_unescape(JSON_ParserState *state, JSON_Parser
1051
1049
  p = ++pe;
1052
1050
  break;
1053
1051
  }
1054
- case 0:
1055
- return Qundef;
1056
1052
  default:
1057
1053
  if ((unsigned char)*pe < 0x20) {
1058
1054
  if (!config->allow_control_characters) {
1059
1055
  if (*pe == '\n') {
1060
1056
  raise_syntax_error_at("Invalid unescaped newline character (\\n) in string: %s", state, pe - 1);
1057
+ } else if (*pe == '\0') {
1058
+ raise_syntax_error_at("Invalid unescaped NULL character (\\0) in string: %s", state, pe - 1);
1061
1059
  }
1062
1060
  raise_syntax_error_at("invalid ASCII control character in string: %s", state, pe - 1);
1063
1061
  }
@@ -1094,7 +1092,7 @@ NOINLINE(static) VALUE json_string_unescape(JSON_ParserState *state, JSON_Parser
1094
1092
 
1095
1093
  typedef VALUE (*json_number_decode_func_t)(const char *ptr);
1096
1094
 
1097
- static inline VALUE json_decode_large_number(const char *start, long len, json_number_decode_func_t func)
1095
+ static inline VALUE json_decode_large_number(const char *start, rb_len_t len, json_number_decode_func_t func)
1098
1096
  {
1099
1097
  if (RB_LIKELY(len < MAX_NUMBER_STACK_BUFFER)) {
1100
1098
  char buffer[MAX_NUMBER_STACK_BUFFER];
@@ -1117,7 +1115,7 @@ static VALUE json_decode_inum(const char *buffer)
1117
1115
  return rb_cstr2inum(buffer, 10);
1118
1116
  }
1119
1117
 
1120
- NOINLINE(static) VALUE json_decode_large_integer(const char *start, long len)
1118
+ NOINLINE(static) VALUE json_decode_large_integer(const char *start, rb_len_t len)
1121
1119
  {
1122
1120
  return json_decode_large_number(start, len, json_decode_inum);
1123
1121
  }
@@ -1158,7 +1156,7 @@ static VALUE json_decode_dnum(const char *buffer)
1158
1156
  return DBL2NUM(rb_cstr_to_dbl(buffer, 1));
1159
1157
  }
1160
1158
 
1161
- NOINLINE(static) VALUE json_decode_large_float(const char *start, long len)
1159
+ NOINLINE(static) VALUE json_decode_large_float(const char *start, rb_len_t len)
1162
1160
  {
1163
1161
  return json_decode_large_number(start, len, json_decode_dnum);
1164
1162
  }
@@ -1174,11 +1172,11 @@ static inline VALUE json_decode_float(JSON_ParserConfig *config, uint64_t mantis
1174
1172
  return rb_funcallv(config->decimal_class, config->decimal_method_id, 1, &text);
1175
1173
  }
1176
1174
 
1177
- if (RB_UNLIKELY(exponent > INT32_MAX)) {
1175
+ if (RB_UNLIKELY(exponent >= (int64_t)INT32_MAX)) {
1178
1176
  return negative ? CMinusInfinity : CInfinity;
1179
1177
  }
1180
1178
 
1181
- if (RB_UNLIKELY(exponent < INT32_MIN)) {
1179
+ if (RB_UNLIKELY(exponent <= INT32_MIN)) {
1182
1180
  return rb_float_new(negative ? -0.0 : 0.0);
1183
1181
  }
1184
1182
 
@@ -1237,9 +1235,10 @@ NORETURN(static) void raise_duplicate_key_error(JSON_ParserState *state, VALUE d
1237
1235
  if (state->parser) { // line and columns can't be accurate in resumable
1238
1236
  exc = parse_error_new(state, message, 0, 0, false);
1239
1237
  } else {
1240
- long line, column;
1238
+ long line;
1239
+ rb_len_t column;
1241
1240
  cursor_position(state, &line, &column);
1242
- rb_str_catf(message, " at line %ld column %ld", line, column);
1241
+ rb_str_catf(message, " at line %ld column %"PRIdLEN, line, column);
1243
1242
  exc = parse_error_new(state, message, line, column, false);
1244
1243
  }
1245
1244
  rb_ivar_set(exc, i_at_json_path, json_path_new(state, duplicate_key));
@@ -1518,7 +1517,7 @@ static inline VALUE json_parse_number(JSON_ParserState *state, JSON_ParserConfig
1518
1517
  }
1519
1518
 
1520
1519
  if (RB_UNLIKELY(exponent_digits >= 20 || abs_exponent > (uint64_t)INT64_MAX)) {
1521
- exponent = negative_exponent ? INT64_MIN : INT64_MAX;
1520
+ exponent = negative_exponent ? INT32_MIN : INT32_MAX;
1522
1521
  } else {
1523
1522
  exponent = negative_exponent ? -(int64_t)abs_exponent : (int64_t)abs_exponent;
1524
1523
  }
@@ -1815,7 +1814,7 @@ ALWAYS_INLINE(static) bool json_parse_any(JSON_ParserState *state, JSON_ParserCo
1815
1814
  if (RB_LIKELY(peek(state) == '"')) {
1816
1815
  VALUE string = json_parse_string(state, config, true);
1817
1816
  if (UNDEF_P(string)) {
1818
- if (resumable) {
1817
+ if (resumable && eos(state)) {
1819
1818
  state->cursor = start;
1820
1819
  return false;
1821
1820
  } else {
@@ -2021,7 +2020,15 @@ static int parser_config_init_i(VALUE key, VALUE val, VALUE data)
2021
2020
  else if (key == sym_allow_invalid_escape) { config->allow_invalid_escape = RTEST(val); }
2022
2021
  else if (key == sym_symbolize_names) { config->symbolize_names = RTEST(val); }
2023
2022
  else if (key == sym_freeze) { config->freeze = RTEST(val); }
2024
- else if (key == sym_on_load) { parser_config_wb_write(self, &config->on_load_proc, RTEST(val) ? val : Qfalse); }
2023
+ else if (key == sym_on_load) {
2024
+ if (RTEST(val) && !rb_obj_is_proc(val)) {
2025
+ val = rb_check_funcall(val, rb_intern("to_proc"), 0, NULL);
2026
+ if (val == Qundef || !rb_obj_is_proc(val)) {
2027
+ rb_raise(rb_eTypeError, "on_load must be a Proc");
2028
+ }
2029
+ }
2030
+ parser_config_wb_write(self, &config->on_load_proc, RTEST(val) ? val : Qfalse);
2031
+ }
2025
2032
  else if (key == sym_allow_duplicate_key) { config->allow_duplicate_key = RTEST(val); }
2026
2033
  else if (key == sym_decimal_class) {
2027
2034
  if (RTEST(val)) {
@@ -2041,8 +2048,8 @@ static int parser_config_init_i(VALUE key, VALUE val, VALUE data)
2041
2048
  parser_config_wb_write(self, &config->decimal_class, rb_path_to_class(mod_path));
2042
2049
 
2043
2050
  const char *method_name_beg = last_colon + 1;
2044
- long before_len = method_name_beg - name_cstr;
2045
- long len = RSTRING_LEN(name) - before_len;
2051
+ rb_len_t before_len = method_name_beg - name_cstr;
2052
+ rb_len_t len = RSTRING_LEN(name) - before_len;
2046
2053
  VALUE method_name = rb_str_substr(name, before_len, len);
2047
2054
  config->decimal_method_id = SYM2ID(rb_str_intern(method_name));
2048
2055
  } else {
@@ -2109,9 +2116,7 @@ static VALUE cParser_parse(JSON_ParserConfig *config, VALUE src)
2109
2116
  // Ensure the string isn't mutated under us.
2110
2117
  // The classic API to use is `rb_str_locktmp`, but then we'd
2111
2118
  // need to use `rb_protect` to make sure we always unlock.
2112
- if (Vsource == src) {
2113
- Vsource = rb_str_new_frozen(Vsource);
2114
- }
2119
+ Vsource = rb_str_new_frozen(Vsource);
2115
2120
 
2116
2121
  VALUE rvalue_stack_buffer[RVALUE_STACK_INITIAL_CAPA];
2117
2122
  rvalue_stack value_stack = {
@@ -2135,7 +2140,7 @@ static VALUE cParser_parse(JSON_ParserConfig *config, VALUE src)
2135
2140
  .head = 1,
2136
2141
  };
2137
2142
 
2138
- long len;
2143
+ rb_len_t len;
2139
2144
  const char *start;
2140
2145
 
2141
2146
  RSTRING_GETMEM(Vsource, start, len);
@@ -2454,7 +2459,7 @@ static VALUE cResumableParser_feed(VALUE self, VALUE str)
2454
2459
  rb_str_append(parser->buffer, str);
2455
2460
  }
2456
2461
 
2457
- long len;
2462
+ rb_len_t len;
2458
2463
  const char *start;
2459
2464
  RSTRING_GETMEM(parser->buffer, start, len);
2460
2465
  parser->state.start = start;
@@ -2760,7 +2765,7 @@ static VALUE cResumableParser_rest(VALUE self)
2760
2765
 
2761
2766
  size_t offset = parser->state.cursor - parser->state.start;
2762
2767
  const char *ptr;
2763
- long len;
2768
+ rb_len_t len;
2764
2769
  RSTRING_GETMEM(parser->buffer, ptr, len);
2765
2770
  return rb_utf8_str_new(ptr + offset, len - offset);
2766
2771
  }
data/lib/json/common.rb CHANGED
@@ -80,14 +80,6 @@ module JSON
80
80
  def generator=(generator) # :nodoc:
81
81
  old, $VERBOSE = $VERBOSE, nil
82
82
 
83
- # The default proc used when the +sort_keys+ generation option is +true+.
84
- # It returns a new hash with the entries sorted by their keys.
85
- sort_keys_proc = ->(hash) { hash.sort.to_h }
86
- if defined?(::Ractor) && Ractor.respond_to?(:shareable_lambda)
87
- sort_keys_proc = Ractor.shareable_lambda(&sort_keys_proc)
88
- end
89
- generator::State.default_sort_keys_proc = sort_keys_proc
90
-
91
83
  @generator = generator
92
84
  if generator.const_defined?(:GeneratorMethods)
93
85
  generator_methods = generator::GeneratorMethods
@@ -143,11 +135,11 @@ module JSON
143
135
  # This exception is raised if a parser error occurs.
144
136
  class ParserError < JSONError
145
137
  # Line number where the parser encountered an error.
146
- # Is <tt>nil</tt> when raised by JSON::ResumableParser.
138
+ # Is +nil+ when raised by JSON::ResumableParser.
147
139
  attr_reader :line
148
140
 
149
- # Column number where the parser encountered an error.
150
- # Is <tt>nil</tt> when raised by JSON::ResumableParser.
141
+ # One-based column number where the parser encountered an error, counted in Unicode codepoints.
142
+ # Is +nil+ when raised by JSON::ResumableParser.
151
143
  attr_reader :column
152
144
 
153
145
  # Returns a best effort JSONPath string representing where in the document
@@ -179,7 +171,10 @@ module JSON
179
171
  when Integer
180
172
  "[#{segment}]"
181
173
  when String, Symbol
182
- if segment.match?(/\A[a-zA-Z\$\_][a-zA-Z\$\_0-9]*\z/)
174
+ if !segment.valid_encoding?
175
+ segment = segment.b.gsub(/["\\]/n, { '"' => '\\"', '\\' => '\\\\' }).force_encoding(Encoding::UTF_8)
176
+ %{["#{segment}"]}
177
+ elsif segment.match?(/\A[a-zA-Z\$\_][a-zA-Z\$\_0-9]*\z/)
183
178
  ".#{segment}"
184
179
  else
185
180
  segment = segment.to_s.gsub(/["\\]/, { '"' => '\\"', '\\' => '\\\\' })
@@ -232,7 +227,7 @@ module JSON
232
227
  Fragment = Struct.new(:json) do
233
228
  def initialize(json)
234
229
  unless string = String.try_convert(json)
235
- raise TypeError, " no implicit conversion of #{json.class} into String"
230
+ raise TypeError, "no implicit conversion of #{json.class} into String"
236
231
  end
237
232
 
238
233
  super(string)
@@ -290,7 +285,7 @@ module JSON
290
285
  # ---
291
286
  #
292
287
  # Raises an exception if +source+ is not valid JSON:
293
- # # Raises JSON::ParserError unexpected character: 'invalid' at line 1 column 1 :
288
+ # # Raises JSON::ParserError (unexpected character: 'invalid' at line 1 column 1):
294
289
  # JSON.parse('invalid')
295
290
  #
296
291
  def parse(source, on_load: nil, object_class: nil, array_class: nil, **options)
@@ -324,8 +319,8 @@ module JSON
324
319
  # parse(File.read(path), **)
325
320
  #
326
321
  # See method #parse.
327
- def load_file(filespec, ...)
328
- parse(File.read(filespec, encoding: Encoding::UTF_8), ...)
322
+ def load_file(filespec, **options)
323
+ parse(File.read(filespec, encoding: Encoding::UTF_8), **options)
329
324
  end
330
325
 
331
326
  # :call-seq:
@@ -335,8 +330,8 @@ module JSON
335
330
  # JSON.parse!(File.read(path), **)
336
331
  #
337
332
  # See method #parse!
338
- def load_file!(filespec, ...)
339
- parse!(File.read(filespec, encoding: Encoding::UTF_8), ...)
333
+ def load_file!(filespec, **options)
334
+ parse!(File.read(filespec, encoding: Encoding::UTF_8), **options)
340
335
  end
341
336
 
342
337
  # :call-seq:
@@ -372,7 +367,7 @@ module JSON
372
367
  #
373
368
  # Raises an exception if +obj+ contains circular references:
374
369
  # a = []; b = []; a.push(b); b.push(a)
375
- # # Raises JSON::NestingError (nesting of 100 is too deep):
370
+ # # Raises JSON::NestingError (nesting of 100 is too deep. Did you try to serialize objects with circular references?):
376
371
  # JSON.generate(a)
377
372
  #
378
373
  def generate(obj, opts = nil)
@@ -707,10 +702,10 @@ module JSON
707
702
  unless source.is_a?(String)
708
703
  if source.respond_to? :to_str
709
704
  source = source.to_str
705
+ elsif source.respond_to? :read
706
+ source = source.read
710
707
  elsif source.respond_to? :to_io
711
708
  source = source.to_io.read
712
- elsif source.respond_to?(:read)
713
- source = source.read
714
709
  end
715
710
  end
716
711
 
@@ -771,7 +766,7 @@ module JSON
771
766
  end
772
767
 
773
768
  opts = {
774
- allow_nan: true,
769
+ allow_nan: !(kwargs && kwargs[:rfc8785]),
775
770
  }
776
771
  opts[:max_nesting] = _deprecated_limit if _deprecated_limit
777
772
  opts.merge!(kwargs) if kwargs
@@ -812,9 +807,9 @@ module JSON
812
807
  private_constant :EXCLUDED_GENERATOR_OPTIONS
813
808
 
814
809
  # :call-seq:
815
- # JSON.new(options = nil, &block)
810
+ # JSON::Coder.new(**options, &block)
816
811
  #
817
- # Argument +options+, if given, contains a \Hash of options for both parsing and generating.
812
+ # Keyword arguments +options+, if given, are options for both parsing and generating.
818
813
  # See {Parsing Options}[rdoc-ref:JSON@Parsing+Options],
819
814
  # and {Generating Options}[rdoc-ref:JSON@Generating+Options].
820
815
  #
@@ -835,7 +830,12 @@ module JSON
835
830
  #
836
831
  # puts MyApp::API_JSON_CODER.dump(Time.now.utc) # => "2025-01-21T08:41:44.286Z"
837
832
  #
838
- def initialize(object_class: nil, array_class: nil, on_load: nil, **options, &as_json)
833
+ # The conversion callback can also be passed as +as_json:+. If both are given, the block takes precedence.
834
+ # Passing +false+ or +nil+ for +strict:+ raises ArgumentError.
835
+ #
836
+ def initialize(object_class: nil, array_class: nil, on_load: nil, strict: true, **options, &as_json)
837
+ raise ArgumentError, "JSON::Coder requires strict mode" unless strict
838
+
839
839
  if object_class || array_class
840
840
  on_load = ParserOptions.on_load(on_load, object_class, array_class)
841
841
  end
@@ -847,7 +847,7 @@ module JSON
847
847
  @state = State.new(
848
848
  **generator_options,
849
849
  strict: true,
850
- as_json: as_json,
850
+ as_json: as_json || generator_options[:as_json],
851
851
  ).freeze
852
852
  end
853
853
 
@@ -54,7 +54,8 @@ module JSON
54
54
  strict: strict?,
55
55
  depth: depth,
56
56
  buffer_initial_length: buffer_initial_length,
57
- sort_keys: sort_keys
57
+ sort_keys: sort_keys,
58
+ rfc8785: rfc8785?,
58
59
  }
59
60
 
60
61
  allow_duplicate_key = allow_duplicate_key?
@@ -62,11 +63,6 @@ module JSON
62
63
  result[:allow_duplicate_key] = allow_duplicate_key
63
64
  end
64
65
 
65
- instance_variables.each do |iv|
66
- iv = iv.to_s[1..-1]
67
- result[iv.to_sym] = self[iv]
68
- end
69
-
70
66
  result
71
67
  end
72
68
 
data/lib/json/ext.rb CHANGED
@@ -28,17 +28,143 @@ module JSON
28
28
  end
29
29
  end
30
30
 
31
+ class << self
32
+ if defined?(::Ractor) && Ractor.respond_to?(:shareable_lambda)
33
+ def shareable_lambda(block) # :nodoc:
34
+ Ractor.shareable_lambda(&block)
35
+ end
36
+ else
37
+ def shareable_lambda(block) # :nodoc:
38
+ block
39
+ end
40
+ end
41
+
42
+ def valid_encoding?(str) # :nodoc:
43
+ str.ascii_only? || (str.encoding == Encoding::UTF_8 && str.valid_encoding?)
44
+ end
45
+
46
+ def coerce_key(key, as_json) # :nodoc:
47
+ as_json_called = false
48
+
49
+ unless String === key || Symbol === key
50
+ key = as_json.call(key, true)
51
+ as_json_called = true
52
+ end
53
+
54
+ if Symbol === key
55
+ key = key.to_s
56
+ end
57
+
58
+ unless String === key
59
+ raise GeneratorError.new("#{key.class} not allowed as object key in JSON", key)
60
+ end
61
+
62
+ unless valid_encoding?(key)
63
+ if as_json_called
64
+ raise GeneratorError.new("source sequence is illegal/malformed utf-8", key)
65
+ end
66
+
67
+ key = as_json.call(key, true)
68
+ unless String === key
69
+ raise GeneratorError.new("#{key.class} not allowed as object key in JSON", key)
70
+ end
71
+
72
+ unless Ext.valid_encoding?(key)
73
+ raise GeneratorError.new("source sequence is illegal/malformed utf-8", key)
74
+ end
75
+ end
76
+
77
+ key
78
+ end
79
+
80
+ def on_duplicate_key(hash, as_json)
81
+ set = {}
82
+ hash.each_key do |key|
83
+ key = coerce_key(key, as_json)
84
+ if set[key]
85
+ raise GeneratorError.new("detected duplicate key #{key.inspect}", key)
86
+ else
87
+ set[key] = true
88
+ end
89
+ end
90
+ end
91
+ end
92
+
31
93
  require 'json/ext/parser'
32
94
  Ext::Parser::Config = Ext::ParserConfig
33
95
  JSON.parser = Ext::Parser
34
96
 
35
- if RUBY_ENGINE == 'truffleruby'
97
+ generator = if RUBY_ENGINE == 'truffleruby'
36
98
  require 'json/truffle_ruby/generator'
37
- JSON.generator = JSON::TruffleRuby::Generator
99
+ JSON::TruffleRuby::Generator
38
100
  else
39
101
  require 'json/ext/generator'
40
- JSON.generator = Generator
102
+ Generator
41
103
  end
104
+
105
+ # The default proc used when the +sort_keys+ generation option is +true+.
106
+ # It returns a new hash with the entries sorted by their keys.
107
+ generator::State.default_sort_keys_proc = shareable_lambda(->(hash) {
108
+ hash.sort.to_h
109
+ })
110
+
111
+ # Directly lifted from Gregg Kellogg's json-canonicalization
112
+ generator::State.rfc8785_number_formatter_proc = shareable_lambda(->(num) {
113
+ if Integer === num
114
+ if num > Float::MAX || num < -Float::MAX
115
+ raise GeneratorError.new("Integer out of range for RFC 8785", num)
116
+ end
117
+ num = num.to_f
118
+ end
119
+ if num.zero?
120
+ "0"
121
+ else
122
+ if num < 0
123
+ num, sign = -num, '-'
124
+ end
125
+ native_rep = num.to_s
126
+ decimal, exponential = native_rep.split('e')
127
+ exp_val = exponential.to_i
128
+ exponential = exp_val > 0 ? ('+' + exp_val.to_s) : exp_val.to_s
129
+
130
+ integral, fractional = decimal.split('.')
131
+ fractional = fractional.sub(/0+$/, '') # Remove trailing zeros
132
+
133
+ if exp_val > 0 && exp_val < 21
134
+ while exp_val > 0
135
+ integral += fractional.to_s[0] || '0'
136
+ fractional = fractional.to_s[1..-1]
137
+ exp_val -= 1
138
+ end
139
+ exponential = nil
140
+ elsif exp_val == 0
141
+ exponential = nil
142
+ elsif exp_val < 0 && exp_val > -7
143
+ # Small numbers are shown as 0.etc with e-6 as lower limit
144
+ fractional, integral, exponential = integral + fractional.to_s, '0', nil
145
+ fractional = ("0" * (-exp_val - 1)) + fractional
146
+ end
147
+
148
+ fractional = nil if fractional.to_s.empty?
149
+ sign.to_s + integral + (fractional ? ".#{fractional}" : '') + (exponential ? "e#{exponential}" : '')
150
+ end
151
+ })
152
+
153
+ generator::State.rfc8785_sort_keys_proc = shareable_lambda(->(hash, as_json) {
154
+ converted = if as_json
155
+ hash.transform_keys { |k| Ext.coerce_key(k, as_json) }
156
+ else
157
+ hash.transform_keys(&:to_s)
158
+ end
159
+
160
+ if converted.size < hash.size
161
+ Ext.on_duplicate_key(hash, as_json)
162
+ end
163
+
164
+ converted.sort_by { |k,| k.encode(Encoding::UTF_16) }.to_h
165
+ })
166
+
167
+ JSON.generator = generator
42
168
  end
43
169
 
44
170
  if defined?(ResumableParser) # Not yet available on JRuby