json 2.6.2 → 2.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/BSDL +22 -0
  3. data/CHANGES.md +144 -17
  4. data/LEGAL +8 -0
  5. data/README.md +67 -224
  6. data/ext/json/ext/fbuffer/fbuffer.h +110 -92
  7. data/ext/json/ext/generator/extconf.rb +8 -2
  8. data/ext/json/ext/generator/generator.c +1020 -806
  9. data/ext/json/ext/parser/extconf.rb +7 -27
  10. data/ext/json/ext/parser/parser.c +1343 -3212
  11. data/json.gemspec +48 -52
  12. data/lib/json/add/bigdecimal.rb +39 -10
  13. data/lib/json/add/complex.rb +29 -6
  14. data/lib/json/add/core.rb +1 -1
  15. data/lib/json/add/date.rb +27 -7
  16. data/lib/json/add/date_time.rb +26 -9
  17. data/lib/json/add/exception.rb +25 -7
  18. data/lib/json/add/ostruct.rb +32 -9
  19. data/lib/json/add/range.rb +33 -8
  20. data/lib/json/add/rational.rb +28 -6
  21. data/lib/json/add/regexp.rb +26 -8
  22. data/lib/json/add/set.rb +25 -6
  23. data/lib/json/add/struct.rb +29 -7
  24. data/lib/json/add/symbol.rb +34 -7
  25. data/lib/json/add/time.rb +29 -15
  26. data/lib/json/common.rb +418 -128
  27. data/lib/json/ext/generator/state.rb +106 -0
  28. data/lib/json/ext.rb +34 -4
  29. data/lib/json/generic_object.rb +7 -3
  30. data/lib/json/truffle_ruby/generator.rb +690 -0
  31. data/lib/json/version.rb +3 -7
  32. data/lib/json.rb +25 -21
  33. metadata +15 -26
  34. data/VERSION +0 -1
  35. data/ext/json/ext/generator/depend +0 -1
  36. data/ext/json/ext/generator/generator.h +0 -174
  37. data/ext/json/ext/parser/depend +0 -1
  38. data/ext/json/ext/parser/parser.h +0 -96
  39. data/ext/json/ext/parser/parser.rl +0 -986
  40. data/ext/json/extconf.rb +0 -3
  41. data/lib/json/pure/generator.rb +0 -479
  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,8 +1,13 @@
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
+ NOT_SET = Object.new.freeze
9
+ private_constant :NOT_SET
10
+
6
11
  class << self
7
12
  # :call-seq:
8
13
  # JSON[object] -> new_array or new_string
@@ -16,16 +21,19 @@ module JSON
16
21
  # ruby = [0, 1, nil]
17
22
  # JSON[ruby] # => '[0,1,null]'
18
23
  def [](object, opts = {})
19
- if object.respond_to? :to_str
20
- JSON.parse(object.to_str, opts)
21
- else
22
- JSON.generate(object, opts)
24
+ if object.is_a?(String)
25
+ return JSON.parse(object, opts)
26
+ elsif object.respond_to?(:to_str)
27
+ str = object.to_str
28
+ if str.is_a?(String)
29
+ return JSON.parse(str, opts)
30
+ end
23
31
  end
32
+
33
+ JSON.generate(object, opts)
24
34
  end
25
35
 
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
36
+ # Returns the JSON parser class that is used by JSON.
29
37
  attr_reader :parser
30
38
 
31
39
  # Set the JSON parser class _parser_ to be used by JSON.
@@ -40,18 +48,9 @@ module JSON
40
48
  # level (absolute namespace path?). If there doesn't exist a constant at
41
49
  # the given path, an ArgumentError is raised.
42
50
  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
51
+ Object.const_get(path)
52
+ rescue NameError => e
53
+ raise ArgumentError, "can't get const #{path}: #{e}"
55
54
  end
56
55
 
57
56
  # Set the module _generator_ to be used by JSON.
@@ -60,7 +59,7 @@ module JSON
60
59
  @generator = generator
61
60
  generator_methods = generator::GeneratorMethods
62
61
  for const in generator_methods.constants
63
- klass = deep_const_get(const)
62
+ klass = const_get(const)
64
63
  modul = generator_methods.const_get(const)
65
64
  klass.class_eval do
66
65
  instance_methods(false).each do |m|
@@ -97,34 +96,24 @@ module JSON
97
96
  )
98
97
  end
99
98
 
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
99
+ # Returns the JSON generator module that is used by JSON.
103
100
  attr_reader :generator
104
101
 
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
102
+ # Sets or Returns the JSON generator state class that is used by JSON.
108
103
  attr_accessor :state
109
104
  end
110
105
 
111
- DEFAULT_CREATE_ID = 'json_class'.freeze
112
- private_constant :DEFAULT_CREATE_ID
113
-
114
- CREATE_ID_TLS_KEY = "JSON.create_id".freeze
115
- private_constant :CREATE_ID_TLS_KEY
116
-
117
106
  # Sets create identifier, which is used to decide if the _json_create_
118
107
  # hook of a class should be called; initial value is +json_class+:
119
108
  # JSON.create_id # => 'json_class'
120
109
  def self.create_id=(new_value)
121
- Thread.current[CREATE_ID_TLS_KEY] = new_value.dup.freeze
110
+ Thread.current[:"JSON.create_id"] = new_value.dup.freeze
122
111
  end
123
112
 
124
113
  # Returns the current create identifier.
125
114
  # See also JSON.create_id=.
126
115
  def self.create_id
127
- Thread.current[CREATE_ID_TLS_KEY] || DEFAULT_CREATE_ID
116
+ Thread.current[:"JSON.create_id"] || 'json_class'
128
117
  end
129
118
 
130
119
  NaN = 0.0/0
@@ -154,7 +143,23 @@ module JSON
154
143
  # :startdoc:
155
144
 
156
145
  # This exception is raised if a generator or unparser error occurs.
157
- class GeneratorError < JSONError; end
146
+ class GeneratorError < JSONError
147
+ attr_reader :invalid_object
148
+
149
+ def initialize(message, invalid_object = nil)
150
+ super(message)
151
+ @invalid_object = invalid_object
152
+ end
153
+
154
+ def detailed_message(...)
155
+ if @invalid_object.nil?
156
+ super
157
+ else
158
+ "#{super}\nInvalid object: #{@invalid_object.inspect}"
159
+ end
160
+ end
161
+ end
162
+
158
163
  # For backwards compatibility
159
164
  UnparserError = GeneratorError # :nodoc:
160
165
 
@@ -162,6 +167,30 @@ module JSON
162
167
  # system. Usually this means that the iconv library is not installed.
163
168
  class MissingUnicodeSupport < JSONError; end
164
169
 
170
+ # Fragment of JSON document that is to be included as is:
171
+ # fragment = JSON::Fragment.new("[1, 2, 3]")
172
+ # JSON.generate({ count: 3, items: fragments })
173
+ #
174
+ # This allows to easily assemble multiple JSON fragments that have
175
+ # been persisted somewhere without having to parse them nor resorting
176
+ # to string interpolation.
177
+ #
178
+ # Note: no validation is performed on the provided string. It is the
179
+ # responsability of the caller to ensure the string contains valid JSON.
180
+ Fragment = Struct.new(:json) do
181
+ def initialize(json)
182
+ unless string = String.try_convert(json)
183
+ raise TypeError, " no implicit conversion of #{json.class} into String"
184
+ end
185
+
186
+ super(string)
187
+ end
188
+
189
+ def to_json(state = nil, *)
190
+ json
191
+ end
192
+ end
193
+
165
194
  module_function
166
195
 
167
196
  # :call-seq:
@@ -192,17 +221,17 @@ module JSON
192
221
  # {Parsing \JSON}[#module-JSON-label-Parsing+JSON].
193
222
  #
194
223
  # 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
224
+ # source = <<~JSON
225
+ # {
226
+ # "name": "Dave",
227
+ # "age" :40,
228
+ # "hats": [
229
+ # "Cattleman's",
230
+ # "Panama",
231
+ # "Tophat"
232
+ # ]
233
+ # }
234
+ # JSON
206
235
  # ruby = JSON.parse(source)
207
236
  # ruby # => {"name"=>"Dave", "age"=>40, "hats"=>["Cattleman's", "Panama", "Tophat"]}
208
237
  #
@@ -212,8 +241,8 @@ module JSON
212
241
  # # Raises JSON::ParserError (783: unexpected token at ''):
213
242
  # JSON.parse('')
214
243
  #
215
- def parse(source, opts = {})
216
- Parser.new(source, **(opts||{})).parse
244
+ def parse(source, opts = nil)
245
+ Parser.parse(source, opts)
217
246
  end
218
247
 
219
248
  # :call-seq:
@@ -227,12 +256,13 @@ module JSON
227
256
  # - Option +max_nesting+, if not provided, defaults to +false+,
228
257
  # which disables checking for nesting depth.
229
258
  # - Option +allow_nan+, if not provided, defaults to +true+.
230
- def parse!(source, opts = {})
231
- opts = {
259
+ def parse!(source, opts = nil)
260
+ options = {
232
261
  :max_nesting => false,
233
262
  :allow_nan => true
234
- }.merge(opts)
235
- Parser.new(source, **(opts||{})).parse
263
+ }
264
+ options.merge!(opts) if opts
265
+ Parser.new(source, options).parse
236
266
  end
237
267
 
238
268
  # :call-seq:
@@ -242,8 +272,8 @@ module JSON
242
272
  # parse(File.read(path), opts)
243
273
  #
244
274
  # See method #parse.
245
- def load_file(filespec, opts = {})
246
- parse(File.read(filespec), opts)
275
+ def load_file(filespec, opts = nil)
276
+ parse(File.read(filespec, encoding: Encoding::UTF_8), opts)
247
277
  end
248
278
 
249
279
  # :call-seq:
@@ -253,8 +283,8 @@ module JSON
253
283
  # JSON.parse!(File.read(path, opts))
254
284
  #
255
285
  # See method #parse!
256
- def load_file!(filespec, opts = {})
257
- parse!(File.read(filespec), opts)
286
+ def load_file!(filespec, opts = nil)
287
+ parse!(File.read(filespec, encoding: Encoding::UTF_8), opts)
258
288
  end
259
289
 
260
290
  # :call-seq:
@@ -295,21 +325,10 @@ module JSON
295
325
  #
296
326
  def generate(obj, opts = nil)
297
327
  if State === opts
298
- state, opts = opts, nil
328
+ opts.generate(obj)
299
329
  else
300
- state = State.new
330
+ State.generate(obj, opts, nil)
301
331
  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)
311
- end
312
- state.generate(obj)
313
332
  end
314
333
 
315
334
  # :stopdoc:
@@ -334,19 +353,9 @@ module JSON
334
353
  # JSON.fast_generate(a)
335
354
  def fast_generate(obj, opts = nil)
336
355
  if State === opts
337
- state, opts = opts, nil
356
+ state = opts
338
357
  else
339
- state = JSON.create_fast_state
340
- 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)
358
+ state = JSON.create_fast_state.configure(opts)
350
359
  end
351
360
  state.generate(obj)
352
361
  end
@@ -412,6 +421,20 @@ module JSON
412
421
  module_function :pretty_unparse
413
422
  # :startdoc:
414
423
 
424
+ class << self
425
+ # Sets or returns default options for the JSON.unsafe_load method.
426
+ # Initially:
427
+ # opts = JSON.load_default_options
428
+ # opts # => {:max_nesting=>false, :allow_nan=>true, :allow_blank=>true, :create_additions=>true}
429
+ attr_accessor :unsafe_load_default_options
430
+ end
431
+ self.unsafe_load_default_options = {
432
+ :max_nesting => false,
433
+ :allow_nan => true,
434
+ :allow_blank => true,
435
+ :create_additions => true,
436
+ }
437
+
415
438
  class << self
416
439
  # Sets or returns default options for the JSON.load method.
417
440
  # Initially:
@@ -420,17 +443,179 @@ module JSON
420
443
  attr_accessor :load_default_options
421
444
  end
422
445
  self.load_default_options = {
423
- :max_nesting => false,
424
446
  :allow_nan => true,
425
- :allow_blank => true,
426
- :create_additions => true,
447
+ :allow_blank => true,
448
+ :create_additions => nil,
427
449
  }
450
+ # :call-seq:
451
+ # JSON.unsafe_load(source, proc = nil, options = {}) -> object
452
+ #
453
+ # Returns the Ruby objects created by parsing the given +source+.
454
+ #
455
+ # BEWARE: This method is meant to serialise data from trusted user input,
456
+ # like from your own database server or clients under your control, it could
457
+ # be dangerous to allow untrusted users to pass JSON sources into it.
458
+ #
459
+ # - Argument +source+ must be, or be convertible to, a \String:
460
+ # - If +source+ responds to instance method +to_str+,
461
+ # <tt>source.to_str</tt> becomes the source.
462
+ # - If +source+ responds to instance method +to_io+,
463
+ # <tt>source.to_io.read</tt> becomes the source.
464
+ # - If +source+ responds to instance method +read+,
465
+ # <tt>source.read</tt> becomes the source.
466
+ # - If both of the following are true, source becomes the \String <tt>'null'</tt>:
467
+ # - Option +allow_blank+ specifies a truthy value.
468
+ # - The source, as defined above, is +nil+ or the empty \String <tt>''</tt>.
469
+ # - Otherwise, +source+ remains the source.
470
+ # - Argument +proc+, if given, must be a \Proc that accepts one argument.
471
+ # It will be called recursively with each result (depth-first order).
472
+ # See details below.
473
+ # - Argument +opts+, if given, contains a \Hash of options for the parsing.
474
+ # See {Parsing Options}[#module-JSON-label-Parsing+Options].
475
+ # The default options can be changed via method JSON.unsafe_load_default_options=.
476
+ #
477
+ # ---
478
+ #
479
+ # When no +proc+ is given, modifies +source+ as above and returns the result of
480
+ # <tt>parse(source, opts)</tt>; see #parse.
481
+ #
482
+ # Source for following examples:
483
+ # source = <<~JSON
484
+ # {
485
+ # "name": "Dave",
486
+ # "age" :40,
487
+ # "hats": [
488
+ # "Cattleman's",
489
+ # "Panama",
490
+ # "Tophat"
491
+ # ]
492
+ # }
493
+ # JSON
494
+ #
495
+ # Load a \String:
496
+ # ruby = JSON.unsafe_load(source)
497
+ # ruby # => {"name"=>"Dave", "age"=>40, "hats"=>["Cattleman's", "Panama", "Tophat"]}
498
+ #
499
+ # Load an \IO object:
500
+ # require 'stringio'
501
+ # object = JSON.unsafe_load(StringIO.new(source))
502
+ # object # => {"name"=>"Dave", "age"=>40, "hats"=>["Cattleman's", "Panama", "Tophat"]}
503
+ #
504
+ # Load a \File object:
505
+ # path = 't.json'
506
+ # File.write(path, source)
507
+ # File.open(path) do |file|
508
+ # JSON.unsafe_load(file)
509
+ # end # => {"name"=>"Dave", "age"=>40, "hats"=>["Cattleman's", "Panama", "Tophat"]}
510
+ #
511
+ # ---
512
+ #
513
+ # When +proc+ is given:
514
+ # - Modifies +source+ as above.
515
+ # - Gets the +result+ from calling <tt>parse(source, opts)</tt>.
516
+ # - Recursively calls <tt>proc(result)</tt>.
517
+ # - Returns the final result.
518
+ #
519
+ # Example:
520
+ # require 'json'
521
+ #
522
+ # # Some classes for the example.
523
+ # class Base
524
+ # def initialize(attributes)
525
+ # @attributes = attributes
526
+ # end
527
+ # end
528
+ # class User < Base; end
529
+ # class Account < Base; end
530
+ # class Admin < Base; end
531
+ # # The JSON source.
532
+ # json = <<-EOF
533
+ # {
534
+ # "users": [
535
+ # {"type": "User", "username": "jane", "email": "jane@example.com"},
536
+ # {"type": "User", "username": "john", "email": "john@example.com"}
537
+ # ],
538
+ # "accounts": [
539
+ # {"account": {"type": "Account", "paid": true, "account_id": "1234"}},
540
+ # {"account": {"type": "Account", "paid": false, "account_id": "1235"}}
541
+ # ],
542
+ # "admins": {"type": "Admin", "password": "0wn3d"}
543
+ # }
544
+ # EOF
545
+ # # Deserializer method.
546
+ # def deserialize_obj(obj, safe_types = %w(User Account Admin))
547
+ # type = obj.is_a?(Hash) && obj["type"]
548
+ # safe_types.include?(type) ? Object.const_get(type).new(obj) : obj
549
+ # end
550
+ # # Call to JSON.unsafe_load
551
+ # ruby = JSON.unsafe_load(json, proc {|obj|
552
+ # case obj
553
+ # when Hash
554
+ # obj.each {|k, v| obj[k] = deserialize_obj v }
555
+ # when Array
556
+ # obj.map! {|v| deserialize_obj v }
557
+ # end
558
+ # })
559
+ # pp ruby
560
+ # Output:
561
+ # {"users"=>
562
+ # [#<User:0x00000000064c4c98
563
+ # @attributes=
564
+ # {"type"=>"User", "username"=>"jane", "email"=>"jane@example.com"}>,
565
+ # #<User:0x00000000064c4bd0
566
+ # @attributes=
567
+ # {"type"=>"User", "username"=>"john", "email"=>"john@example.com"}>],
568
+ # "accounts"=>
569
+ # [{"account"=>
570
+ # #<Account:0x00000000064c4928
571
+ # @attributes={"type"=>"Account", "paid"=>true, "account_id"=>"1234"}>},
572
+ # {"account"=>
573
+ # #<Account:0x00000000064c4680
574
+ # @attributes={"type"=>"Account", "paid"=>false, "account_id"=>"1235"}>}],
575
+ # "admins"=>
576
+ # #<Admin:0x00000000064c41f8
577
+ # @attributes={"type"=>"Admin", "password"=>"0wn3d"}>}
578
+ #
579
+ def unsafe_load(source, proc = nil, options = nil)
580
+ opts = if options.nil?
581
+ unsafe_load_default_options
582
+ else
583
+ unsafe_load_default_options.merge(options)
584
+ end
585
+
586
+ unless source.is_a?(String)
587
+ if source.respond_to? :to_str
588
+ source = source.to_str
589
+ elsif source.respond_to? :to_io
590
+ source = source.to_io.read
591
+ elsif source.respond_to?(:read)
592
+ source = source.read
593
+ end
594
+ end
595
+
596
+ if opts[:allow_blank] && (source.nil? || source.empty?)
597
+ source = 'null'
598
+ end
599
+ result = parse(source, opts)
600
+ recurse_proc(result, &proc) if proc
601
+ result
602
+ end
428
603
 
429
604
  # :call-seq:
430
605
  # JSON.load(source, proc = nil, options = {}) -> object
431
606
  #
432
607
  # Returns the Ruby objects created by parsing the given +source+.
433
608
  #
609
+ # BEWARE: This method is meant to serialise data from trusted user input,
610
+ # like from your own database server or clients under your control, it could
611
+ # be dangerous to allow untrusted users to pass JSON sources into it.
612
+ # If you must use it, use JSON.unsafe_load instead to make it clear.
613
+ #
614
+ # Since JSON version 2.8.0, `load` emits a deprecation warning when a
615
+ # non native type is deserialized, without `create_additions` being explicitly
616
+ # enabled, and in JSON version 3.0, `load` will have `create_additions` disabled
617
+ # by default.
618
+ #
434
619
  # - Argument +source+ must be, or be convertible to, a \String:
435
620
  # - If +source+ responds to instance method +to_str+,
436
621
  # <tt>source.to_str</tt> becomes the source.
@@ -445,9 +630,6 @@ module JSON
445
630
  # - Argument +proc+, if given, must be a \Proc that accepts one argument.
446
631
  # It will be called recursively with each result (depth-first order).
447
632
  # 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
633
  # - Argument +opts+, if given, contains a \Hash of options for the parsing.
452
634
  # See {Parsing Options}[#module-JSON-label-Parsing+Options].
453
635
  # The default options can be changed via method JSON.load_default_options=.
@@ -458,17 +640,17 @@ module JSON
458
640
  # <tt>parse(source, opts)</tt>; see #parse.
459
641
  #
460
642
  # Source for following examples:
461
- # source = <<-EOT
462
- # {
463
- # "name": "Dave",
464
- # "age" :40,
465
- # "hats": [
466
- # "Cattleman's",
467
- # "Panama",
468
- # "Tophat"
469
- # ]
470
- # }
471
- # EOT
643
+ # source = <<~JSON
644
+ # {
645
+ # "name": "Dave",
646
+ # "age" :40,
647
+ # "hats": [
648
+ # "Cattleman's",
649
+ # "Panama",
650
+ # "Tophat"
651
+ # ]
652
+ # }
653
+ # JSON
472
654
  #
473
655
  # Load a \String:
474
656
  # ruby = JSON.load(source)
@@ -554,15 +736,23 @@ module JSON
554
736
  # #<Admin:0x00000000064c41f8
555
737
  # @attributes={"type"=>"Admin", "password"=>"0wn3d"}>}
556
738
  #
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
739
+ def load(source, proc = nil, options = nil)
740
+ opts = if options.nil?
741
+ load_default_options
742
+ else
743
+ load_default_options.merge(options)
565
744
  end
745
+
746
+ unless source.is_a?(String)
747
+ if source.respond_to? :to_str
748
+ source = source.to_str
749
+ elsif source.respond_to? :to_io
750
+ source = source.to_io.read
751
+ elsif source.respond_to?(:read)
752
+ source = source.read
753
+ end
754
+ end
755
+
566
756
  if opts[:allow_blank] && (source.nil? || source.empty?)
567
757
  source = 'null'
568
758
  end
@@ -592,13 +782,12 @@ module JSON
592
782
  # Sets or returns the default options for the JSON.dump method.
593
783
  # Initially:
594
784
  # opts = JSON.dump_default_options
595
- # opts # => {:max_nesting=>false, :allow_nan=>true, :escape_slash=>false}
785
+ # opts # => {:max_nesting=>false, :allow_nan=>true}
596
786
  attr_accessor :dump_default_options
597
787
  end
598
788
  self.dump_default_options = {
599
789
  :max_nesting => false,
600
790
  :allow_nan => true,
601
- :escape_slash => false,
602
791
  }
603
792
 
604
793
  # :call-seq:
@@ -628,31 +817,127 @@ module JSON
628
817
  # puts File.read(path)
629
818
  # Output:
630
819
  # {"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
820
+ def dump(obj, anIO = nil, limit = nil, kwargs = nil)
821
+ if kwargs.nil?
822
+ if limit.nil?
823
+ if anIO.is_a?(Hash)
824
+ kwargs = anIO
825
+ anIO = nil
826
+ end
827
+ elsif limit.is_a?(Hash)
828
+ kwargs = limit
829
+ limit = nil
830
+ end
831
+ end
832
+
833
+ unless anIO.nil?
834
+ if anIO.respond_to?(:to_io)
835
+ anIO = anIO.to_io
836
+ elsif limit.nil? && !anIO.respond_to?(:write)
837
+ anIO, limit = nil, anIO
637
838
  end
638
839
  end
840
+
639
841
  opts = JSON.dump_default_options
640
842
  opts = opts.merge(:max_nesting => limit) if limit
641
- result = generate(obj, opts)
642
- if anIO
643
- anIO.write result
644
- anIO
645
- else
646
- result
843
+ opts = merge_dump_options(opts, **kwargs) if kwargs
844
+
845
+ begin
846
+ State.generate(obj, opts, anIO)
847
+ rescue JSON::NestingError
848
+ raise ArgumentError, "exceed depth limit"
647
849
  end
648
- rescue JSON::NestingError
649
- raise ArgumentError, "exceed depth limit"
650
850
  end
651
851
 
652
852
  # Encodes string using String.encode.
653
853
  def self.iconv(to, from, string)
654
854
  string.encode(to, from)
655
855
  end
856
+
857
+ def merge_dump_options(opts, strict: NOT_SET)
858
+ opts = opts.merge(strict: strict) if NOT_SET != strict
859
+ opts
860
+ end
861
+
862
+ class << self
863
+ private :merge_dump_options
864
+ end
865
+
866
+ # JSON::Coder holds a parser and generator configuration.
867
+ #
868
+ # module MyApp
869
+ # JSONC_CODER = JSON::Coder.new(
870
+ # allow_trailing_comma: true
871
+ # )
872
+ # end
873
+ #
874
+ # MyApp::JSONC_CODER.load(document)
875
+ #
876
+ class Coder
877
+ # :call-seq:
878
+ # JSON.new(options = nil, &block)
879
+ #
880
+ # Argument +options+, if given, contains a \Hash of options for both parsing and generating.
881
+ # See {Parsing Options}[#module-JSON-label-Parsing+Options], and {Generating Options}[#module-JSON-label-Generating+Options].
882
+ #
883
+ # For generation, the <tt>strict: true</tt> option is always set. When a Ruby object with no native \JSON counterpart is
884
+ # encoutered, the block provided to the initialize method is invoked, and must return a Ruby object that has a native
885
+ # \JSON counterpart:
886
+ #
887
+ # module MyApp
888
+ # API_JSON_CODER = JSON::Coder.new do |object|
889
+ # case object
890
+ # when Time
891
+ # object.iso8601(3)
892
+ # else
893
+ # object # Unknown type, will raise
894
+ # end
895
+ # end
896
+ # end
897
+ #
898
+ # puts MyApp::API_JSON_CODER.dump(Time.now.utc) # => "2025-01-21T08:41:44.286Z"
899
+ #
900
+ def initialize(options = nil, &as_json)
901
+ if options.nil?
902
+ options = { strict: true }
903
+ else
904
+ options = options.dup
905
+ options[:strict] = true
906
+ end
907
+ options[:as_json] = as_json if as_json
908
+ options[:create_additions] = false unless options.key?(:create_additions)
909
+
910
+ @state = State.new(options).freeze
911
+ @parser_config = Ext::Parser::Config.new(options)
912
+ end
913
+
914
+ # call-seq:
915
+ # dump(object) -> String
916
+ # dump(object, io) -> io
917
+ #
918
+ # Serialize the given object into a \JSON document.
919
+ def dump(object, io = nil)
920
+ @state.generate_new(object, io)
921
+ end
922
+ alias_method :generate, :dump
923
+
924
+ # call-seq:
925
+ # load(string) -> Object
926
+ #
927
+ # Parse the given \JSON document and return an equivalent Ruby object.
928
+ def load(source)
929
+ @parser_config.parse(source)
930
+ end
931
+ alias_method :parse, :load
932
+
933
+ # call-seq:
934
+ # load(path) -> Object
935
+ #
936
+ # Parse the given \JSON document and return an equivalent Ruby object.
937
+ def load_file(path)
938
+ load(File.read(path, encoding: Encoding::UTF_8))
939
+ end
940
+ end
656
941
  end
657
942
 
658
943
  module ::Kernel
@@ -683,11 +968,16 @@ module ::Kernel
683
968
  # The _opts_ argument is passed through to generate/parse respectively. See
684
969
  # generate and parse for their documentation.
685
970
  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)
971
+ if object.is_a?(String)
972
+ return JSON.parse(object, args.first)
973
+ elsif object.respond_to?(:to_str)
974
+ str = object.to_str
975
+ if str.is_a?(String)
976
+ return JSON.parse(object.to_str, args.first)
977
+ end
690
978
  end
979
+
980
+ JSON.generate(object, args.first)
691
981
  end
692
982
  end
693
983