json 2.7.2 → 2.12.2

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 (44) hide show
  1. checksums.yaml +4 -4
  2. data/BSDL +22 -0
  3. data/CHANGES.md +160 -17
  4. data/LEGAL +8 -0
  5. data/README.md +76 -211
  6. data/ext/json/ext/fbuffer/fbuffer.h +178 -95
  7. data/ext/json/ext/generator/extconf.rb +38 -2
  8. data/ext/json/ext/generator/generator.c +1311 -826
  9. data/ext/json/ext/generator/simd.h +112 -0
  10. data/ext/json/ext/parser/extconf.rb +6 -27
  11. data/ext/json/ext/parser/parser.c +1176 -1971
  12. data/ext/json/ext/vendor/fpconv.c +479 -0
  13. data/ext/json/ext/vendor/jeaiii-ltoa.h +267 -0
  14. data/json.gemspec +44 -49
  15. data/lib/json/add/bigdecimal.rb +2 -2
  16. data/lib/json/add/complex.rb +1 -1
  17. data/lib/json/add/core.rb +1 -1
  18. data/lib/json/add/date.rb +1 -1
  19. data/lib/json/add/date_time.rb +1 -1
  20. data/lib/json/add/exception.rb +1 -1
  21. data/lib/json/add/ostruct.rb +1 -1
  22. data/lib/json/add/range.rb +1 -1
  23. data/lib/json/add/rational.rb +1 -1
  24. data/lib/json/add/regexp.rb +1 -1
  25. data/lib/json/add/struct.rb +1 -1
  26. data/lib/json/add/symbol.rb +8 -4
  27. data/lib/json/add/time.rb +3 -10
  28. data/lib/json/common.rb +647 -241
  29. data/lib/json/ext/generator/state.rb +106 -0
  30. data/lib/json/ext.rb +35 -5
  31. data/lib/json/generic_object.rb +1 -1
  32. data/lib/json/{pure → truffle_ruby}/generator.rb +322 -145
  33. data/lib/json/version.rb +3 -7
  34. data/lib/json.rb +16 -21
  35. metadata +18 -22
  36. data/ext/json/ext/generator/depend +0 -1
  37. data/ext/json/ext/generator/generator.h +0 -177
  38. data/ext/json/ext/parser/depend +0 -1
  39. data/ext/json/ext/parser/parser.h +0 -96
  40. data/ext/json/ext/parser/parser.rl +0 -971
  41. data/ext/json/extconf.rb +0 -3
  42. data/lib/json/pure/parser.rb +0 -337
  43. data/lib/json/pure.rb +0 -15
  44. /data/{LICENSE → COPYING} +0 -0
data/lib/json/common.rb CHANGED
@@ -1,11 +1,115 @@
1
- #frozen_string_literal: false
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,17 +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 = {})
23
- if object.respond_to? :to_str
24
- JSON.parse(object.to_str, opts)
25
- else
26
- 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
27
134
  end
135
+
136
+ JSON.generate(object, opts)
28
137
  end
29
138
 
30
- # Returns the JSON parser class that is used by JSON. This is either
31
- # JSON::Ext::Parser or JSON::Pure::Parser:
32
- # JSON.parser # => JSON::Ext::Parser
139
+ # Returns the JSON parser class that is used by JSON.
33
140
  attr_reader :parser
34
141
 
35
142
  # Set the JSON parser class _parser_ to be used by JSON.
@@ -44,18 +151,9 @@ module JSON
44
151
  # level (absolute namespace path?). If there doesn't exist a constant at
45
152
  # the given path, an ArgumentError is raised.
46
153
  def deep_const_get(path) # :nodoc:
47
- path.to_s.split(/::/).inject(Object) do |p, c|
48
- case
49
- when c.empty? then p
50
- when p.const_defined?(c, true) then p.const_get(c)
51
- else
52
- begin
53
- p.const_missing(c)
54
- rescue NameError => e
55
- raise ArgumentError, "can't get const #{path}: #{e}"
56
- end
57
- end
58
- end
154
+ Object.const_get(path)
155
+ rescue NameError => e
156
+ raise ArgumentError, "can't get const #{path}: #{e}"
59
157
  end
60
158
 
61
159
  # Set the module _generator_ to be used by JSON.
@@ -64,7 +162,7 @@ module JSON
64
162
  @generator = generator
65
163
  generator_methods = generator::GeneratorMethods
66
164
  for const in generator_methods.constants
67
- klass = deep_const_get(const)
165
+ klass = const_get(const)
68
166
  modul = generator_methods.const_get(const)
69
167
  klass.class_eval do
70
168
  instance_methods(false).each do |m|
@@ -74,97 +172,116 @@ module JSON
74
172
  end
75
173
  end
76
174
  self.state = generator::State
77
- const_set :State, self.state
78
- const_set :SAFE_STATE_PROTOTYPE, State.new # for JRuby
79
- const_set :FAST_STATE_PROTOTYPE, create_fast_state
80
- const_set :PRETTY_STATE_PROTOTYPE, create_pretty_state
175
+ const_set :State, state
81
176
  ensure
82
177
  $VERBOSE = old
83
178
  end
84
179
 
85
- def create_fast_state
86
- State.new(
87
- :indent => '',
88
- :space => '',
89
- :object_nl => "",
90
- :array_nl => "",
91
- :max_nesting => false
92
- )
93
- end
94
-
95
- def create_pretty_state
96
- State.new(
97
- :indent => ' ',
98
- :space => ' ',
99
- :object_nl => "\n",
100
- :array_nl => "\n"
101
- )
102
- end
103
-
104
- # Returns the JSON generator module that is used by JSON. This is
105
- # either JSON::Ext::Generator or JSON::Pure::Generator:
106
- # JSON.generator # => JSON::Ext::Generator
180
+ # Returns the JSON generator module that is used by JSON.
107
181
  attr_reader :generator
108
182
 
109
- # Sets or Returns the JSON generator state class that is used by JSON. This is
110
- # either JSON::Ext::Generator::State or JSON::Pure::Generator::State:
111
- # JSON.state # => JSON::Ext::Generator::State
183
+ # Sets or Returns the JSON generator state class that is used by JSON.
112
184
  attr_accessor :state
113
- end
114
185
 
115
- DEFAULT_CREATE_ID = 'json_class'.freeze
116
- private_constant :DEFAULT_CREATE_ID
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
117
201
 
118
- CREATE_ID_TLS_KEY = "JSON.create_id".freeze
119
- private_constant :CREATE_ID_TLS_KEY
202
+ def _#{attr}
203
+ @#{attr}
204
+ end
205
+ RUBY
206
+ end
207
+ end
208
+ end
120
209
 
121
210
  # Sets create identifier, which is used to decide if the _json_create_
122
211
  # hook of a class should be called; initial value is +json_class+:
123
212
  # JSON.create_id # => 'json_class'
124
213
  def self.create_id=(new_value)
125
- Thread.current[CREATE_ID_TLS_KEY] = new_value.dup.freeze
214
+ Thread.current[:"JSON.create_id"] = new_value.dup.freeze
126
215
  end
127
216
 
128
217
  # Returns the current create identifier.
129
218
  # See also JSON.create_id=.
130
219
  def self.create_id
131
- Thread.current[CREATE_ID_TLS_KEY] || DEFAULT_CREATE_ID
220
+ Thread.current[:"JSON.create_id"] || 'json_class'
132
221
  end
133
222
 
134
- NaN = 0.0/0
223
+ NaN = Float::NAN
135
224
 
136
- Infinity = 1.0/0
225
+ Infinity = Float::INFINITY
137
226
 
138
227
  MinusInfinity = -Infinity
139
228
 
140
229
  # The base exception for JSON errors.
141
- class JSONError < StandardError
142
- def self.wrap(exception)
143
- obj = new("Wrapped(#{exception.class}): #{exception.message.inspect}")
144
- obj.set_backtrace exception.backtrace
145
- obj
146
- end
147
- end
230
+ class JSONError < StandardError; end
148
231
 
149
232
  # This exception is raised if a parser error occurs.
150
- class ParserError < JSONError; end
233
+ class ParserError < JSONError
234
+ attr_reader :line, :column
235
+ end
151
236
 
152
237
  # This exception is raised if the nesting of parsed data structures is too
153
238
  # deep.
154
239
  class NestingError < ParserError; end
155
240
 
156
- # :stopdoc:
157
- class CircularDatastructure < NestingError; end
158
- # :startdoc:
159
-
160
241
  # This exception is raised if a generator or unparser error occurs.
161
- class GeneratorError < JSONError; end
162
- # For backwards compatibility
163
- 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
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
+ # 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
164
280
 
165
- # This exception is raised if the required unicode support is missing on the
166
- # system. Usually this means that the iconv library is not installed.
167
- class MissingUnicodeSupport < JSONError; end
281
+ def to_json(state = nil, *)
282
+ json
283
+ end
284
+ end
168
285
 
169
286
  module_function
170
287
 
@@ -196,17 +313,17 @@ module JSON
196
313
  # {Parsing \JSON}[#module-JSON-label-Parsing+JSON].
197
314
  #
198
315
  # Parses nested JSON objects:
199
- # source = <<-EOT
200
- # {
201
- # "name": "Dave",
202
- # "age" :40,
203
- # "hats": [
204
- # "Cattleman's",
205
- # "Panama",
206
- # "Tophat"
207
- # ]
208
- # }
209
- # EOT
316
+ # source = <<~JSON
317
+ # {
318
+ # "name": "Dave",
319
+ # "age" :40,
320
+ # "hats": [
321
+ # "Cattleman's",
322
+ # "Panama",
323
+ # "Tophat"
324
+ # ]
325
+ # }
326
+ # JSON
210
327
  # ruby = JSON.parse(source)
211
328
  # ruby # => {"name"=>"Dave", "age"=>40, "hats"=>["Cattleman's", "Panama", "Tophat"]}
212
329
  #
@@ -216,10 +333,17 @@ module JSON
216
333
  # # Raises JSON::ParserError (783: unexpected token at ''):
217
334
  # JSON.parse('')
218
335
  #
219
- def parse(source, opts = {})
220
- Parser.new(source, **(opts||{})).parse
336
+ def parse(source, opts = nil)
337
+ opts = ParserOptions.prepare(opts) unless opts.nil?
338
+ Parser.parse(source, opts)
221
339
  end
222
340
 
341
+ PARSE_L_OPTIONS = {
342
+ max_nesting: false,
343
+ allow_nan: true,
344
+ }.freeze
345
+ private_constant :PARSE_L_OPTIONS
346
+
223
347
  # :call-seq:
224
348
  # JSON.parse!(source, opts) -> object
225
349
  #
@@ -231,12 +355,12 @@ module JSON
231
355
  # - Option +max_nesting+, if not provided, defaults to +false+,
232
356
  # which disables checking for nesting depth.
233
357
  # - Option +allow_nan+, if not provided, defaults to +true+.
234
- def parse!(source, opts = {})
235
- opts = {
236
- :max_nesting => false,
237
- :allow_nan => true
238
- }.merge(opts)
239
- 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
240
364
  end
241
365
 
242
366
  # :call-seq:
@@ -246,8 +370,8 @@ module JSON
246
370
  # parse(File.read(path), opts)
247
371
  #
248
372
  # See method #parse.
249
- def load_file(filespec, opts = {})
250
- parse(File.read(filespec), opts)
373
+ def load_file(filespec, opts = nil)
374
+ parse(File.read(filespec, encoding: Encoding::UTF_8), opts)
251
375
  end
252
376
 
253
377
  # :call-seq:
@@ -257,8 +381,8 @@ module JSON
257
381
  # JSON.parse!(File.read(path, opts))
258
382
  #
259
383
  # See method #parse!
260
- def load_file!(filespec, opts = {})
261
- parse!(File.read(filespec), opts)
384
+ def load_file!(filespec, opts = nil)
385
+ parse!(File.read(filespec, encoding: Encoding::UTF_8), opts)
262
386
  end
263
387
 
264
388
  # :call-seq:
@@ -299,20 +423,12 @@ module JSON
299
423
  #
300
424
  def generate(obj, opts = nil)
301
425
  if State === opts
302
- state = opts
426
+ opts.generate(obj)
303
427
  else
304
- state = State.new(opts)
428
+ State.generate(obj, opts, nil)
305
429
  end
306
- state.generate(obj)
307
430
  end
308
431
 
309
- # :stopdoc:
310
- # I want to deprecate these later, so I'll first be silent about them, and
311
- # later delete them.
312
- alias unparse generate
313
- module_function :unparse
314
- # :startdoc:
315
-
316
432
  # :call-seq:
317
433
  # JSON.fast_generate(obj, opts) -> new_string
318
434
  #
@@ -327,19 +443,21 @@ module JSON
327
443
  # # Raises SystemStackError (stack level too deep):
328
444
  # JSON.fast_generate(a)
329
445
  def fast_generate(obj, opts = nil)
330
- if State === opts
331
- 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
332
448
  else
333
- 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
334
450
  end
335
- state.generate(obj)
451
+ generate(obj, opts)
336
452
  end
337
453
 
338
- # :stopdoc:
339
- # I want to deprecate these later, so I'll first be silent about them, and later delete them.
340
- alias fast_unparse fast_generate
341
- module_function :fast_unparse
342
- # :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
343
461
 
344
462
  # :call-seq:
345
463
  # JSON.pretty_generate(obj, opts = nil) -> new_string
@@ -372,49 +490,59 @@ module JSON
372
490
  # }
373
491
  #
374
492
  def pretty_generate(obj, opts = nil)
375
- if State === opts
376
- state, opts = opts, nil
377
- else
378
- state = JSON.create_pretty_state
379
- end
493
+ return opts.generate(obj) if State === opts
494
+
495
+ options = PRETTY_GENERATE_OPTIONS
496
+
380
497
  if opts
381
- if opts.respond_to? :to_hash
382
- opts = opts.to_hash
383
- elsif opts.respond_to? :to_h
384
- opts = opts.to_h
385
- else
386
- 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
387
506
  end
388
- state.configure(opts)
507
+ options = options.merge(opts)
389
508
  end
390
- state.generate(obj)
509
+
510
+ State.generate(obj, options, nil)
391
511
  end
392
512
 
393
- # :stopdoc:
394
- # I want to deprecate these later, so I'll first be silent about them, and later delete them.
395
- alias pretty_unparse pretty_generate
396
- module_function :pretty_unparse
397
- # :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
398
518
 
399
- class << self
400
- # Sets or returns default options for the JSON.load method.
401
- # Initially:
402
- # opts = JSON.load_default_options
403
- # opts # => {:max_nesting=>false, :allow_nan=>true, :allow_blank=>true, :create_additions=>true}
404
- attr_accessor :load_default_options
405
- end
406
- self.load_default_options = {
519
+ @unsafe_load_default_options = {
407
520
  :max_nesting => false,
408
521
  :allow_nan => true,
409
- :allow_blank => true,
522
+ :allow_blank => true,
410
523
  :create_additions => true,
411
524
  }
412
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
+ }
413
537
  # :call-seq:
414
- # JSON.load(source, proc = nil, options = {}) -> object
538
+ # JSON.unsafe_load(source, proc = nil, options = {}) -> object
415
539
  #
416
540
  # Returns the Ruby objects created by parsing the given +source+.
417
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
+ #
418
546
  # - Argument +source+ must be, or be convertible to, a \String:
419
547
  # - If +source+ responds to instance method +to_str+,
420
548
  # <tt>source.to_str</tt> becomes the source.
@@ -429,12 +557,9 @@ module JSON
429
557
  # - Argument +proc+, if given, must be a \Proc that accepts one argument.
430
558
  # It will be called recursively with each result (depth-first order).
431
559
  # See details below.
432
- # BEWARE: This method is meant to serialise data from trusted user input,
433
- # like from your own database server or clients under your control, it could
434
- # be dangerous to allow untrusted users to pass JSON sources into it.
435
560
  # - Argument +opts+, if given, contains a \Hash of options for the parsing.
436
561
  # See {Parsing Options}[#module-JSON-label-Parsing+Options].
437
- # 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=.
438
563
  #
439
564
  # ---
440
565
  #
@@ -442,17 +567,177 @@ module JSON
442
567
  # <tt>parse(source, opts)</tt>; see #parse.
443
568
  #
444
569
  # Source for following examples:
445
- # 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
446
620
  # {
447
- # "name": "Dave",
448
- # "age" :40,
449
- # "hats": [
450
- # "Cattleman's",
451
- # "Panama",
452
- # "Tophat"
453
- # ]
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"}
454
630
  # }
455
- # 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
456
741
  #
457
742
  # Load a \String:
458
743
  # ruby = JSON.load(source)
@@ -538,51 +823,43 @@ module JSON
538
823
  # #<Admin:0x00000000064c41f8
539
824
  # @attributes={"type"=>"Admin", "password"=>"0wn3d"}>}
540
825
  #
541
- def load(source, proc = nil, options = {})
542
- opts = load_default_options.merge options
543
- if source.respond_to? :to_str
544
- source = source.to_str
545
- elsif source.respond_to? :to_io
546
- source = source.to_io.read
547
- elsif source.respond_to?(:read)
548
- 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)
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
549
841
  end
842
+
550
843
  if opts[:allow_blank] && (source.nil? || source.empty?)
551
844
  source = 'null'
552
845
  end
553
- result = parse(source, opts)
554
- recurse_proc(result, &proc) if proc
555
- result
556
- end
557
846
 
558
- # Recursively calls passed _Proc_ if the parsed data structure is an _Array_ or _Hash_
559
- def recurse_proc(result, &proc) # :nodoc:
560
- case result
561
- when Array
562
- result.each { |x| recurse_proc x, &proc }
563
- proc.call result
564
- when Hash
565
- result.each { |x, y| recurse_proc x, &proc; recurse_proc y, &proc }
566
- proc.call result
567
- else
568
- proc.call result
847
+ if proc
848
+ opts = opts.dup
849
+ opts[:on_load] = proc.to_proc
569
850
  end
570
- end
571
-
572
- alias restore load
573
- module_function :restore
574
851
 
575
- class << self
576
- # Sets or returns the default options for the JSON.dump method.
577
- # Initially:
578
- # opts = JSON.dump_default_options
579
- # opts # => {:max_nesting=>false, :allow_nan=>true, :script_safe=>false}
580
- attr_accessor :dump_default_options
852
+ parse(source, opts)
581
853
  end
582
- 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 = {
583
861
  :max_nesting => false,
584
862
  :allow_nan => true,
585
- :script_safe => false,
586
863
  }
587
864
 
588
865
  # :call-seq:
@@ -613,40 +890,172 @@ module JSON
613
890
  # Output:
614
891
  # {"foo":[0,1],"bar":{"baz":2,"bat":3},"bam":"bad"}
615
892
  def dump(obj, anIO = nil, limit = nil, kwargs = nil)
616
- io_limit_opt = [anIO, limit, kwargs].compact
617
- kwargs = io_limit_opt.pop if io_limit_opt.last.is_a?(Hash)
618
- anIO, limit = io_limit_opt
619
- if anIO.respond_to?(:to_io)
620
- anIO = anIO.to_io
621
- elsif limit.nil? && !anIO.respond_to?(:write)
622
- anIO, limit = nil, anIO
623
- end
624
- opts = JSON.dump_default_options
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
910
+ end
911
+ end
912
+
913
+ opts = JSON._dump_default_options
625
914
  opts = opts.merge(:max_nesting => limit) if limit
626
- opts = merge_dump_options(opts, **kwargs) if kwargs
627
- result = generate(obj, opts)
628
- if anIO
629
- anIO.write result
630
- 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
631
940
  else
632
- result
941
+ warn "JSON.fast_unparse is deprecated and will be removed in json 3.0.0, just use JSON.generate", uplevel: 1
633
942
  end
634
- rescue JSON::NestingError
635
- raise ArgumentError, "exceed depth limit"
943
+ generate(...)
636
944
  end
945
+ module_function :fast_unparse
637
946
 
638
- # Encodes string using String.encode.
639
- def self.iconv(to, from, string)
640
- string.encode(to, from)
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(...)
641
954
  end
955
+ module_function :fast_unparse
642
956
 
643
- def merge_dump_options(opts, strict: NOT_SET)
644
- opts = opts.merge(strict: strict) if NOT_SET != strict
645
- opts
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(...)
646
964
  end
965
+ module_function :restore
647
966
 
648
967
  class << self
649
- 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
650
1059
  end
651
1060
  end
652
1061
 
@@ -656,8 +1065,14 @@ module ::Kernel
656
1065
  # Outputs _objs_ to STDOUT as JSON strings in the shortest form, that is in
657
1066
  # one line.
658
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
+
659
1074
  objs.each do |obj|
660
- puts JSON::generate(obj, :allow_nan => true, :max_nesting => false)
1075
+ puts JSON.generate(obj, :allow_nan => true, :max_nesting => false)
661
1076
  end
662
1077
  nil
663
1078
  end
@@ -665,8 +1080,14 @@ module ::Kernel
665
1080
  # Outputs _objs_ to STDOUT as JSON strings in a pretty format, with
666
1081
  # indentation and over many lines.
667
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
+
668
1089
  objs.each do |obj|
669
- puts JSON::pretty_generate(obj, :allow_nan => true, :max_nesting => false)
1090
+ puts JSON.pretty_generate(obj, :allow_nan => true, :max_nesting => false)
670
1091
  end
671
1092
  nil
672
1093
  end
@@ -677,22 +1098,7 @@ module ::Kernel
677
1098
  #
678
1099
  # The _opts_ argument is passed through to generate/parse respectively. See
679
1100
  # generate and parse for their documentation.
680
- def JSON(object, *args)
681
- if object.respond_to? :to_str
682
- JSON.parse(object.to_str, args.first)
683
- else
684
- JSON.generate(object, args.first)
685
- end
686
- end
687
- end
688
-
689
- # Extends any Class to include _json_creatable?_ method.
690
- class ::Class
691
- # Returns true if this class can be used to create an instance
692
- # from a serialised JSON string. The class has to implement a class
693
- # method _json_create_ that expects a hash as first parameter. The hash
694
- # should include the required data.
695
- def json_creatable?
696
- respond_to?(:json_create)
1101
+ def JSON(object, opts = nil)
1102
+ JSON[object, opts]
697
1103
  end
698
1104
  end