json 2.6.3 → 2.13.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.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/BSDL +22 -0
  3. data/CHANGES.md +212 -17
  4. data/LEGAL +8 -0
  5. data/README.md +75 -219
  6. data/ext/json/ext/fbuffer/fbuffer.h +178 -95
  7. data/ext/json/ext/generator/extconf.rb +14 -2
  8. data/ext/json/ext/generator/generator.c +1336 -805
  9. data/ext/json/ext/parser/extconf.rb +8 -25
  10. data/ext/json/ext/parser/parser.c +1365 -3205
  11. data/ext/json/ext/simd/conf.rb +20 -0
  12. data/ext/json/ext/simd/simd.h +187 -0
  13. data/ext/json/ext/vendor/fpconv.c +479 -0
  14. data/ext/json/ext/vendor/jeaiii-ltoa.h +267 -0
  15. data/json.gemspec +48 -53
  16. data/lib/json/add/bigdecimal.rb +39 -10
  17. data/lib/json/add/complex.rb +29 -6
  18. data/lib/json/add/core.rb +1 -1
  19. data/lib/json/add/date.rb +27 -7
  20. data/lib/json/add/date_time.rb +26 -9
  21. data/lib/json/add/exception.rb +25 -7
  22. data/lib/json/add/ostruct.rb +32 -9
  23. data/lib/json/add/range.rb +33 -8
  24. data/lib/json/add/rational.rb +28 -6
  25. data/lib/json/add/regexp.rb +26 -8
  26. data/lib/json/add/set.rb +25 -6
  27. data/lib/json/add/struct.rb +29 -7
  28. data/lib/json/add/symbol.rb +34 -7
  29. data/lib/json/add/time.rb +29 -15
  30. data/lib/json/common.rb +654 -253
  31. data/lib/json/ext/generator/state.rb +106 -0
  32. data/lib/json/ext.rb +35 -5
  33. data/lib/json/generic_object.rb +7 -3
  34. data/lib/json/truffle_ruby/generator.rb +690 -0
  35. data/lib/json/version.rb +3 -7
  36. data/lib/json.rb +58 -21
  37. metadata +19 -26
  38. data/VERSION +0 -1
  39. data/ext/json/ext/generator/depend +0 -1
  40. data/ext/json/ext/generator/generator.h +0 -174
  41. data/ext/json/ext/parser/depend +0 -1
  42. data/ext/json/ext/parser/parser.h +0 -96
  43. data/ext/json/ext/parser/parser.rl +0 -986
  44. data/ext/json/extconf.rb +0 -3
  45. data/lib/json/pure/generator.rb +0 -479
  46. data/lib/json/pure/parser.rb +0 -337
  47. data/lib/json/pure.rb +0 -15
  48. /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|
@@ -70,97 +172,116 @@ module JSON
70
172
  end
71
173
  end
72
174
  self.state = generator::State
73
- 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
175
+ const_set :State, 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
- class ParserError < JSONError; end
233
+ class ParserError < JSONError
234
+ attr_reader :line, :column
235
+ end
147
236
 
148
237
  # This exception is raised if the nesting of parsed data structures is too
149
238
  # deep.
150
239
  class NestingError < ParserError; end
151
240
 
152
- # :stopdoc:
153
- class CircularDatastructure < NestingError; end
154
- # :startdoc:
155
-
156
241
  # This exception is raised if a generator or unparser error occurs.
157
- class GeneratorError < JSONError; end
158
- # For backwards compatibility
159
- UnparserError = GeneratorError # :nodoc:
242
+ class GeneratorError < JSONError
243
+ attr_reader :invalid_object
160
244
 
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
245
+ def initialize(message, invalid_object = nil)
246
+ super(message)
247
+ @invalid_object = invalid_object
248
+ end
249
+
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
+ # responsibility 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
164
285
 
165
286
  module_function
166
287
 
@@ -192,17 +313,17 @@ module JSON
192
313
  # {Parsing \JSON}[#module-JSON-label-Parsing+JSON].
193
314
  #
194
315
  # 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
316
+ # source = <<~JSON
317
+ # {
318
+ # "name": "Dave",
319
+ # "age" :40,
320
+ # "hats": [
321
+ # "Cattleman's",
322
+ # "Panama",
323
+ # "Tophat"
324
+ # ]
325
+ # }
326
+ # JSON
206
327
  # ruby = JSON.parse(source)
207
328
  # ruby # => {"name"=>"Dave", "age"=>40, "hats"=>["Cattleman's", "Panama", "Tophat"]}
208
329
  #
@@ -212,10 +333,17 @@ module JSON
212
333
  # # Raises JSON::ParserError (783: unexpected token at ''):
213
334
  # JSON.parse('')
214
335
  #
215
- def parse(source, opts = {})
216
- Parser.new(source, **(opts||{})).parse
336
+ def parse(source, opts = nil)
337
+ opts = ParserOptions.prepare(opts) unless opts.nil?
338
+ Parser.parse(source, opts)
217
339
  end
218
340
 
341
+ PARSE_L_OPTIONS = {
342
+ max_nesting: false,
343
+ allow_nan: true,
344
+ }.freeze
345
+ private_constant :PARSE_L_OPTIONS
346
+
219
347
  # :call-seq:
220
348
  # JSON.parse!(source, opts) -> object
221
349
  #
@@ -227,12 +355,12 @@ module JSON
227
355
  # - Option +max_nesting+, if not provided, defaults to +false+,
228
356
  # which disables checking for nesting depth.
229
357
  # - 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
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
236
364
  end
237
365
 
238
366
  # :call-seq:
@@ -242,8 +370,8 @@ module JSON
242
370
  # parse(File.read(path), opts)
243
371
  #
244
372
  # See method #parse.
245
- def load_file(filespec, opts = {})
246
- parse(File.read(filespec), opts)
373
+ def load_file(filespec, opts = nil)
374
+ parse(File.read(filespec, encoding: Encoding::UTF_8), opts)
247
375
  end
248
376
 
249
377
  # :call-seq:
@@ -253,8 +381,8 @@ module JSON
253
381
  # JSON.parse!(File.read(path, opts))
254
382
  #
255
383
  # See method #parse!
256
- def load_file!(filespec, opts = {})
257
- parse!(File.read(filespec), opts)
384
+ def load_file!(filespec, opts = nil)
385
+ parse!(File.read(filespec, encoding: Encoding::UTF_8), opts)
258
386
  end
259
387
 
260
388
  # :call-seq:
@@ -295,30 +423,12 @@ module JSON
295
423
  #
296
424
  def generate(obj, opts = nil)
297
425
  if State === opts
298
- state, opts = opts, nil
426
+ opts.generate(obj)
299
427
  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)
428
+ State.generate(obj, opts, nil)
311
429
  end
312
- state.generate(obj)
313
430
  end
314
431
 
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
432
  # :call-seq:
323
433
  # JSON.fast_generate(obj, opts) -> new_string
324
434
  #
@@ -333,29 +443,21 @@ module JSON
333
443
  # # Raises SystemStackError (stack level too deep):
334
444
  # JSON.fast_generate(a)
335
445
  def fast_generate(obj, opts = nil)
336
- if State === opts
337
- state, opts = opts, nil
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
338
448
  else
339
- state = JSON.create_fast_state
449
+ warn "JSON.fast_generate is deprecated and will be removed in json 3.0.0, just use JSON.generate", uplevel: 1
340
450
  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)
451
+ generate(obj, opts)
352
452
  end
353
453
 
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:
454
+ PRETTY_GENERATE_OPTIONS = {
455
+ indent: ' ',
456
+ space: ' ',
457
+ object_nl: "\n",
458
+ array_nl: "\n",
459
+ }.freeze
460
+ private_constant :PRETTY_GENERATE_OPTIONS
359
461
 
360
462
  # :call-seq:
361
463
  # JSON.pretty_generate(obj, opts = nil) -> new_string
@@ -388,49 +490,59 @@ module JSON
388
490
  # }
389
491
  #
390
492
  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
493
+ return opts.generate(obj) if State === opts
494
+
495
+ options = PRETTY_GENERATE_OPTIONS
496
+
396
497
  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"
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
403
506
  end
404
- state.configure(opts)
507
+ options = options.merge(opts)
405
508
  end
406
- state.generate(obj)
509
+
510
+ State.generate(obj, options, nil)
407
511
  end
408
512
 
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:
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
414
518
 
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 = {
519
+ @unsafe_load_default_options = {
423
520
  :max_nesting => false,
424
521
  :allow_nan => true,
425
- :allow_blank => true,
522
+ :allow_blank => true,
426
523
  :create_additions => true,
427
524
  }
428
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
+ }
429
537
  # :call-seq:
430
- # JSON.load(source, proc = nil, options = {}) -> object
538
+ # JSON.unsafe_load(source, proc = nil, options = {}) -> object
431
539
  #
432
540
  # Returns the Ruby objects created by parsing the given +source+.
433
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
+ #
434
546
  # - Argument +source+ must be, or be convertible to, a \String:
435
547
  # - If +source+ responds to instance method +to_str+,
436
548
  # <tt>source.to_str</tt> becomes the source.
@@ -445,12 +557,9 @@ module JSON
445
557
  # - Argument +proc+, if given, must be a \Proc that accepts one argument.
446
558
  # It will be called recursively with each result (depth-first order).
447
559
  # 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
560
  # - Argument +opts+, if given, contains a \Hash of options for the parsing.
452
561
  # See {Parsing Options}[#module-JSON-label-Parsing+Options].
453
- # 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=.
454
563
  #
455
564
  # ---
456
565
  #
@@ -458,17 +567,177 @@ module JSON
458
567
  # <tt>parse(source, opts)</tt>; see #parse.
459
568
  #
460
569
  # Source for following examples:
461
- # 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
462
620
  # {
463
- # "name": "Dave",
464
- # "age" :40,
465
- # "hats": [
466
- # "Cattleman's",
467
- # "Panama",
468
- # "Tophat"
469
- # ]
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"}
470
630
  # }
471
- # 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
472
741
  #
473
742
  # Load a \String:
474
743
  # ruby = JSON.load(source)
@@ -554,51 +823,43 @@ module JSON
554
823
  # #<Admin:0x00000000064c41f8
555
824
  # @attributes={"type"=>"Admin", "password"=>"0wn3d"}>}
556
825
  #
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
826
+ def load(source, proc = nil, options = nil)
827
+ opts = if options.nil?
828
+ _load_default_options
829
+ else
830
+ _load_default_options.merge(options)
565
831
  end
832
+
833
+ unless source.is_a?(String)
834
+ if source.respond_to? :to_str
835
+ source = source.to_str
836
+ elsif source.respond_to? :to_io
837
+ source = source.to_io.read
838
+ elsif source.respond_to?(:read)
839
+ source = source.read
840
+ end
841
+ end
842
+
566
843
  if opts[:allow_blank] && (source.nil? || source.empty?)
567
844
  source = 'null'
568
845
  end
569
- result = parse(source, opts)
570
- recurse_proc(result, &proc) if proc
571
- result
572
- end
573
846
 
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
847
+ if proc
848
+ opts = opts.dup
849
+ opts[:on_load] = proc.to_proc
585
850
  end
586
- end
587
851
 
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
852
+ parse(source, opts)
597
853
  end
598
- 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 = {
599
861
  :max_nesting => false,
600
862
  :allow_nan => true,
601
- :escape_slash => false,
602
863
  }
603
864
 
604
865
  # :call-seq:
@@ -628,30 +889,173 @@ module JSON
628
889
  # puts File.read(path)
629
890
  # Output:
630
891
  # {"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
892
+ def dump(obj, anIO = nil, limit = nil, kwargs = nil)
893
+ if kwargs.nil?
894
+ if limit.nil?
895
+ if anIO.is_a?(Hash)
896
+ kwargs = anIO
897
+ anIO = nil
898
+ end
899
+ elsif limit.is_a?(Hash)
900
+ kwargs = limit
901
+ limit = nil
902
+ end
903
+ end
904
+
905
+ unless anIO.nil?
906
+ if anIO.respond_to?(:to_io)
907
+ anIO = anIO.to_io
908
+ elsif limit.nil? && !anIO.respond_to?(:write)
909
+ anIO, limit = nil, anIO
637
910
  end
638
911
  end
639
- opts = JSON.dump_default_options
912
+
913
+ opts = JSON._dump_default_options
640
914
  opts = opts.merge(:max_nesting => limit) if limit
641
- result = generate(obj, opts)
642
- if anIO
643
- anIO.write result
644
- anIO
915
+ opts = opts.merge(kwargs) if kwargs
916
+
917
+ begin
918
+ State.generate(obj, opts, anIO)
919
+ rescue JSON::NestingError
920
+ raise ArgumentError, "exceed depth limit"
921
+ end
922
+ end
923
+
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
930
+ else
931
+ warn "JSON.unparse is deprecated and will be removed in json 3.0.0, just use JSON.generate", uplevel: 1
932
+ end
933
+ generate(...)
934
+ end
935
+ module_function :unparse
936
+
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(...)
944
+ end
945
+ module_function :fast_unparse
946
+
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(...)
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
645
960
  else
646
- result
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
966
+
967
+ class << self
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
647
982
  end
648
- rescue JSON::NestingError
649
- raise ArgumentError, "exceed depth limit"
650
983
  end
984
+ # :startdoc:
651
985
 
652
- # Encodes string using String.encode.
653
- def self.iconv(to, from, string)
654
- string.encode(to, from)
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
655
1059
  end
656
1060
  end
657
1061
 
@@ -661,8 +1065,14 @@ module ::Kernel
661
1065
  # Outputs _objs_ to STDOUT as JSON strings in the shortest form, that is in
662
1066
  # one line.
663
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
+
664
1074
  objs.each do |obj|
665
- puts JSON::generate(obj, :allow_nan => true, :max_nesting => false)
1075
+ puts JSON.generate(obj, :allow_nan => true, :max_nesting => false)
666
1076
  end
667
1077
  nil
668
1078
  end
@@ -670,8 +1080,14 @@ module ::Kernel
670
1080
  # Outputs _objs_ to STDOUT as JSON strings in a pretty format, with
671
1081
  # indentation and over many lines.
672
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
+
673
1089
  objs.each do |obj|
674
- puts JSON::pretty_generate(obj, :allow_nan => true, :max_nesting => false)
1090
+ puts JSON.pretty_generate(obj, :allow_nan => true, :max_nesting => false)
675
1091
  end
676
1092
  nil
677
1093
  end
@@ -682,22 +1098,7 @@ module ::Kernel
682
1098
  #
683
1099
  # The _opts_ argument is passed through to generate/parse respectively. See
684
1100
  # 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)
1101
+ def JSON(object, opts = nil)
1102
+ JSON[object, opts]
702
1103
  end
703
1104
  end