json 2.6.3 → 2.11.3

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