json 2.21.2 → 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.
@@ -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,42 +63,10 @@ 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
 
73
69
  alias_method :to_hash, :to_h
74
-
75
- # call-seq: [](name)
76
- #
77
- # Returns the value returned by method +name+.
78
- def [](name)
79
- ::JSON.deprecation_warning("JSON::State#[] is deprecated and will be removed in json 3.0.0")
80
-
81
- if respond_to?(name)
82
- __send__(name)
83
- else
84
- instance_variable_get("@#{name}") if
85
- instance_variables.include?("@#{name}".to_sym) # avoid warning
86
- end
87
- end
88
-
89
- # call-seq: []=(name, value)
90
- #
91
- # Sets the attribute name to value.
92
- def []=(name, value)
93
- ::JSON.deprecation_warning("JSON::State#[]= is deprecated and will be removed in json 3.0.0")
94
-
95
- if respond_to?(name_writer = "#{name}=")
96
- __send__ name_writer, value
97
- else
98
- instance_variable_set "@#{name}", value
99
- end
100
- end
101
70
  end
102
71
  end
103
72
  end
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
@@ -66,6 +192,4 @@ module JSON
66
192
  end
67
193
  end
68
194
  end
69
-
70
- JSON_LOADED = true unless defined?(JSON::JSON_LOADED)
71
195
  end
@@ -112,6 +112,8 @@ module JSON
112
112
  # while generating a JSON text from a Ruby data structure.
113
113
  class State
114
114
  singleton_class.attr_accessor :default_sort_keys_proc # :nodoc:
115
+ singleton_class.attr_accessor :rfc8785_number_formatter_proc # :nodoc:
116
+ singleton_class.attr_accessor :rfc8785_sort_keys_proc # :nodoc:
115
117
 
116
118
  def self.generate(obj, opts = nil, io = nil)
117
119
  new(opts).generate(obj, io)
@@ -125,7 +127,7 @@ module JSON
125
127
  if opts
126
128
  case
127
129
  when self === opts
128
- return opts
130
+ return opts.validate_rfc8785
129
131
  when opts.respond_to?(:to_hash)
130
132
  return new(opts.to_hash)
131
133
  when opts.respond_to?(:to_h)
@@ -167,7 +169,9 @@ module JSON
167
169
  @strict = false
168
170
  @max_nesting = 100
169
171
  @sort_keys = false
170
- configure(opts) if opts
172
+ @allow_duplicate_key = false
173
+ @rfc8785 = false
174
+ _configure(**opts) if opts
171
175
  end
172
176
 
173
177
  # This string is used to indent levels in the JSON text.
@@ -188,7 +192,16 @@ module JSON
188
192
  attr_accessor :array_nl
189
193
 
190
194
  # This proc converts unsupported types into native JSON types.
191
- attr_accessor :as_json
195
+ attr_reader :as_json
196
+
197
+ def as_json=(value)
198
+ raise FrozenError, "can't modify frozen #{self.class}: #{inspect}" if frozen?
199
+ value = value.to_proc if !(Proc === value) && value.respond_to?(:to_proc)
200
+ unless Proc === value
201
+ raise TypeError, "as_json must be a Proc"
202
+ end
203
+ @as_json = value
204
+ end
192
205
 
193
206
  # This integer returns the maximum level of data structure nesting in
194
207
  # the generated JSON, max_nesting = 0 if no maximum is checked.
@@ -208,6 +221,9 @@ module JSON
208
221
  # desired order.
209
222
  attr_reader :sort_keys
210
223
 
224
+ # Controls whether RFC8785 (canonicalization) mode is enabled. Otherwise returns false.
225
+ attr_writer :rfc8785
226
+
211
227
  def sort_keys=(value) # :nodoc:
212
228
  type_error = false
213
229
  @sort_keys = case value
@@ -287,67 +303,80 @@ module JSON
287
303
  @strict
288
304
  end
289
305
 
290
- # Configure this State instance with the Hash _opts_, and return
291
- # itself.
292
- def configure(opts)
293
- if opts.respond_to?(:to_hash)
294
- opts = opts.to_hash
295
- elsif opts.respond_to?(:to_h)
296
- opts = opts.to_h
297
- else
298
- raise TypeError, "can't convert #{opts.class} into Hash"
299
- end
306
+ # Returns true, if RFC8785 (canonicalization) mode is enabled. Otherwise returns false.
307
+ def rfc8785?
308
+ @rfc8785
309
+ end
300
310
 
301
- if opts[:depth]&.negative?
302
- raise ArgumentError, "depth must be >= 0 (got #{opts[:depth]})"
311
+ def validate_rfc8785 # :nodoc:
312
+ if @rfc8785
313
+ option = if !@indent.empty? then :indent
314
+ elsif !@space.empty? then :space
315
+ elsif !@space_before.empty? then :space_before
316
+ elsif !@object_nl.empty? then :object_nl
317
+ elsif !@array_nl.empty? then :array_nl
318
+ elsif @ascii_only then :ascii_only
319
+ elsif @script_safe then :script_safe
320
+ elsif @allow_nan then :allow_nan
321
+ end
322
+ raise ArgumentError, "#{option} cannot be used with rfc8785" if option
303
323
  end
324
+ self
325
+ end
304
326
 
305
- opts.each do |key, value|
306
- instance_variable_set "@#{key}", value
327
+ # Configure this State instance with the Hash _opts_, and return
328
+ # itself.
329
+ def configure(options)
330
+ unless Hash == options
331
+ if options.respond_to?(:to_hash)
332
+ options = options.to_hash
333
+ elsif options.respond_to?(:to_h)
334
+ options = options.to_h
335
+ end
307
336
  end
337
+ _configure(**options)
338
+ end
339
+ alias merge configure
308
340
 
309
- # NOTE: If adding new instance variables here, check whether #generate should check them for #generate_json
310
- @indent = opts[:indent] || '' if opts.key?(:indent)
311
- @space = opts[:space] || '' if opts.key?(:space)
312
- @space_before = opts[:space_before] || '' if opts.key?(:space_before)
313
- @object_nl = opts[:object_nl] || '' if opts.key?(:object_nl)
314
- @array_nl = opts[:array_nl] || '' if opts.key?(:array_nl)
315
- @allow_nan = !!opts[:allow_nan] if opts.key?(:allow_nan)
316
- @as_json = opts[:as_json].to_proc if opts[:as_json]
317
- @ascii_only = opts[:ascii_only] if opts.key?(:ascii_only)
318
- self.sort_keys = opts[:sort_keys] if opts.key?(:sort_keys)
319
- @depth = opts[:depth] || 0
320
- @buffer_initial_length ||= opts[:buffer_initial_length]
321
-
322
- @script_safe = if opts.key?(:script_safe)
323
- !!opts[:script_safe]
324
- elsif opts.key?(:escape_slash)
325
- !!opts[:escape_slash]
326
- else
327
- false
341
+ # Defaults are the current values so that #configure only writes what it was given.
342
+ private def _configure(
343
+ indent: @indent, space: @space, space_before: @space_before, object_nl: @object_nl,
344
+ array_nl: @array_nl, allow_nan: @allow_nan, as_json: @as_json, ascii_only: @ascii_only,
345
+ sort_keys: @sort_keys, depth: @depth, buffer_initial_length: @buffer_initial_length,
346
+ allow_duplicate_key: @allow_duplicate_key, script_safe: @script_safe, strict: @strict,
347
+ max_nesting: @max_nesting, rfc8785: @rfc8785
348
+ )
349
+ if depth.negative?
350
+ raise ArgumentError, "depth must be >= 0 (got #{depth})"
328
351
  end
329
352
 
330
- if opts.key?(:allow_duplicate_key)
331
- @allow_duplicate_key = !!opts[:allow_duplicate_key]
332
- else
333
- @allow_duplicate_key = nil # nil is deprecation
353
+ if max_nesting && !(Integer === max_nesting)
354
+ raise TypeError, ":max_nesting must be `false` or an Integer, got: #{max_nesting.class}"
334
355
  end
335
356
 
336
- @strict = !!opts[:strict] if opts.key?(:strict)
337
-
338
- if !opts.key?(:max_nesting) # defaults to 100
339
- @max_nesting = 100
340
- elsif opts[:max_nesting]
341
- unless opts[:max_nesting].is_a?(Integer)
342
- raise TypeError, ":max_nesting must be an Integer, got: #{opts[:max_nesting].class}"
343
- end
344
- @max_nesting = opts[:max_nesting]
357
+ @indent = indent || ''
358
+ @space = space || ''
359
+ @space_before = space_before || ''
360
+ @object_nl = object_nl || ''
361
+ @array_nl = array_nl || ''
362
+ @allow_nan = allow_nan || false
363
+ if as_json
364
+ self.as_json = as_json
345
365
  else
346
- @max_nesting = 0
366
+ @as_json = false
347
367
  end
348
- self
368
+ @ascii_only = ascii_only || false
369
+ self.sort_keys = sort_keys
370
+ @depth = depth
371
+ @buffer_initial_length = buffer_initial_length
372
+ @allow_duplicate_key = allow_duplicate_key
373
+ @script_safe = script_safe
374
+ @strict = strict
375
+ @max_nesting = max_nesting || 0
376
+ self.rfc8785 = rfc8785
377
+
378
+ validate_rfc8785
349
379
  end
350
- alias merge configure
351
380
 
352
381
  def allow_duplicate_key? # :nodoc:
353
382
  @allow_duplicate_key
@@ -356,17 +385,24 @@ module JSON
356
385
  # Returns the configuration instance variables as a hash, that can be
357
386
  # passed to the configure method.
358
387
  def to_h
359
- result = {}
360
- instance_variables.each do |iv|
361
- key = iv.to_s[1..-1]
362
- result[key.to_sym] = instance_variable_get(iv)
363
- end
364
-
365
- if result[:allow_duplicate_key].nil?
366
- result.delete(:allow_duplicate_key)
367
- end
368
-
369
- result
388
+ {
389
+ indent: @indent,
390
+ space: @space,
391
+ space_before: @space_before,
392
+ object_nl: @object_nl,
393
+ array_nl: @array_nl,
394
+ as_json: @as_json,
395
+ allow_nan: @allow_nan,
396
+ ascii_only: @ascii_only,
397
+ max_nesting: @max_nesting,
398
+ script_safe: @script_safe,
399
+ strict: @strict,
400
+ depth: @depth,
401
+ buffer_initial_length: @buffer_initial_length,
402
+ sort_keys: @sort_keys,
403
+ allow_duplicate_key: @allow_duplicate_key,
404
+ rfc8785: @rfc8785,
405
+ }
370
406
  end
371
407
 
372
408
  alias to_hash to_h
@@ -379,16 +415,14 @@ module JSON
379
415
  return dup.generate(obj, anIO) if frozen?
380
416
 
381
417
  depth = @depth
418
+ validate_rfc8785
382
419
  if @indent.empty? and @space.empty? and @space_before.empty? and @object_nl.empty? and @array_nl.empty? and
383
420
  !@ascii_only and !@script_safe and @max_nesting == 0 and (!@strict || Symbol === obj) and !@sort_keys
384
421
  result = generate_json(obj, ''.dup)
385
422
  else
386
- if @sort_keys
387
- obj = @sort_keys.call(obj)
388
- end
389
-
390
423
  result = obj.to_json(self)
391
424
  end
425
+
392
426
  JSON::TruffleRuby::Generator.valid_utf8?(result) or raise GeneratorError.new(
393
427
  "source sequence #{result.inspect} is illegal/malformed utf-8",
394
428
  obj
@@ -410,13 +444,18 @@ module JSON
410
444
  buf << '{'
411
445
  first = true
412
446
  key_type = nil
447
+ if @rfc8785
448
+ obj = State.rfc8785_sort_keys_proc.call(obj, @strict && @as_json)
449
+ elsif @sort_keys
450
+ obj = @sort_keys.call(obj)
451
+ end
413
452
  obj.each_pair do |k,v|
414
453
  if first
415
454
  key_type = k.class
416
455
  else
417
456
  if key_type && !@allow_duplicate_key && key_type != k.class
418
457
  key_type = nil # stop checking
419
- JSON.send(:on_mixed_keys_hash, obj, !@allow_duplicate_key.nil?)
458
+ JSON.send(:on_mixed_keys_hash, obj)
420
459
  end
421
460
  buf << ','
422
461
  end
@@ -451,7 +490,7 @@ module JSON
451
490
  buf << obj.to_json(self)
452
491
  end
453
492
  when Integer
454
- buf << obj.to_s
493
+ buf << (@rfc8785 ? State.rfc8785_number_formatter_proc.call(obj) : obj.to_s)
455
494
  when Symbol
456
495
  if @strict
457
496
  fast_serialize_string(obj.name, buf)
@@ -483,28 +522,6 @@ module JSON
483
522
  end
484
523
  buf << '"'
485
524
  end
486
-
487
- # Return the value returned by method +name+.
488
- def [](name)
489
- ::JSON.deprecation_warning("JSON::State#[] is deprecated and will be removed in json 3.0.0")
490
-
491
- if respond_to?(name)
492
- __send__(name)
493
- else
494
- instance_variable_get("@#{name}") if
495
- instance_variables.include?("@#{name}".to_sym) # avoid warning
496
- end
497
- end
498
-
499
- def []=(name, value)
500
- ::JSON.deprecation_warning("JSON::State#[]= is deprecated and will be removed in json 3.0.0")
501
-
502
- if respond_to?(name_writer = "#{name}=")
503
- __send__ name_writer, value
504
- else
505
- instance_variable_set "@#{name}", value
506
- end
507
- end
508
525
  end
509
526
 
510
527
  module GeneratorMethods
@@ -542,10 +559,12 @@ module JSON
542
559
  def to_json(state = nil, *)
543
560
  state = State.from_state(state)
544
561
  depth = state.depth
545
- state.check_max_nesting
546
- json_transform(state)
547
- ensure
548
- state.depth = depth
562
+ begin
563
+ state.check_max_nesting
564
+ json_transform(state)
565
+ ensure
566
+ state.depth = depth
567
+ end
549
568
  end
550
569
 
551
570
  private
@@ -563,13 +582,21 @@ module JSON
563
582
  first = true
564
583
  key_type = nil
565
584
  indent = !state.object_nl.empty?
566
- each { |key, value|
585
+ hash = self
586
+
587
+ if state.rfc8785?
588
+ hash = State.rfc8785_sort_keys_proc.call(hash, state.strict? && state.as_json)
589
+ elsif state.sort_keys
590
+ hash = state.sort_keys.call(hash)
591
+ end
592
+
593
+ hash.each { |key, value|
567
594
  if first
568
595
  key_type = key.class
569
596
  else
570
597
  if key_type && !state.allow_duplicate_key? && key_type != key.class
571
598
  key_type = nil # stop checking
572
- JSON.send(:on_mixed_keys_hash, self, state.allow_duplicate_key? == false)
599
+ JSON.send(:on_mixed_keys_hash, hash)
573
600
  end
574
601
  result << delim
575
602
  end
@@ -634,10 +661,12 @@ module JSON
634
661
  def to_json(state = nil, *)
635
662
  state = State.from_state(state)
636
663
  depth = state.depth
637
- state.check_max_nesting
638
- json_transform(state)
639
- ensure
640
- state.depth = depth
664
+ begin
665
+ state.check_max_nesting
666
+ json_transform(state)
667
+ ensure
668
+ state.depth = depth
669
+ end
641
670
  end
642
671
 
643
672
  private
@@ -690,13 +719,20 @@ module JSON
690
719
 
691
720
  module Integer
692
721
  # Returns a JSON string representation for this Integer number.
693
- def to_json(*) to_s end
722
+ def to_json(state = nil, *)
723
+ if state && State.from_state(state).rfc8785?
724
+ State.rfc8785_number_formatter_proc.call(self)
725
+ else
726
+ to_s
727
+ end
728
+ end
694
729
  end
695
730
 
696
731
  module Float
697
732
  # Returns a JSON string representation for this Float number.
698
733
  def to_json(state = nil, *args)
699
734
  state = State.from_state(state)
735
+
700
736
  if infinite? || nan?
701
737
  if state.allow_nan?
702
738
  to_s
@@ -718,6 +754,8 @@ module JSON
718
754
  else
719
755
  raise GeneratorError.new("#{self} not allowed in JSON", self)
720
756
  end
757
+ elsif state.rfc8785?
758
+ State.rfc8785_number_formatter_proc.call(self)
721
759
  else
722
760
  to_s
723
761
  end
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.21.2'
4
+ VERSION = '3.1.0'
5
5
  end