json 2.7.3 → 2.9.1

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.
data/json.gemspec CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  version = File.foreach(File.join(__dir__, "lib/json/version.rb")) do |line|
2
4
  /^\s*VERSION\s*=\s*'(.*)'/ =~ line and break $1
3
5
  end rescue nil
@@ -19,7 +21,7 @@ spec = Gem::Specification.new do |s|
19
21
  'wiki_uri' => 'https://github.com/ruby/json/wiki'
20
22
  }
21
23
 
22
- s.required_ruby_version = Gem::Requirement.new(">= 2.3")
24
+ s.required_ruby_version = Gem::Requirement.new(">= 2.7")
23
25
 
24
26
  if java_ext
25
27
  s.description = "A JSON implementation as a JRuby extension."
@@ -48,6 +50,7 @@ spec = Gem::Specification.new do |s|
48
50
 
49
51
  if java_ext
50
52
  s.platform = 'java'
53
+ s.files += Dir["lib/json/ext/**/*.jar"]
51
54
  else
52
55
  s.extensions = Dir["ext/json/**/extconf.rb"]
53
56
  s.files += Dir["ext/json/**/*.{c,h,rl}"]
@@ -35,7 +35,7 @@ class BigDecimal
35
35
  def as_json(*)
36
36
  {
37
37
  JSON.create_id => self.class.name,
38
- 'b' => _dump,
38
+ 'b' => _dump.force_encoding(Encoding::UTF_8),
39
39
  }
40
40
  end
41
41
 
data/lib/json/common.rb CHANGED
@@ -1,4 +1,5 @@
1
- #frozen_string_literal: true
1
+ # frozen_string_literal: true
2
+
2
3
  require 'json/version'
3
4
 
4
5
  module JSON
@@ -25,16 +26,14 @@ module JSON
25
26
  elsif object.respond_to?(:to_str)
26
27
  str = object.to_str
27
28
  if str.is_a?(String)
28
- return JSON.parse(object.to_str, opts)
29
+ return JSON.parse(str, opts)
29
30
  end
30
31
  end
31
32
 
32
33
  JSON.generate(object, opts)
33
34
  end
34
35
 
35
- # Returns the JSON parser class that is used by JSON. This is either
36
- # JSON::Ext::Parser or JSON::Pure::Parser:
37
- # JSON.parser # => JSON::Ext::Parser
36
+ # Returns the JSON parser class that is used by JSON.
38
37
  attr_reader :parser
39
38
 
40
39
  # Set the JSON parser class _parser_ to be used by JSON.
@@ -49,18 +48,9 @@ module JSON
49
48
  # level (absolute namespace path?). If there doesn't exist a constant at
50
49
  # the given path, an ArgumentError is raised.
51
50
  def deep_const_get(path) # :nodoc:
52
- path.to_s.split(/::/).inject(Object) do |p, c|
53
- case
54
- when c.empty? then p
55
- when p.const_defined?(c, true) then p.const_get(c)
56
- else
57
- begin
58
- p.const_missing(c)
59
- rescue NameError => e
60
- raise ArgumentError, "can't get const #{path}: #{e}"
61
- end
62
- end
63
- end
51
+ Object.const_get(path)
52
+ rescue NameError => e
53
+ raise ArgumentError, "can't get const #{path}: #{e}"
64
54
  end
65
55
 
66
56
  # Set the module _generator_ to be used by JSON.
@@ -69,7 +59,7 @@ module JSON
69
59
  @generator = generator
70
60
  generator_methods = generator::GeneratorMethods
71
61
  for const in generator_methods.constants
72
- klass = deep_const_get(const)
62
+ klass = const_get(const)
73
63
  modul = generator_methods.const_get(const)
74
64
  klass.class_eval do
75
65
  instance_methods(false).each do |m|
@@ -106,14 +96,10 @@ module JSON
106
96
  )
107
97
  end
108
98
 
109
- # Returns the JSON generator module that is used by JSON. This is
110
- # either JSON::Ext::Generator or JSON::Pure::Generator:
111
- # JSON.generator # => JSON::Ext::Generator
99
+ # Returns the JSON generator module that is used by JSON.
112
100
  attr_reader :generator
113
101
 
114
- # Sets or Returns the JSON generator state class that is used by JSON. This is
115
- # either JSON::Ext::Generator::State or JSON::Pure::Generator::State:
116
- # JSON.state # => JSON::Ext::Generator::State
102
+ # Sets or Returns the JSON generator state class that is used by JSON.
117
103
  attr_accessor :state
118
104
  end
119
105
 
@@ -157,7 +143,23 @@ module JSON
157
143
  # :startdoc:
158
144
 
159
145
  # This exception is raised if a generator or unparser error occurs.
160
- class GeneratorError < JSONError; end
146
+ class GeneratorError < JSONError
147
+ attr_reader :invalid_object
148
+
149
+ def initialize(message, invalid_object = nil)
150
+ super(message)
151
+ @invalid_object = invalid_object
152
+ end
153
+
154
+ def detailed_message(...)
155
+ if @invalid_object.nil?
156
+ super
157
+ else
158
+ "#{super}\nInvalid object: #{@invalid_object.inspect}"
159
+ end
160
+ end
161
+ end
162
+
161
163
  # For backwards compatibility
162
164
  UnparserError = GeneratorError # :nodoc:
163
165
 
@@ -195,17 +197,17 @@ module JSON
195
197
  # {Parsing \JSON}[#module-JSON-label-Parsing+JSON].
196
198
  #
197
199
  # Parses nested JSON objects:
198
- # source = <<-EOT
199
- # {
200
- # "name": "Dave",
201
- # "age" :40,
202
- # "hats": [
203
- # "Cattleman's",
204
- # "Panama",
205
- # "Tophat"
206
- # ]
207
- # }
208
- # EOT
200
+ # source = <<~JSON
201
+ # {
202
+ # "name": "Dave",
203
+ # "age" :40,
204
+ # "hats": [
205
+ # "Cattleman's",
206
+ # "Panama",
207
+ # "Tophat"
208
+ # ]
209
+ # }
210
+ # JSON
209
211
  # ruby = JSON.parse(source)
210
212
  # ruby # => {"name"=>"Dave", "age"=>40, "hats"=>["Cattleman's", "Panama", "Tophat"]}
211
213
  #
@@ -216,11 +218,7 @@ module JSON
216
218
  # JSON.parse('')
217
219
  #
218
220
  def parse(source, opts = nil)
219
- if opts.nil?
220
- Parser.new(source).parse
221
- else
222
- Parser.new(source, opts).parse
223
- end
221
+ Parser.parse(source, opts)
224
222
  end
225
223
 
226
224
  # :call-seq:
@@ -249,8 +247,8 @@ module JSON
249
247
  # parse(File.read(path), opts)
250
248
  #
251
249
  # See method #parse.
252
- def load_file(filespec, opts = {})
253
- parse(File.read(filespec), opts)
250
+ def load_file(filespec, opts = nil)
251
+ parse(File.read(filespec, encoding: Encoding::UTF_8), opts)
254
252
  end
255
253
 
256
254
  # :call-seq:
@@ -261,7 +259,7 @@ module JSON
261
259
  #
262
260
  # See method #parse!
263
261
  def load_file!(filespec, opts = {})
264
- parse!(File.read(filespec), opts)
262
+ parse!(File.read(filespec, encoding: Encoding::UTF_8), opts)
265
263
  end
266
264
 
267
265
  # :call-seq:
@@ -302,11 +300,10 @@ module JSON
302
300
  #
303
301
  def generate(obj, opts = nil)
304
302
  if State === opts
305
- state = opts
303
+ opts.generate(obj)
306
304
  else
307
- state = State.new(opts)
305
+ State.generate(obj, opts, nil)
308
306
  end
309
- state.generate(obj)
310
307
  end
311
308
 
312
309
  # :stopdoc:
@@ -399,6 +396,20 @@ module JSON
399
396
  module_function :pretty_unparse
400
397
  # :startdoc:
401
398
 
399
+ class << self
400
+ # Sets or returns default options for the JSON.unsafe_load method.
401
+ # Initially:
402
+ # opts = JSON.load_default_options
403
+ # opts # => {:max_nesting=>false, :allow_nan=>true, :allow_blank=>true, :create_additions=>true}
404
+ attr_accessor :unsafe_load_default_options
405
+ end
406
+ self.unsafe_load_default_options = {
407
+ :max_nesting => false,
408
+ :allow_nan => true,
409
+ :allow_blank => true,
410
+ :create_additions => true,
411
+ }
412
+
402
413
  class << self
403
414
  # Sets or returns default options for the JSON.load method.
404
415
  # Initially:
@@ -407,17 +418,179 @@ module JSON
407
418
  attr_accessor :load_default_options
408
419
  end
409
420
  self.load_default_options = {
410
- :max_nesting => false,
411
421
  :allow_nan => true,
412
422
  :allow_blank => true,
413
- :create_additions => true,
423
+ :create_additions => nil,
414
424
  }
425
+ # :call-seq:
426
+ # JSON.unsafe_load(source, proc = nil, options = {}) -> object
427
+ #
428
+ # Returns the Ruby objects created by parsing the given +source+.
429
+ #
430
+ # BEWARE: This method is meant to serialise data from trusted user input,
431
+ # like from your own database server or clients under your control, it could
432
+ # be dangerous to allow untrusted users to pass JSON sources into it.
433
+ #
434
+ # - Argument +source+ must be, or be convertible to, a \String:
435
+ # - If +source+ responds to instance method +to_str+,
436
+ # <tt>source.to_str</tt> becomes the source.
437
+ # - If +source+ responds to instance method +to_io+,
438
+ # <tt>source.to_io.read</tt> becomes the source.
439
+ # - If +source+ responds to instance method +read+,
440
+ # <tt>source.read</tt> becomes the source.
441
+ # - If both of the following are true, source becomes the \String <tt>'null'</tt>:
442
+ # - Option +allow_blank+ specifies a truthy value.
443
+ # - The source, as defined above, is +nil+ or the empty \String <tt>''</tt>.
444
+ # - Otherwise, +source+ remains the source.
445
+ # - Argument +proc+, if given, must be a \Proc that accepts one argument.
446
+ # It will be called recursively with each result (depth-first order).
447
+ # See details below.
448
+ # - Argument +opts+, if given, contains a \Hash of options for the parsing.
449
+ # See {Parsing Options}[#module-JSON-label-Parsing+Options].
450
+ # The default options can be changed via method JSON.unsafe_load_default_options=.
451
+ #
452
+ # ---
453
+ #
454
+ # When no +proc+ is given, modifies +source+ as above and returns the result of
455
+ # <tt>parse(source, opts)</tt>; see #parse.
456
+ #
457
+ # Source for following examples:
458
+ # source = <<~JSON
459
+ # {
460
+ # "name": "Dave",
461
+ # "age" :40,
462
+ # "hats": [
463
+ # "Cattleman's",
464
+ # "Panama",
465
+ # "Tophat"
466
+ # ]
467
+ # }
468
+ # JSON
469
+ #
470
+ # Load a \String:
471
+ # ruby = JSON.unsafe_load(source)
472
+ # ruby # => {"name"=>"Dave", "age"=>40, "hats"=>["Cattleman's", "Panama", "Tophat"]}
473
+ #
474
+ # Load an \IO object:
475
+ # require 'stringio'
476
+ # object = JSON.unsafe_load(StringIO.new(source))
477
+ # object # => {"name"=>"Dave", "age"=>40, "hats"=>["Cattleman's", "Panama", "Tophat"]}
478
+ #
479
+ # Load a \File object:
480
+ # path = 't.json'
481
+ # File.write(path, source)
482
+ # File.open(path) do |file|
483
+ # JSON.unsafe_load(file)
484
+ # end # => {"name"=>"Dave", "age"=>40, "hats"=>["Cattleman's", "Panama", "Tophat"]}
485
+ #
486
+ # ---
487
+ #
488
+ # When +proc+ is given:
489
+ # - Modifies +source+ as above.
490
+ # - Gets the +result+ from calling <tt>parse(source, opts)</tt>.
491
+ # - Recursively calls <tt>proc(result)</tt>.
492
+ # - Returns the final result.
493
+ #
494
+ # Example:
495
+ # require 'json'
496
+ #
497
+ # # Some classes for the example.
498
+ # class Base
499
+ # def initialize(attributes)
500
+ # @attributes = attributes
501
+ # end
502
+ # end
503
+ # class User < Base; end
504
+ # class Account < Base; end
505
+ # class Admin < Base; end
506
+ # # The JSON source.
507
+ # json = <<-EOF
508
+ # {
509
+ # "users": [
510
+ # {"type": "User", "username": "jane", "email": "jane@example.com"},
511
+ # {"type": "User", "username": "john", "email": "john@example.com"}
512
+ # ],
513
+ # "accounts": [
514
+ # {"account": {"type": "Account", "paid": true, "account_id": "1234"}},
515
+ # {"account": {"type": "Account", "paid": false, "account_id": "1235"}}
516
+ # ],
517
+ # "admins": {"type": "Admin", "password": "0wn3d"}
518
+ # }
519
+ # EOF
520
+ # # Deserializer method.
521
+ # def deserialize_obj(obj, safe_types = %w(User Account Admin))
522
+ # type = obj.is_a?(Hash) && obj["type"]
523
+ # safe_types.include?(type) ? Object.const_get(type).new(obj) : obj
524
+ # end
525
+ # # Call to JSON.unsafe_load
526
+ # ruby = JSON.unsafe_load(json, proc {|obj|
527
+ # case obj
528
+ # when Hash
529
+ # obj.each {|k, v| obj[k] = deserialize_obj v }
530
+ # when Array
531
+ # obj.map! {|v| deserialize_obj v }
532
+ # end
533
+ # })
534
+ # pp ruby
535
+ # Output:
536
+ # {"users"=>
537
+ # [#<User:0x00000000064c4c98
538
+ # @attributes=
539
+ # {"type"=>"User", "username"=>"jane", "email"=>"jane@example.com"}>,
540
+ # #<User:0x00000000064c4bd0
541
+ # @attributes=
542
+ # {"type"=>"User", "username"=>"john", "email"=>"john@example.com"}>],
543
+ # "accounts"=>
544
+ # [{"account"=>
545
+ # #<Account:0x00000000064c4928
546
+ # @attributes={"type"=>"Account", "paid"=>true, "account_id"=>"1234"}>},
547
+ # {"account"=>
548
+ # #<Account:0x00000000064c4680
549
+ # @attributes={"type"=>"Account", "paid"=>false, "account_id"=>"1235"}>}],
550
+ # "admins"=>
551
+ # #<Admin:0x00000000064c41f8
552
+ # @attributes={"type"=>"Admin", "password"=>"0wn3d"}>}
553
+ #
554
+ def unsafe_load(source, proc = nil, options = nil)
555
+ opts = if options.nil?
556
+ unsafe_load_default_options
557
+ else
558
+ unsafe_load_default_options.merge(options)
559
+ end
560
+
561
+ unless source.is_a?(String)
562
+ if source.respond_to? :to_str
563
+ source = source.to_str
564
+ elsif source.respond_to? :to_io
565
+ source = source.to_io.read
566
+ elsif source.respond_to?(:read)
567
+ source = source.read
568
+ end
569
+ end
570
+
571
+ if opts[:allow_blank] && (source.nil? || source.empty?)
572
+ source = 'null'
573
+ end
574
+ result = parse(source, opts)
575
+ recurse_proc(result, &proc) if proc
576
+ result
577
+ end
415
578
 
416
579
  # :call-seq:
417
580
  # JSON.load(source, proc = nil, options = {}) -> object
418
581
  #
419
582
  # Returns the Ruby objects created by parsing the given +source+.
420
583
  #
584
+ # BEWARE: This method is meant to serialise data from trusted user input,
585
+ # like from your own database server or clients under your control, it could
586
+ # be dangerous to allow untrusted users to pass JSON sources into it.
587
+ # If you must use it, use JSON.unsafe_load instead to make it clear.
588
+ #
589
+ # Since JSON version 2.8.0, `load` emits a deprecation warning when a
590
+ # non native type is deserialized, without `create_additions` being explicitly
591
+ # enabled, and in JSON version 3.0, `load` will have `create_additions` disabled
592
+ # by default.
593
+ #
421
594
  # - Argument +source+ must be, or be convertible to, a \String:
422
595
  # - If +source+ responds to instance method +to_str+,
423
596
  # <tt>source.to_str</tt> becomes the source.
@@ -432,9 +605,6 @@ module JSON
432
605
  # - Argument +proc+, if given, must be a \Proc that accepts one argument.
433
606
  # It will be called recursively with each result (depth-first order).
434
607
  # See details below.
435
- # BEWARE: This method is meant to serialise data from trusted user input,
436
- # like from your own database server or clients under your control, it could
437
- # be dangerous to allow untrusted users to pass JSON sources into it.
438
608
  # - Argument +opts+, if given, contains a \Hash of options for the parsing.
439
609
  # See {Parsing Options}[#module-JSON-label-Parsing+Options].
440
610
  # The default options can be changed via method JSON.load_default_options=.
@@ -445,17 +615,17 @@ module JSON
445
615
  # <tt>parse(source, opts)</tt>; see #parse.
446
616
  #
447
617
  # Source for following examples:
448
- # source = <<-EOT
449
- # {
450
- # "name": "Dave",
451
- # "age" :40,
452
- # "hats": [
453
- # "Cattleman's",
454
- # "Panama",
455
- # "Tophat"
456
- # ]
457
- # }
458
- # EOT
618
+ # source = <<~JSON
619
+ # {
620
+ # "name": "Dave",
621
+ # "age" :40,
622
+ # "hats": [
623
+ # "Cattleman's",
624
+ # "Panama",
625
+ # "Tophat"
626
+ # ]
627
+ # }
628
+ # JSON
459
629
  #
460
630
  # Load a \String:
461
631
  # ruby = JSON.load(source)
@@ -647,18 +817,15 @@ module JSON
647
817
  opts = opts.merge(:max_nesting => limit) if limit
648
818
  opts = merge_dump_options(opts, **kwargs) if kwargs
649
819
 
650
- result = begin
651
- generate(obj, opts)
820
+ begin
821
+ if State === opts
822
+ opts.generate(obj, anIO)
823
+ else
824
+ State.generate(obj, opts, anIO)
825
+ end
652
826
  rescue JSON::NestingError
653
827
  raise ArgumentError, "exceed depth limit"
654
828
  end
655
-
656
- if anIO.nil?
657
- result
658
- else
659
- anIO.write result
660
- anIO
661
- end
662
829
  end
663
830
 
664
831
  # Encodes string using String.encode.
@@ -42,41 +42,22 @@ module JSON
42
42
  raise TypeError, "can't convert #{opts.class} into Hash"
43
43
  end
44
44
  end
45
-
46
- opts.each do |key, value|
47
- case key
48
- when :indent
49
- self.indent = value
50
- when :space
51
- self.space = value
52
- when :space_before
53
- self.space_before = value
54
- when :array_nl
55
- self.array_nl = value
56
- when :object_nl
57
- self.object_nl = value
58
- when :max_nesting
59
- self.max_nesting = value || 0
60
- when :depth
61
- self.depth = value
62
- when :buffer_initial_length
63
- self.buffer_initial_length = value
64
- when :allow_nan
65
- self.allow_nan = value
66
- when :ascii_only
67
- self.ascii_only = value
68
- when :script_safe, :escape_slash
69
- self.script_safe = value
70
- when :strict
71
- self.strict = value
72
- end
73
- end
74
-
75
- self
45
+ _configure(opts)
76
46
  end
77
47
 
78
48
  alias_method :merge, :configure
79
49
 
50
+ # call-seq:
51
+ # generate(obj) -> String
52
+ # generate(obj, anIO) -> anIO
53
+ #
54
+ # Generates a valid JSON document from object +obj+ and returns the
55
+ # result. If no valid JSON document can be created this method raises a
56
+ # GeneratorError exception.
57
+ def generate(obj, io = nil)
58
+ _generate(obj, io)
59
+ end
60
+
80
61
  # call-seq: to_h
81
62
  #
82
63
  # Returns the configuration instance variables as a hash, that can be
data/lib/json/ext.rb CHANGED
@@ -8,17 +8,12 @@ module JSON
8
8
  module Ext
9
9
  if RUBY_ENGINE == 'truffleruby'
10
10
  require 'json/ext/parser'
11
- require 'json/pure'
12
- $DEBUG and warn "Using Ext extension for JSON parser and Pure library for JSON generator."
11
+ require 'json/truffle_ruby/generator'
13
12
  JSON.parser = Parser
14
- JSON.generator = JSON::Pure::Generator
13
+ JSON.generator = ::JSON::TruffleRuby::Generator
15
14
  else
16
15
  require 'json/ext/parser'
17
16
  require 'json/ext/generator'
18
- unless RUBY_ENGINE == 'jruby'
19
- require 'json/ext/generator/state'
20
- end
21
- $DEBUG and warn "Using Ext extension for JSON."
22
17
  JSON.parser = Parser
23
18
  JSON.generator = Generator
24
19
  end