json 2.7.3 → 2.12.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.
data/lib/json/common.rb CHANGED
@@ -1,11 +1,115 @@
1
- #frozen_string_literal: true
1
+ # frozen_string_literal: true
2
+
2
3
  require 'json/version'
3
4
 
4
5
  module JSON
5
6
  autoload :GenericObject, 'json/generic_object'
6
7
 
7
- NOT_SET = Object.new.freeze
8
- private_constant :NOT_SET
8
+ module ParserOptions # :nodoc:
9
+ class << self
10
+ def prepare(opts)
11
+ if opts[:object_class] || opts[:array_class]
12
+ opts = opts.dup
13
+ on_load = opts[:on_load]
14
+
15
+ on_load = object_class_proc(opts[:object_class], on_load) if opts[:object_class]
16
+ on_load = array_class_proc(opts[:array_class], on_load) if opts[:array_class]
17
+ opts[:on_load] = on_load
18
+ end
19
+
20
+ if opts.fetch(:create_additions, false) != false
21
+ opts = create_additions_proc(opts)
22
+ end
23
+
24
+ opts
25
+ end
26
+
27
+ private
28
+
29
+ def object_class_proc(object_class, on_load)
30
+ ->(obj) do
31
+ if Hash === obj
32
+ object = object_class.new
33
+ obj.each { |k, v| object[k] = v }
34
+ obj = object
35
+ end
36
+ on_load.nil? ? obj : on_load.call(obj)
37
+ end
38
+ end
39
+
40
+ def array_class_proc(array_class, on_load)
41
+ ->(obj) do
42
+ if Array === obj
43
+ array = array_class.new
44
+ obj.each { |v| array << v }
45
+ obj = array
46
+ end
47
+ on_load.nil? ? obj : on_load.call(obj)
48
+ end
49
+ end
50
+
51
+ # TODO: exctract :create_additions support to another gem for version 3.0
52
+ def create_additions_proc(opts)
53
+ if opts[:symbolize_names]
54
+ raise ArgumentError, "options :symbolize_names and :create_additions cannot be used in conjunction"
55
+ end
56
+
57
+ opts = opts.dup
58
+ create_additions = opts.fetch(:create_additions, false)
59
+ on_load = opts[:on_load]
60
+ object_class = opts[:object_class] || Hash
61
+
62
+ opts[:on_load] = ->(object) do
63
+ case object
64
+ when String
65
+ opts[:match_string]&.each do |pattern, klass|
66
+ if match = pattern.match(object)
67
+ create_additions_warning if create_additions.nil?
68
+ object = klass.json_create(object)
69
+ break
70
+ end
71
+ end
72
+ when object_class
73
+ if opts[:create_additions] != false
74
+ if class_name = object[JSON.create_id]
75
+ klass = JSON.deep_const_get(class_name)
76
+ if (klass.respond_to?(:json_creatable?) && klass.json_creatable?) || klass.respond_to?(:json_create)
77
+ create_additions_warning if create_additions.nil?
78
+ object = klass.json_create(object)
79
+ end
80
+ end
81
+ end
82
+ end
83
+
84
+ on_load.nil? ? object : on_load.call(object)
85
+ end
86
+
87
+ opts
88
+ end
89
+
90
+ GEM_ROOT = File.expand_path("../../../", __FILE__) + "/"
91
+ def create_additions_warning
92
+ message = "JSON.load implicit support for `create_additions: true` is deprecated " \
93
+ "and will be removed in 3.0, use JSON.unsafe_load or explicitly " \
94
+ "pass `create_additions: true`"
95
+
96
+ uplevel = 4
97
+ caller_locations(uplevel, 10).each do |frame|
98
+ if frame.path.nil? || frame.path.start_with?(GEM_ROOT) || frame.path.end_with?("/truffle/cext_ruby.rb", ".c")
99
+ uplevel += 1
100
+ else
101
+ break
102
+ end
103
+ end
104
+
105
+ if RUBY_VERSION >= "3.0"
106
+ warn(message, uplevel: uplevel - 1, category: :deprecated)
107
+ else
108
+ warn(message, uplevel: uplevel - 1)
109
+ end
110
+ end
111
+ end
112
+ end
9
113
 
10
114
  class << self
11
115
  # :call-seq:
@@ -19,22 +123,20 @@ module JSON
19
123
  # Otherwise, calls JSON.generate with +object+ and +opts+ (see method #generate):
20
124
  # ruby = [0, 1, nil]
21
125
  # JSON[ruby] # => '[0,1,null]'
22
- def [](object, opts = {})
126
+ def [](object, opts = nil)
23
127
  if object.is_a?(String)
24
128
  return JSON.parse(object, opts)
25
129
  elsif object.respond_to?(:to_str)
26
130
  str = object.to_str
27
131
  if str.is_a?(String)
28
- return JSON.parse(object.to_str, opts)
132
+ return JSON.parse(str, opts)
29
133
  end
30
134
  end
31
135
 
32
136
  JSON.generate(object, opts)
33
137
  end
34
138
 
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
139
+ # Returns the JSON parser class that is used by JSON.
38
140
  attr_reader :parser
39
141
 
40
142
  # Set the JSON parser class _parser_ to be used by JSON.
@@ -49,18 +151,9 @@ module JSON
49
151
  # level (absolute namespace path?). If there doesn't exist a constant at
50
152
  # the given path, an ArgumentError is raised.
51
153
  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
154
+ Object.const_get(path)
155
+ rescue NameError => e
156
+ raise ArgumentError, "can't get const #{path}: #{e}"
64
157
  end
65
158
 
66
159
  # Set the module _generator_ to be used by JSON.
@@ -69,7 +162,7 @@ module JSON
69
162
  @generator = generator
70
163
  generator_methods = generator::GeneratorMethods
71
164
  for const in generator_methods.constants
72
- klass = deep_const_get(const)
165
+ klass = const_get(const)
73
166
  modul = generator_methods.const_get(const)
74
167
  klass.class_eval do
75
168
  instance_methods(false).each do |m|
@@ -80,41 +173,38 @@ module JSON
80
173
  end
81
174
  self.state = generator::State
82
175
  const_set :State, self.state
83
- const_set :SAFE_STATE_PROTOTYPE, State.new # for JRuby
84
- const_set :FAST_STATE_PROTOTYPE, create_fast_state
85
- const_set :PRETTY_STATE_PROTOTYPE, create_pretty_state
86
176
  ensure
87
177
  $VERBOSE = old
88
178
  end
89
179
 
90
- def create_fast_state
91
- State.new(
92
- :indent => '',
93
- :space => '',
94
- :object_nl => "",
95
- :array_nl => "",
96
- :max_nesting => false
97
- )
98
- end
99
-
100
- def create_pretty_state
101
- State.new(
102
- :indent => ' ',
103
- :space => ' ',
104
- :object_nl => "\n",
105
- :array_nl => "\n"
106
- )
107
- end
108
-
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
180
+ # Returns the JSON generator module that is used by JSON.
112
181
  attr_reader :generator
113
182
 
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
183
+ # Sets or Returns the JSON generator state class that is used by JSON.
117
184
  attr_accessor :state
185
+
186
+ private
187
+
188
+ def deprecated_singleton_attr_accessor(*attrs)
189
+ args = RUBY_VERSION >= "3.0" ? ", category: :deprecated" : ""
190
+ attrs.each do |attr|
191
+ singleton_class.class_eval <<~RUBY
192
+ def #{attr}
193
+ warn "JSON.#{attr} is deprecated and will be removed in json 3.0.0", uplevel: 1 #{args}
194
+ @#{attr}
195
+ end
196
+
197
+ def #{attr}=(val)
198
+ warn "JSON.#{attr}= is deprecated and will be removed in json 3.0.0", uplevel: 1 #{args}
199
+ @#{attr} = val
200
+ end
201
+
202
+ def _#{attr}
203
+ @#{attr}
204
+ end
205
+ RUBY
206
+ end
207
+ end
118
208
  end
119
209
 
120
210
  # Sets create identifier, which is used to decide if the _json_create_
@@ -130,40 +220,68 @@ module JSON
130
220
  Thread.current[:"JSON.create_id"] || 'json_class'
131
221
  end
132
222
 
133
- NaN = 0.0/0
223
+ NaN = Float::NAN
134
224
 
135
- Infinity = 1.0/0
225
+ Infinity = Float::INFINITY
136
226
 
137
227
  MinusInfinity = -Infinity
138
228
 
139
229
  # The base exception for JSON errors.
140
- class JSONError < StandardError
141
- def self.wrap(exception)
142
- obj = new("Wrapped(#{exception.class}): #{exception.message.inspect}")
143
- obj.set_backtrace exception.backtrace
144
- obj
145
- end
146
- end
230
+ class JSONError < StandardError; end
147
231
 
148
232
  # This exception is raised if a parser error occurs.
149
- class ParserError < JSONError; end
233
+ class ParserError < JSONError
234
+ attr_reader :line, :column
235
+ end
150
236
 
151
237
  # This exception is raised if the nesting of parsed data structures is too
152
238
  # deep.
153
239
  class NestingError < ParserError; end
154
240
 
155
- # :stopdoc:
156
- class CircularDatastructure < NestingError; end
157
- # :startdoc:
158
-
159
241
  # This exception is raised if a generator or unparser error occurs.
160
- class GeneratorError < JSONError; end
161
- # For backwards compatibility
162
- UnparserError = GeneratorError # :nodoc:
242
+ class GeneratorError < JSONError
243
+ attr_reader :invalid_object
244
+
245
+ def initialize(message, invalid_object = nil)
246
+ super(message)
247
+ @invalid_object = invalid_object
248
+ end
163
249
 
164
- # This exception is raised if the required unicode support is missing on the
165
- # system. Usually this means that the iconv library is not installed.
166
- class MissingUnicodeSupport < JSONError; end
250
+ def detailed_message(...)
251
+ # Exception#detailed_message doesn't exist until Ruby 3.2
252
+ super_message = defined?(super) ? super : message
253
+
254
+ if @invalid_object.nil?
255
+ super_message
256
+ else
257
+ "#{super_message}\nInvalid object: #{@invalid_object.inspect}"
258
+ end
259
+ end
260
+ end
261
+
262
+ # Fragment of JSON document that is to be included as is:
263
+ # fragment = JSON::Fragment.new("[1, 2, 3]")
264
+ # JSON.generate({ count: 3, items: fragments })
265
+ #
266
+ # This allows to easily assemble multiple JSON fragments that have
267
+ # been persisted somewhere without having to parse them nor resorting
268
+ # to string interpolation.
269
+ #
270
+ # Note: no validation is performed on the provided string. It is the
271
+ # responsability of the caller to ensure the string contains valid JSON.
272
+ Fragment = Struct.new(:json) do
273
+ def initialize(json)
274
+ unless string = String.try_convert(json)
275
+ raise TypeError, " no implicit conversion of #{json.class} into String"
276
+ end
277
+
278
+ super(string)
279
+ end
280
+
281
+ def to_json(state = nil, *)
282
+ json
283
+ end
284
+ end
167
285
 
168
286
  module_function
169
287
 
@@ -195,17 +313,17 @@ module JSON
195
313
  # {Parsing \JSON}[#module-JSON-label-Parsing+JSON].
196
314
  #
197
315
  # 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
316
+ # source = <<~JSON
317
+ # {
318
+ # "name": "Dave",
319
+ # "age" :40,
320
+ # "hats": [
321
+ # "Cattleman's",
322
+ # "Panama",
323
+ # "Tophat"
324
+ # ]
325
+ # }
326
+ # JSON
209
327
  # ruby = JSON.parse(source)
210
328
  # ruby # => {"name"=>"Dave", "age"=>40, "hats"=>["Cattleman's", "Panama", "Tophat"]}
211
329
  #
@@ -216,13 +334,16 @@ module JSON
216
334
  # JSON.parse('')
217
335
  #
218
336
  def parse(source, opts = nil)
219
- if opts.nil?
220
- Parser.new(source).parse
221
- else
222
- Parser.new(source, opts).parse
223
- end
337
+ opts = ParserOptions.prepare(opts) unless opts.nil?
338
+ Parser.parse(source, opts)
224
339
  end
225
340
 
341
+ PARSE_L_OPTIONS = {
342
+ max_nesting: false,
343
+ allow_nan: true,
344
+ }.freeze
345
+ private_constant :PARSE_L_OPTIONS
346
+
226
347
  # :call-seq:
227
348
  # JSON.parse!(source, opts) -> object
228
349
  #
@@ -234,12 +355,12 @@ module JSON
234
355
  # - Option +max_nesting+, if not provided, defaults to +false+,
235
356
  # which disables checking for nesting depth.
236
357
  # - Option +allow_nan+, if not provided, defaults to +true+.
237
- def parse!(source, opts = {})
238
- opts = {
239
- :max_nesting => false,
240
- :allow_nan => true
241
- }.merge(opts)
242
- Parser.new(source, **(opts||{})).parse
358
+ def parse!(source, opts = nil)
359
+ if opts.nil?
360
+ parse(source, PARSE_L_OPTIONS)
361
+ else
362
+ parse(source, PARSE_L_OPTIONS.merge(opts))
363
+ end
243
364
  end
244
365
 
245
366
  # :call-seq:
@@ -249,8 +370,8 @@ module JSON
249
370
  # parse(File.read(path), opts)
250
371
  #
251
372
  # See method #parse.
252
- def load_file(filespec, opts = {})
253
- parse(File.read(filespec), opts)
373
+ def load_file(filespec, opts = nil)
374
+ parse(File.read(filespec, encoding: Encoding::UTF_8), opts)
254
375
  end
255
376
 
256
377
  # :call-seq:
@@ -260,8 +381,8 @@ module JSON
260
381
  # JSON.parse!(File.read(path, opts))
261
382
  #
262
383
  # See method #parse!
263
- def load_file!(filespec, opts = {})
264
- parse!(File.read(filespec), opts)
384
+ def load_file!(filespec, opts = nil)
385
+ parse!(File.read(filespec, encoding: Encoding::UTF_8), opts)
265
386
  end
266
387
 
267
388
  # :call-seq:
@@ -302,20 +423,12 @@ module JSON
302
423
  #
303
424
  def generate(obj, opts = nil)
304
425
  if State === opts
305
- state = opts
426
+ opts.generate(obj)
306
427
  else
307
- state = State.new(opts)
428
+ State.generate(obj, opts, nil)
308
429
  end
309
- state.generate(obj)
310
430
  end
311
431
 
312
- # :stopdoc:
313
- # I want to deprecate these later, so I'll first be silent about them, and
314
- # later delete them.
315
- alias unparse generate
316
- module_function :unparse
317
- # :startdoc:
318
-
319
432
  # :call-seq:
320
433
  # JSON.fast_generate(obj, opts) -> new_string
321
434
  #
@@ -330,19 +443,21 @@ module JSON
330
443
  # # Raises SystemStackError (stack level too deep):
331
444
  # JSON.fast_generate(a)
332
445
  def fast_generate(obj, opts = nil)
333
- if State === opts
334
- state = opts
446
+ if RUBY_VERSION >= "3.0"
447
+ warn "JSON.fast_generate is deprecated and will be removed in json 3.0.0, just use JSON.generate", uplevel: 1, category: :deprecated
335
448
  else
336
- state = JSON.create_fast_state.configure(opts)
449
+ warn "JSON.fast_generate is deprecated and will be removed in json 3.0.0, just use JSON.generate", uplevel: 1
337
450
  end
338
- state.generate(obj)
451
+ generate(obj, opts)
339
452
  end
340
453
 
341
- # :stopdoc:
342
- # I want to deprecate these later, so I'll first be silent about them, and later delete them.
343
- alias fast_unparse fast_generate
344
- module_function :fast_unparse
345
- # :startdoc:
454
+ PRETTY_GENERATE_OPTIONS = {
455
+ indent: ' ',
456
+ space: ' ',
457
+ object_nl: "\n",
458
+ array_nl: "\n",
459
+ }.freeze
460
+ private_constant :PRETTY_GENERATE_OPTIONS
346
461
 
347
462
  # :call-seq:
348
463
  # JSON.pretty_generate(obj, opts = nil) -> new_string
@@ -375,49 +490,59 @@ module JSON
375
490
  # }
376
491
  #
377
492
  def pretty_generate(obj, opts = nil)
378
- if State === opts
379
- state, opts = opts, nil
380
- else
381
- state = JSON.create_pretty_state
382
- end
493
+ return state.generate(obj) if State === opts
494
+
495
+ options = PRETTY_GENERATE_OPTIONS
496
+
383
497
  if opts
384
- if opts.respond_to? :to_hash
385
- opts = opts.to_hash
386
- elsif opts.respond_to? :to_h
387
- opts = opts.to_h
388
- else
389
- raise TypeError, "can't convert #{opts.class} into Hash"
498
+ unless opts.is_a?(Hash)
499
+ if opts.respond_to? :to_hash
500
+ opts = opts.to_hash
501
+ elsif opts.respond_to? :to_h
502
+ opts = opts.to_h
503
+ else
504
+ raise TypeError, "can't convert #{opts.class} into Hash"
505
+ end
390
506
  end
391
- state.configure(opts)
507
+ options = options.merge(opts)
392
508
  end
393
- state.generate(obj)
509
+
510
+ State.generate(obj, options, nil)
394
511
  end
395
512
 
396
- # :stopdoc:
397
- # I want to deprecate these later, so I'll first be silent about them, and later delete them.
398
- alias pretty_unparse pretty_generate
399
- module_function :pretty_unparse
400
- # :startdoc:
513
+ # Sets or returns default options for the JSON.unsafe_load method.
514
+ # Initially:
515
+ # opts = JSON.load_default_options
516
+ # opts # => {:max_nesting=>false, :allow_nan=>true, :allow_blank=>true, :create_additions=>true}
517
+ deprecated_singleton_attr_accessor :unsafe_load_default_options
401
518
 
402
- class << self
403
- # Sets or returns default options for the JSON.load method.
404
- # Initially:
405
- # opts = JSON.load_default_options
406
- # opts # => {:max_nesting=>false, :allow_nan=>true, :allow_blank=>true, :create_additions=>true}
407
- attr_accessor :load_default_options
408
- end
409
- self.load_default_options = {
519
+ @unsafe_load_default_options = {
410
520
  :max_nesting => false,
411
521
  :allow_nan => true,
412
522
  :allow_blank => true,
413
523
  :create_additions => true,
414
524
  }
415
525
 
526
+ # Sets or returns default options for the JSON.load method.
527
+ # Initially:
528
+ # opts = JSON.load_default_options
529
+ # opts # => {:max_nesting=>false, :allow_nan=>true, :allow_blank=>true, :create_additions=>true}
530
+ deprecated_singleton_attr_accessor :load_default_options
531
+
532
+ @load_default_options = {
533
+ :allow_nan => true,
534
+ :allow_blank => true,
535
+ :create_additions => nil,
536
+ }
416
537
  # :call-seq:
417
- # JSON.load(source, proc = nil, options = {}) -> object
538
+ # JSON.unsafe_load(source, proc = nil, options = {}) -> object
418
539
  #
419
540
  # Returns the Ruby objects created by parsing the given +source+.
420
541
  #
542
+ # BEWARE: This method is meant to serialise data from trusted user input,
543
+ # like from your own database server or clients under your control, it could
544
+ # be dangerous to allow untrusted users to pass JSON sources into it.
545
+ #
421
546
  # - Argument +source+ must be, or be convertible to, a \String:
422
547
  # - If +source+ responds to instance method +to_str+,
423
548
  # <tt>source.to_str</tt> becomes the source.
@@ -432,12 +557,9 @@ module JSON
432
557
  # - Argument +proc+, if given, must be a \Proc that accepts one argument.
433
558
  # It will be called recursively with each result (depth-first order).
434
559
  # 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
560
  # - Argument +opts+, if given, contains a \Hash of options for the parsing.
439
561
  # See {Parsing Options}[#module-JSON-label-Parsing+Options].
440
- # The default options can be changed via method JSON.load_default_options=.
562
+ # The default options can be changed via method JSON.unsafe_load_default_options=.
441
563
  #
442
564
  # ---
443
565
  #
@@ -445,17 +567,177 @@ module JSON
445
567
  # <tt>parse(source, opts)</tt>; see #parse.
446
568
  #
447
569
  # Source for following examples:
448
- # source = <<-EOT
570
+ # source = <<~JSON
571
+ # {
572
+ # "name": "Dave",
573
+ # "age" :40,
574
+ # "hats": [
575
+ # "Cattleman's",
576
+ # "Panama",
577
+ # "Tophat"
578
+ # ]
579
+ # }
580
+ # JSON
581
+ #
582
+ # Load a \String:
583
+ # ruby = JSON.unsafe_load(source)
584
+ # ruby # => {"name"=>"Dave", "age"=>40, "hats"=>["Cattleman's", "Panama", "Tophat"]}
585
+ #
586
+ # Load an \IO object:
587
+ # require 'stringio'
588
+ # object = JSON.unsafe_load(StringIO.new(source))
589
+ # object # => {"name"=>"Dave", "age"=>40, "hats"=>["Cattleman's", "Panama", "Tophat"]}
590
+ #
591
+ # Load a \File object:
592
+ # path = 't.json'
593
+ # File.write(path, source)
594
+ # File.open(path) do |file|
595
+ # JSON.unsafe_load(file)
596
+ # end # => {"name"=>"Dave", "age"=>40, "hats"=>["Cattleman's", "Panama", "Tophat"]}
597
+ #
598
+ # ---
599
+ #
600
+ # When +proc+ is given:
601
+ # - Modifies +source+ as above.
602
+ # - Gets the +result+ from calling <tt>parse(source, opts)</tt>.
603
+ # - Recursively calls <tt>proc(result)</tt>.
604
+ # - Returns the final result.
605
+ #
606
+ # Example:
607
+ # require 'json'
608
+ #
609
+ # # Some classes for the example.
610
+ # class Base
611
+ # def initialize(attributes)
612
+ # @attributes = attributes
613
+ # end
614
+ # end
615
+ # class User < Base; end
616
+ # class Account < Base; end
617
+ # class Admin < Base; end
618
+ # # The JSON source.
619
+ # json = <<-EOF
449
620
  # {
450
- # "name": "Dave",
451
- # "age" :40,
452
- # "hats": [
453
- # "Cattleman's",
454
- # "Panama",
455
- # "Tophat"
456
- # ]
621
+ # "users": [
622
+ # {"type": "User", "username": "jane", "email": "jane@example.com"},
623
+ # {"type": "User", "username": "john", "email": "john@example.com"}
624
+ # ],
625
+ # "accounts": [
626
+ # {"account": {"type": "Account", "paid": true, "account_id": "1234"}},
627
+ # {"account": {"type": "Account", "paid": false, "account_id": "1235"}}
628
+ # ],
629
+ # "admins": {"type": "Admin", "password": "0wn3d"}
457
630
  # }
458
- # EOT
631
+ # EOF
632
+ # # Deserializer method.
633
+ # def deserialize_obj(obj, safe_types = %w(User Account Admin))
634
+ # type = obj.is_a?(Hash) && obj["type"]
635
+ # safe_types.include?(type) ? Object.const_get(type).new(obj) : obj
636
+ # end
637
+ # # Call to JSON.unsafe_load
638
+ # ruby = JSON.unsafe_load(json, proc {|obj|
639
+ # case obj
640
+ # when Hash
641
+ # obj.each {|k, v| obj[k] = deserialize_obj v }
642
+ # when Array
643
+ # obj.map! {|v| deserialize_obj v }
644
+ # end
645
+ # })
646
+ # pp ruby
647
+ # Output:
648
+ # {"users"=>
649
+ # [#<User:0x00000000064c4c98
650
+ # @attributes=
651
+ # {"type"=>"User", "username"=>"jane", "email"=>"jane@example.com"}>,
652
+ # #<User:0x00000000064c4bd0
653
+ # @attributes=
654
+ # {"type"=>"User", "username"=>"john", "email"=>"john@example.com"}>],
655
+ # "accounts"=>
656
+ # [{"account"=>
657
+ # #<Account:0x00000000064c4928
658
+ # @attributes={"type"=>"Account", "paid"=>true, "account_id"=>"1234"}>},
659
+ # {"account"=>
660
+ # #<Account:0x00000000064c4680
661
+ # @attributes={"type"=>"Account", "paid"=>false, "account_id"=>"1235"}>}],
662
+ # "admins"=>
663
+ # #<Admin:0x00000000064c41f8
664
+ # @attributes={"type"=>"Admin", "password"=>"0wn3d"}>}
665
+ #
666
+ def unsafe_load(source, proc = nil, options = nil)
667
+ opts = if options.nil?
668
+ _unsafe_load_default_options
669
+ else
670
+ _unsafe_load_default_options.merge(options)
671
+ end
672
+
673
+ unless source.is_a?(String)
674
+ if source.respond_to? :to_str
675
+ source = source.to_str
676
+ elsif source.respond_to? :to_io
677
+ source = source.to_io.read
678
+ elsif source.respond_to?(:read)
679
+ source = source.read
680
+ end
681
+ end
682
+
683
+ if opts[:allow_blank] && (source.nil? || source.empty?)
684
+ source = 'null'
685
+ end
686
+ result = parse(source, opts)
687
+ recurse_proc(result, &proc) if proc
688
+ result
689
+ end
690
+
691
+ # :call-seq:
692
+ # JSON.load(source, proc = nil, options = {}) -> object
693
+ #
694
+ # Returns the Ruby objects created by parsing the given +source+.
695
+ #
696
+ # BEWARE: This method is meant to serialise data from trusted user input,
697
+ # like from your own database server or clients under your control, it could
698
+ # be dangerous to allow untrusted users to pass JSON sources into it.
699
+ # If you must use it, use JSON.unsafe_load instead to make it clear.
700
+ #
701
+ # Since JSON version 2.8.0, `load` emits a deprecation warning when a
702
+ # non native type is deserialized, without `create_additions` being explicitly
703
+ # enabled, and in JSON version 3.0, `load` will have `create_additions` disabled
704
+ # by default.
705
+ #
706
+ # - Argument +source+ must be, or be convertible to, a \String:
707
+ # - If +source+ responds to instance method +to_str+,
708
+ # <tt>source.to_str</tt> becomes the source.
709
+ # - If +source+ responds to instance method +to_io+,
710
+ # <tt>source.to_io.read</tt> becomes the source.
711
+ # - If +source+ responds to instance method +read+,
712
+ # <tt>source.read</tt> becomes the source.
713
+ # - If both of the following are true, source becomes the \String <tt>'null'</tt>:
714
+ # - Option +allow_blank+ specifies a truthy value.
715
+ # - The source, as defined above, is +nil+ or the empty \String <tt>''</tt>.
716
+ # - Otherwise, +source+ remains the source.
717
+ # - Argument +proc+, if given, must be a \Proc that accepts one argument.
718
+ # It will be called recursively with each result (depth-first order).
719
+ # See details below.
720
+ # - Argument +opts+, if given, contains a \Hash of options for the parsing.
721
+ # See {Parsing Options}[#module-JSON-label-Parsing+Options].
722
+ # The default options can be changed via method JSON.load_default_options=.
723
+ #
724
+ # ---
725
+ #
726
+ # When no +proc+ is given, modifies +source+ as above and returns the result of
727
+ # <tt>parse(source, opts)</tt>; see #parse.
728
+ #
729
+ # Source for following examples:
730
+ # source = <<~JSON
731
+ # {
732
+ # "name": "Dave",
733
+ # "age" :40,
734
+ # "hats": [
735
+ # "Cattleman's",
736
+ # "Panama",
737
+ # "Tophat"
738
+ # ]
739
+ # }
740
+ # JSON
459
741
  #
460
742
  # Load a \String:
461
743
  # ruby = JSON.load(source)
@@ -543,9 +825,9 @@ module JSON
543
825
  #
544
826
  def load(source, proc = nil, options = nil)
545
827
  opts = if options.nil?
546
- load_default_options
828
+ _load_default_options
547
829
  else
548
- load_default_options.merge(options)
830
+ _load_default_options.merge(options)
549
831
  end
550
832
 
551
833
  unless source.is_a?(String)
@@ -561,36 +843,21 @@ module JSON
561
843
  if opts[:allow_blank] && (source.nil? || source.empty?)
562
844
  source = 'null'
563
845
  end
564
- result = parse(source, opts)
565
- recurse_proc(result, &proc) if proc
566
- result
567
- end
568
846
 
569
- # Recursively calls passed _Proc_ if the parsed data structure is an _Array_ or _Hash_
570
- def recurse_proc(result, &proc) # :nodoc:
571
- case result
572
- when Array
573
- result.each { |x| recurse_proc x, &proc }
574
- proc.call result
575
- when Hash
576
- result.each { |x, y| recurse_proc x, &proc; recurse_proc y, &proc }
577
- proc.call result
578
- else
579
- proc.call result
847
+ if proc
848
+ opts = opts.dup
849
+ opts[:on_load] = proc.to_proc
580
850
  end
581
- end
582
-
583
- alias restore load
584
- module_function :restore
585
851
 
586
- class << self
587
- # Sets or returns the default options for the JSON.dump method.
588
- # Initially:
589
- # opts = JSON.dump_default_options
590
- # opts # => {:max_nesting=>false, :allow_nan=>true}
591
- attr_accessor :dump_default_options
852
+ parse(source, opts)
592
853
  end
593
- self.dump_default_options = {
854
+
855
+ # Sets or returns the default options for the JSON.dump method.
856
+ # Initially:
857
+ # opts = JSON.dump_default_options
858
+ # opts # => {:max_nesting=>false, :allow_nan=>true}
859
+ deprecated_singleton_attr_accessor :dump_default_options
860
+ @dump_default_options = {
594
861
  :max_nesting => false,
595
862
  :allow_nan => true,
596
863
  }
@@ -643,36 +910,152 @@ module JSON
643
910
  end
644
911
  end
645
912
 
646
- opts = JSON.dump_default_options
913
+ opts = JSON._dump_default_options
647
914
  opts = opts.merge(:max_nesting => limit) if limit
648
- opts = merge_dump_options(opts, **kwargs) if kwargs
915
+ opts = opts.merge(kwargs) if kwargs
649
916
 
650
- result = begin
651
- generate(obj, opts)
917
+ begin
918
+ State.generate(obj, opts, anIO)
652
919
  rescue JSON::NestingError
653
920
  raise ArgumentError, "exceed depth limit"
654
921
  end
922
+ end
655
923
 
656
- if anIO.nil?
657
- result
924
+ # :stopdoc:
925
+ # All these were meant to be deprecated circa 2009, but were just set as undocumented
926
+ # so usage still exist in the wild.
927
+ def unparse(...)
928
+ if RUBY_VERSION >= "3.0"
929
+ warn "JSON.unparse is deprecated and will be removed in json 3.0.0, just use JSON.generate", uplevel: 1, category: :deprecated
658
930
  else
659
- anIO.write result
660
- anIO
931
+ warn "JSON.unparse is deprecated and will be removed in json 3.0.0, just use JSON.generate", uplevel: 1
661
932
  end
933
+ generate(...)
662
934
  end
935
+ module_function :unparse
663
936
 
664
- # Encodes string using String.encode.
665
- def self.iconv(to, from, string)
666
- string.encode(to, from)
937
+ def fast_unparse(...)
938
+ if RUBY_VERSION >= "3.0"
939
+ warn "JSON.fast_unparse is deprecated and will be removed in json 3.0.0, just use JSON.generate", uplevel: 1, category: :deprecated
940
+ else
941
+ warn "JSON.fast_unparse is deprecated and will be removed in json 3.0.0, just use JSON.generate", uplevel: 1
942
+ end
943
+ generate(...)
667
944
  end
945
+ module_function :fast_unparse
668
946
 
669
- def merge_dump_options(opts, strict: NOT_SET)
670
- opts = opts.merge(strict: strict) if NOT_SET != strict
671
- opts
947
+ def pretty_unparse(...)
948
+ if RUBY_VERSION >= "3.0"
949
+ warn "JSON.pretty_unparse is deprecated and will be removed in json 3.0.0, just use JSON.pretty_generate", uplevel: 1, category: :deprecated
950
+ else
951
+ warn "JSON.pretty_unparse is deprecated and will be removed in json 3.0.0, just use JSON.pretty_generate", uplevel: 1
952
+ end
953
+ pretty_generate(...)
672
954
  end
955
+ module_function :fast_unparse
956
+
957
+ def restore(...)
958
+ if RUBY_VERSION >= "3.0"
959
+ warn "JSON.restore is deprecated and will be removed in json 3.0.0, just use JSON.load", uplevel: 1, category: :deprecated
960
+ else
961
+ warn "JSON.restore is deprecated and will be removed in json 3.0.0, just use JSON.load", uplevel: 1
962
+ end
963
+ load(...)
964
+ end
965
+ module_function :restore
673
966
 
674
967
  class << self
675
- private :merge_dump_options
968
+ private
969
+
970
+ def const_missing(const_name)
971
+ case const_name
972
+ when :PRETTY_STATE_PROTOTYPE
973
+ if RUBY_VERSION >= "3.0"
974
+ warn "JSON::PRETTY_STATE_PROTOTYPE is deprecated and will be removed in json 3.0.0, just use JSON.pretty_generate", uplevel: 1, category: :deprecated
975
+ else
976
+ warn "JSON::PRETTY_STATE_PROTOTYPE is deprecated and will be removed in json 3.0.0, just use JSON.pretty_generate", uplevel: 1
977
+ end
978
+ state.new(PRETTY_GENERATE_OPTIONS)
979
+ else
980
+ super
981
+ end
982
+ end
983
+ end
984
+ # :startdoc:
985
+
986
+ # JSON::Coder holds a parser and generator configuration.
987
+ #
988
+ # module MyApp
989
+ # JSONC_CODER = JSON::Coder.new(
990
+ # allow_trailing_comma: true
991
+ # )
992
+ # end
993
+ #
994
+ # MyApp::JSONC_CODER.load(document)
995
+ #
996
+ class Coder
997
+ # :call-seq:
998
+ # JSON.new(options = nil, &block)
999
+ #
1000
+ # Argument +options+, if given, contains a \Hash of options for both parsing and generating.
1001
+ # See {Parsing Options}[#module-JSON-label-Parsing+Options], and {Generating Options}[#module-JSON-label-Generating+Options].
1002
+ #
1003
+ # For generation, the <tt>strict: true</tt> option is always set. When a Ruby object with no native \JSON counterpart is
1004
+ # encoutered, the block provided to the initialize method is invoked, and must return a Ruby object that has a native
1005
+ # \JSON counterpart:
1006
+ #
1007
+ # module MyApp
1008
+ # API_JSON_CODER = JSON::Coder.new do |object|
1009
+ # case object
1010
+ # when Time
1011
+ # object.iso8601(3)
1012
+ # else
1013
+ # object # Unknown type, will raise
1014
+ # end
1015
+ # end
1016
+ # end
1017
+ #
1018
+ # puts MyApp::API_JSON_CODER.dump(Time.now.utc) # => "2025-01-21T08:41:44.286Z"
1019
+ #
1020
+ def initialize(options = nil, &as_json)
1021
+ if options.nil?
1022
+ options = { strict: true }
1023
+ else
1024
+ options = options.dup
1025
+ options[:strict] = true
1026
+ end
1027
+ options[:as_json] = as_json if as_json
1028
+
1029
+ @state = State.new(options).freeze
1030
+ @parser_config = Ext::Parser::Config.new(ParserOptions.prepare(options))
1031
+ end
1032
+
1033
+ # call-seq:
1034
+ # dump(object) -> String
1035
+ # dump(object, io) -> io
1036
+ #
1037
+ # Serialize the given object into a \JSON document.
1038
+ def dump(object, io = nil)
1039
+ @state.generate_new(object, io)
1040
+ end
1041
+ alias_method :generate, :dump
1042
+
1043
+ # call-seq:
1044
+ # load(string) -> Object
1045
+ #
1046
+ # Parse the given \JSON document and return an equivalent Ruby object.
1047
+ def load(source)
1048
+ @parser_config.parse(source)
1049
+ end
1050
+ alias_method :parse, :load
1051
+
1052
+ # call-seq:
1053
+ # load(path) -> Object
1054
+ #
1055
+ # Parse the given \JSON document and return an equivalent Ruby object.
1056
+ def load_file(path)
1057
+ load(File.read(path, encoding: Encoding::UTF_8))
1058
+ end
676
1059
  end
677
1060
  end
678
1061
 
@@ -682,6 +1065,12 @@ module ::Kernel
682
1065
  # Outputs _objs_ to STDOUT as JSON strings in the shortest form, that is in
683
1066
  # one line.
684
1067
  def j(*objs)
1068
+ if RUBY_VERSION >= "3.0"
1069
+ warn "Kernel#j is deprecated and will be removed in json 3.0.0", uplevel: 1, category: :deprecated
1070
+ else
1071
+ warn "Kernel#j is deprecated and will be removed in json 3.0.0", uplevel: 1
1072
+ end
1073
+
685
1074
  objs.each do |obj|
686
1075
  puts JSON::generate(obj, :allow_nan => true, :max_nesting => false)
687
1076
  end
@@ -691,6 +1080,12 @@ module ::Kernel
691
1080
  # Outputs _objs_ to STDOUT as JSON strings in a pretty format, with
692
1081
  # indentation and over many lines.
693
1082
  def jj(*objs)
1083
+ if RUBY_VERSION >= "3.0"
1084
+ warn "Kernel#jj is deprecated and will be removed in json 3.0.0", uplevel: 1, category: :deprecated
1085
+ else
1086
+ warn "Kernel#jj is deprecated and will be removed in json 3.0.0", uplevel: 1
1087
+ end
1088
+
694
1089
  objs.each do |obj|
695
1090
  puts JSON::pretty_generate(obj, :allow_nan => true, :max_nesting => false)
696
1091
  end
@@ -703,27 +1098,7 @@ module ::Kernel
703
1098
  #
704
1099
  # The _opts_ argument is passed through to generate/parse respectively. See
705
1100
  # generate and parse for their documentation.
706
- def JSON(object, *args)
707
- if object.is_a?(String)
708
- return JSON.parse(object, args.first)
709
- elsif object.respond_to?(:to_str)
710
- str = object.to_str
711
- if str.is_a?(String)
712
- return JSON.parse(object.to_str, args.first)
713
- end
714
- end
715
-
716
- JSON.generate(object, args.first)
717
- end
718
- end
719
-
720
- # Extends any Class to include _json_creatable?_ method.
721
- class ::Class
722
- # Returns true if this class can be used to create an instance
723
- # from a serialised JSON string. The class has to implement a class
724
- # method _json_create_ that expects a hash as first parameter. The hash
725
- # should include the required data.
726
- def json_creatable?
727
- respond_to?(:json_create)
1101
+ def JSON(object, opts = nil)
1102
+ JSON[object, opts]
728
1103
  end
729
1104
  end