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.
@@ -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)
@@ -168,6 +170,7 @@ module JSON
168
170
  @max_nesting = 100
169
171
  @sort_keys = false
170
172
  @allow_duplicate_key = false
173
+ @rfc8785 = false
171
174
  _configure(**opts) if opts
172
175
  end
173
176
 
@@ -189,7 +192,16 @@ module JSON
189
192
  attr_accessor :array_nl
190
193
 
191
194
  # This proc converts unsupported types into native JSON types.
192
- 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
193
205
 
194
206
  # This integer returns the maximum level of data structure nesting in
195
207
  # the generated JSON, max_nesting = 0 if no maximum is checked.
@@ -209,6 +221,9 @@ module JSON
209
221
  # desired order.
210
222
  attr_reader :sort_keys
211
223
 
224
+ # Controls whether RFC8785 (canonicalization) mode is enabled. Otherwise returns false.
225
+ attr_writer :rfc8785
226
+
212
227
  def sort_keys=(value) # :nodoc:
213
228
  type_error = false
214
229
  @sort_keys = case value
@@ -288,6 +303,27 @@ module JSON
288
303
  @strict
289
304
  end
290
305
 
306
+ # Returns true, if RFC8785 (canonicalization) mode is enabled. Otherwise returns false.
307
+ def rfc8785?
308
+ @rfc8785
309
+ end
310
+
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
323
+ end
324
+ self
325
+ end
326
+
291
327
  # Configure this State instance with the Hash _opts_, and return
292
328
  # itself.
293
329
  def configure(options)
@@ -302,10 +338,13 @@ module JSON
302
338
  end
303
339
  alias merge configure
304
340
 
341
+ # Defaults are the current values so that #configure only writes what it was given.
305
342
  private def _configure(
306
- indent: '', space: '', space_before: '', object_nl: '', array_nl: '', allow_nan: false,
307
- as_json: false, ascii_only: false, sort_keys: false, depth: 0, buffer_initial_length: 1024,
308
- allow_duplicate_key: false, script_safe: false, strict: false, max_nesting: 100
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
309
348
  )
310
349
  if depth.negative?
311
350
  raise ArgumentError, "depth must be >= 0 (got #{depth})"
@@ -321,7 +360,11 @@ module JSON
321
360
  @object_nl = object_nl || ''
322
361
  @array_nl = array_nl || ''
323
362
  @allow_nan = allow_nan || false
324
- @as_json = as_json || false
363
+ if as_json
364
+ self.as_json = as_json
365
+ else
366
+ @as_json = false
367
+ end
325
368
  @ascii_only = ascii_only || false
326
369
  self.sort_keys = sort_keys
327
370
  @depth = depth
@@ -330,7 +373,9 @@ module JSON
330
373
  @script_safe = script_safe
331
374
  @strict = strict
332
375
  @max_nesting = max_nesting || 0
333
- self
376
+ self.rfc8785 = rfc8785
377
+
378
+ validate_rfc8785
334
379
  end
335
380
 
336
381
  def allow_duplicate_key? # :nodoc:
@@ -340,13 +385,24 @@ module JSON
340
385
  # Returns the configuration instance variables as a hash, that can be
341
386
  # passed to the configure method.
342
387
  def to_h
343
- result = {}
344
- instance_variables.each do |iv|
345
- key = iv.to_s[1..-1]
346
- result[key.to_sym] = instance_variable_get(iv)
347
- end
348
-
349
- 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
+ }
350
406
  end
351
407
 
352
408
  alias to_hash to_h
@@ -359,16 +415,14 @@ module JSON
359
415
  return dup.generate(obj, anIO) if frozen?
360
416
 
361
417
  depth = @depth
418
+ validate_rfc8785
362
419
  if @indent.empty? and @space.empty? and @space_before.empty? and @object_nl.empty? and @array_nl.empty? and
363
420
  !@ascii_only and !@script_safe and @max_nesting == 0 and (!@strict || Symbol === obj) and !@sort_keys
364
421
  result = generate_json(obj, ''.dup)
365
422
  else
366
- if @sort_keys
367
- obj = @sort_keys.call(obj)
368
- end
369
-
370
423
  result = obj.to_json(self)
371
424
  end
425
+
372
426
  JSON::TruffleRuby::Generator.valid_utf8?(result) or raise GeneratorError.new(
373
427
  "source sequence #{result.inspect} is illegal/malformed utf-8",
374
428
  obj
@@ -390,6 +444,11 @@ module JSON
390
444
  buf << '{'
391
445
  first = true
392
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
393
452
  obj.each_pair do |k,v|
394
453
  if first
395
454
  key_type = k.class
@@ -431,7 +490,7 @@ module JSON
431
490
  buf << obj.to_json(self)
432
491
  end
433
492
  when Integer
434
- buf << obj.to_s
493
+ buf << (@rfc8785 ? State.rfc8785_number_formatter_proc.call(obj) : obj.to_s)
435
494
  when Symbol
436
495
  if @strict
437
496
  fast_serialize_string(obj.name, buf)
@@ -500,10 +559,12 @@ module JSON
500
559
  def to_json(state = nil, *)
501
560
  state = State.from_state(state)
502
561
  depth = state.depth
503
- state.check_max_nesting
504
- json_transform(state)
505
- ensure
506
- state.depth = depth
562
+ begin
563
+ state.check_max_nesting
564
+ json_transform(state)
565
+ ensure
566
+ state.depth = depth
567
+ end
507
568
  end
508
569
 
509
570
  private
@@ -521,13 +582,21 @@ module JSON
521
582
  first = true
522
583
  key_type = nil
523
584
  indent = !state.object_nl.empty?
524
- 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|
525
594
  if first
526
595
  key_type = key.class
527
596
  else
528
597
  if key_type && !state.allow_duplicate_key? && key_type != key.class
529
598
  key_type = nil # stop checking
530
- JSON.send(:on_mixed_keys_hash, self)
599
+ JSON.send(:on_mixed_keys_hash, hash)
531
600
  end
532
601
  result << delim
533
602
  end
@@ -592,10 +661,12 @@ module JSON
592
661
  def to_json(state = nil, *)
593
662
  state = State.from_state(state)
594
663
  depth = state.depth
595
- state.check_max_nesting
596
- json_transform(state)
597
- ensure
598
- state.depth = depth
664
+ begin
665
+ state.check_max_nesting
666
+ json_transform(state)
667
+ ensure
668
+ state.depth = depth
669
+ end
599
670
  end
600
671
 
601
672
  private
@@ -648,13 +719,20 @@ module JSON
648
719
 
649
720
  module Integer
650
721
  # Returns a JSON string representation for this Integer number.
651
- 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
652
729
  end
653
730
 
654
731
  module Float
655
732
  # Returns a JSON string representation for this Float number.
656
733
  def to_json(state = nil, *args)
657
734
  state = State.from_state(state)
735
+
658
736
  if infinite? || nan?
659
737
  if state.allow_nan?
660
738
  to_s
@@ -676,6 +754,8 @@ module JSON
676
754
  else
677
755
  raise GeneratorError.new("#{self} not allowed in JSON", self)
678
756
  end
757
+ elsif state.rfc8785?
758
+ State.rfc8785_number_formatter_proc.call(self)
679
759
  else
680
760
  to_s
681
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 = '3.0.1'
4
+ VERSION = '3.1.0'
5
5
  end
data/lib/json.rb CHANGED
@@ -44,13 +44,13 @@ require 'json/common'
44
44
  #
45
45
  # You can parse a \String containing \JSON data using
46
46
  # either of two methods:
47
- # - <tt>JSON.parse(source, opts)</tt>
48
- # - <tt>JSON.parse!(source, opts)</tt>
47
+ # - <tt>JSON.parse(source, **opts)</tt>
48
+ # - <tt>JSON.parse!(source, **opts)</tt>
49
49
  #
50
50
  # where
51
51
  # - +source+ is a Ruby object.
52
- # - +opts+ is a \Hash object containing options
53
- # that control both input allowed and output formatting.
52
+ # - +opts+ are keyword arguments that control both input
53
+ # allowed and output formatting.
54
54
  #
55
55
  # The difference between the two methods
56
56
  # is that JSON.parse! omits some checks
@@ -102,7 +102,7 @@ require 'json/common'
102
102
  # ruby # => 1.0
103
103
  # ruby.class # => Float
104
104
  # ruby = JSON.parse('2.0e2')
105
- # ruby # => 200
105
+ # ruby # => 200.0
106
106
  # ruby.class # => Float
107
107
  # Boolean:
108
108
  # ruby = JSON.parse('true')
@@ -131,10 +131,10 @@ require 'json/common'
131
131
  # ruby # => [0, [1, [2, [3]]]]
132
132
  # Too deep:
133
133
  # # Raises JSON::NestingError (nesting of 2 is too deep):
134
- # JSON.parse(source, {max_nesting: 1})
134
+ # JSON.parse(source, max_nesting: 1)
135
135
  # Bad value:
136
- # # Raises TypeError (wrong argument type Symbol (expected Fixnum)):
137
- # JSON.parse(source, {max_nesting: :foo})
136
+ # # Raises TypeError (no implicit conversion of Symbol into Integer):
137
+ # JSON.parse(source, max_nesting: :foo)
138
138
  #
139
139
  # ---
140
140
  #
@@ -142,11 +142,11 @@ require 'json/common'
142
142
  # should be ignored or cause an error to be raised:
143
143
  #
144
144
  # When set to +false+, the default:
145
- # JSON.parse('{"a": 1, "a":2}') => duplicate key at line 1 column 1 (JSON::ParserError)
145
+ # JSON.parse('{"a": 1, "a": 2}') # duplicate key "a" at line 1 column 1 (JSON::ParserError)
146
146
  #
147
147
  # When set to +true+:
148
148
  # # The last value is used.
149
- # JSON.parse('{"a": 1, "a":2}', allow_duplicate_key: true) => {"a" => 2}
149
+ # JSON.parse('{"a": 1, "a": 2}', allow_duplicate_key: true) # => {"a" => 2}
150
150
  #
151
151
  # ---
152
152
  #
@@ -155,15 +155,15 @@ require 'json/common'
155
155
  # defaults to +false+.
156
156
  #
157
157
  # With the default, +false+:
158
- # # Raises JSON::ParserError (225: unexpected token at '[NaN]'):
158
+ # # Raises JSON::ParserError (unexpected token 'NaN]' at line 1 column 2):
159
159
  # JSON.parse('[NaN]')
160
- # # Raises JSON::ParserError (232: unexpected token at '[Infinity]'):
160
+ # # Raises JSON::ParserError (unexpected token 'Infinity]' at line 1 column 2):
161
161
  # JSON.parse('[Infinity]')
162
- # # Raises JSON::ParserError (248: unexpected token at '[-Infinity]'):
162
+ # # Raises JSON::ParserError (invalid number: '-Infinity]' at line 1 column 2):
163
163
  # JSON.parse('[-Infinity]')
164
164
  # Allow:
165
165
  # source = '[NaN, Infinity, -Infinity]'
166
- # ruby = JSON.parse(source, {allow_nan: true})
166
+ # ruby = JSON.parse(source, allow_nan: true)
167
167
  # ruby # => [NaN, Infinity, -Infinity]
168
168
  #
169
169
  # ---
@@ -185,10 +185,10 @@ require 'json/common'
185
185
  # defaults to +false+.
186
186
  #
187
187
  # When set to +false+, the default:
188
- # JSON.parse('/* comment */ {"a": 1, "a":2}') # unexpected character: '/' at line 1 column 1 (JSON::ParserError)
188
+ # JSON.parse('/* comment */ {"a": 1, "a": 2}') # unexpected token '/*' at line 1 column 1 (JSON::ParserError)
189
189
  #
190
190
  # When set to +true+, comments are ignored:
191
- # JSON.parse('/* comment */ {"a": 1, "a":2} // more comment') # => {"a" => 2}
191
+ # JSON.parse('/* comment */ {"a": 1} // more comment', allow_comments: true) # => {"a" => 1}
192
192
  #
193
193
  # ---
194
194
  #
@@ -197,7 +197,7 @@ require 'json/common'
197
197
  # defaults to +false+.
198
198
  #
199
199
  # With the default, +false+:
200
- # JSON.parse(%{"Hello\nWorld"}) # invalid ASCII control character in string (JSON::ParserError)
200
+ # JSON.parse(%{"Hello\nWorld"}) # invalid ASCII control character in string: \nWorld" at line 2 column 0 (JSON::ParserError)
201
201
  #
202
202
  # When enabled:
203
203
  # JSON.parse(%{"Hello\nWorld"}, allow_control_characters: true) # => "Hello\nWorld"
@@ -209,7 +209,7 @@ require 'json/common'
209
209
  # defaults to +false+.
210
210
  #
211
211
  # With the default, +false+:
212
- # JSON.parse('"Hell\o"') # invalid escape character in string (JSON::ParserError)
212
+ # JSON.parse('"Hell\o"') # invalid escape character in string: '\o"' at line 1 column 6 (JSON::ParserError)
213
213
  #
214
214
  # When enabled:
215
215
  # JSON.parse('"Hell\o"', allow_invalid_escape: true) # => "Hello"
@@ -228,8 +228,8 @@ require 'json/common'
228
228
  # ruby = JSON.parse(source)
229
229
  # ruby # => {"a"=>"foo", "b"=>1.0, "c"=>true, "d"=>false, "e"=>nil}
230
230
  # Use Symbols:
231
- # ruby = JSON.parse(source, {symbolize_names: true})
232
- # ruby # => {:a=>"foo", :b=>1.0, :c=>true, :d=>false, :e=>nil}
231
+ # ruby = JSON.parse(source, symbolize_names: true)
232
+ # ruby # => {a: "foo", b: 1.0, c: true, d: false, e: nil}
233
233
  #
234
234
  # ---
235
235
  #
@@ -242,7 +242,7 @@ require 'json/common'
242
242
  # ruby = JSON.parse(source)
243
243
  # ruby.class # => Hash
244
244
  # Use class \OpenStruct:
245
- # ruby = JSON.parse(source, {object_class: OpenStruct})
245
+ # ruby = JSON.parse(source, object_class: OpenStruct)
246
246
  # ruby # => #<OpenStruct a="foo", b=1.0, c=true, d=false, e=nil>
247
247
  #
248
248
  # ---
@@ -256,8 +256,8 @@ require 'json/common'
256
256
  # ruby = JSON.parse(source)
257
257
  # ruby.class # => Array
258
258
  # Use class \Set:
259
- # ruby = JSON.parse(source, {array_class: Set})
260
- # ruby # => #<Set: {"foo", 1.0, true, false, nil}>
259
+ # ruby = JSON.parse(source, array_class: Set)
260
+ # ruby # => Set["foo", 1.0, true, false, nil]
261
261
  #
262
262
  # === Generating \JSON
263
263
  #
@@ -319,22 +319,22 @@ require 'json/common'
319
319
  # a \String containing a \JSON string representation of the source:
320
320
  # JSON.generate(:foo) # => '"foo"'
321
321
  # JSON.generate(Complex(0, 0)) # => '"0+0i"'
322
- # JSON.generate(Dir.new('.')) # => '"#<Dir>"'
322
+ # JSON.generate(Dir.new('.')) # => '"#<Dir:0x...>"'
323
323
  #
324
324
  # ==== Generating Options
325
325
  #
326
326
  # ====== Input Options
327
327
  #
328
328
  # Option +allow_nan+ (boolean) specifies whether
329
- # +NaN+, +Infinity+, and <tt>-Infinity</tt> may be generated;
329
+ # +NaN+, +Infinity+, and +-Infinity+ may be generated;
330
330
  # defaults to +false+.
331
331
  #
332
332
  # With the default, +false+:
333
- # # Raises JSON::GeneratorError (920: NaN not allowed in JSON):
333
+ # # Raises JSON::GeneratorError (NaN not allowed in JSON):
334
334
  # JSON.generate(JSON::NaN)
335
- # # Raises JSON::GeneratorError (917: Infinity not allowed in JSON):
335
+ # # Raises JSON::GeneratorError (Infinity not allowed in JSON):
336
336
  # JSON.generate(JSON::Infinity)
337
- # # Raises JSON::GeneratorError (917: -Infinity not allowed in JSON):
337
+ # # Raises JSON::GeneratorError (-Infinity not allowed in JSON):
338
338
  # JSON.generate(JSON::MinusInfinity)
339
339
  #
340
340
  # Allow:
@@ -345,14 +345,14 @@ require 'json/common'
345
345
  #
346
346
  # Option +allow_duplicate_key+ (boolean) specifies whether
347
347
  # hashes with duplicate keys should be allowed or produce an error.
348
- # defaults to emit a deprecation warning.
348
+ # Defaults to +false+, which raises an error.
349
349
  #
350
- # With the default, <tt>false</tt>:
351
- # JSON.generate({ foo: 1, "foo" => 2 })
350
+ # With the default, +false+:
351
+ # JSON.generate({foo: 1, "foo" => 2})
352
352
  # # detected duplicate key "foo" in {foo: 1, "foo" => 2} (JSON::GeneratorError)
353
353
  #
354
- # With <tt>true</tt>
355
- # JSON.generate({ foo: 1, "foo" => 2 }, allow_duplicate_key: true)
354
+ # With +true+:
355
+ # JSON.generate({foo: 1, "foo" => 2}, allow_duplicate_key: true)
356
356
  # # => '{"foo":1,"foo":2}'
357
357
  #
358
358
  # ---
@@ -365,13 +365,13 @@ require 'json/common'
365
365
  # JSON.generate(obj) # => '[[[[[[0]]]]]]'
366
366
  #
367
367
  # Too deep:
368
- # # Raises JSON::NestingError (nesting of 2 is too deep):
368
+ # # Raises JSON::NestingError (nesting of 2 is too deep. Did you try to serialize objects with circular references?):
369
369
  # JSON.generate(obj, max_nesting: 2)
370
370
  #
371
371
  # With +false+:
372
372
  # obj = []
373
373
  # obj[0] = obj
374
- # # Raises SystemStackError: stack level too deep
374
+ # # Raises SystemStackError (stack level too deep):
375
375
  # JSON.generate(obj, max_nesting: false)
376
376
  #
377
377
  # Setting +max_nesting+ to +false+ or a very large number can lead to a stack overflow
@@ -410,10 +410,18 @@ require 'json/common'
410
410
  # inserted before the colon in each \JSON object's pair;
411
411
  # defaults to the empty \String, <tt>''</tt>.
412
412
  # - Option +sort_keys+ (boolean or \Proc) controls whether and how the keys of a
413
- # hash are sorted when generating the output; defaults to <tt>false</tt>.
413
+ # hash are sorted when generating the output; defaults to +false+.
414
414
  # When +true+, keys are sorted lexicographically. When a \Proc, it receives
415
415
  # the entire \Hash and must return a \Hash with its pairs in the desired
416
416
  # order, allowing for arbitrary sort orders.
417
+ # - Option +rfc8785+ (boolean) controls whether the generated JSON will be canonicalized
418
+ # as defined in RFC8785.
419
+ # Integers are converted to Float before formatting and may be rounded.
420
+ # Integers outside the finite Float range raise JSON::GeneratorError.
421
+ # Nonempty +indent+, +space+, +space_before+, +object_nl+, or +array_nl+,
422
+ # and enabled +ascii_only+, +script_safe+, or +allow_nan+ raise ArgumentError.
423
+ # JSON.dump defaults +allow_nan+ to +false+ when +rfc8785+ is enabled.
424
+ # JSON::Fragment contents are inserted as is; the caller must ensure they are canonical.
417
425
  #
418
426
  # In this example, +obj+ is used first to generate the shortest
419
427
  # \JSON data (no whitespace), then again with all formatting options
@@ -439,7 +447,7 @@ require 'json/common'
439
447
  # "foo" : [
440
448
  # "bar",
441
449
  # "baz"
442
- # ],
450
+ # ],
443
451
  # "bat" : {
444
452
  # "bam" : 0,
445
453
  # "bad" : 1
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: 3.0.1
4
+ version: 3.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Florian Frank
@@ -68,7 +68,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
68
68
  - !ruby/object:Gem::Version
69
69
  version: '0'
70
70
  requirements: []
71
- rubygems_version: 4.0.16
71
+ rubygems_version: 4.0.20
72
72
  specification_version: 4
73
73
  summary: JSON Implementation for Ruby
74
74
  test_files: []