json 2.7.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.
data/lib/json/common.rb CHANGED
@@ -1,4 +1,5 @@
1
- #frozen_string_literal: false
1
+ # frozen_string_literal: true
2
+
2
3
  require 'json/version'
3
4
 
4
5
  module JSON
@@ -20,16 +21,19 @@ module JSON
20
21
  # ruby = [0, 1, nil]
21
22
  # JSON[ruby] # => '[0,1,null]'
22
23
  def [](object, opts = {})
23
- if object.respond_to? :to_str
24
- JSON.parse(object.to_str, opts)
25
- else
26
- 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
27
31
  end
32
+
33
+ JSON.generate(object, opts)
28
34
  end
29
35
 
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
36
+ # Returns the JSON parser class that is used by JSON.
33
37
  attr_reader :parser
34
38
 
35
39
  # Set the JSON parser class _parser_ to be used by JSON.
@@ -44,18 +48,9 @@ module JSON
44
48
  # level (absolute namespace path?). If there doesn't exist a constant at
45
49
  # the given path, an ArgumentError is raised.
46
50
  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
51
+ Object.const_get(path)
52
+ rescue NameError => e
53
+ raise ArgumentError, "can't get const #{path}: #{e}"
59
54
  end
60
55
 
61
56
  # Set the module _generator_ to be used by JSON.
@@ -64,7 +59,7 @@ module JSON
64
59
  @generator = generator
65
60
  generator_methods = generator::GeneratorMethods
66
61
  for const in generator_methods.constants
67
- klass = deep_const_get(const)
62
+ klass = const_get(const)
68
63
  modul = generator_methods.const_get(const)
69
64
  klass.class_eval do
70
65
  instance_methods(false).each do |m|
@@ -101,34 +96,24 @@ module JSON
101
96
  )
102
97
  end
103
98
 
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
99
+ # Returns the JSON generator module that is used by JSON.
107
100
  attr_reader :generator
108
101
 
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
102
+ # Sets or Returns the JSON generator state class that is used by JSON.
112
103
  attr_accessor :state
113
104
  end
114
105
 
115
- DEFAULT_CREATE_ID = 'json_class'.freeze
116
- private_constant :DEFAULT_CREATE_ID
117
-
118
- CREATE_ID_TLS_KEY = "JSON.create_id".freeze
119
- private_constant :CREATE_ID_TLS_KEY
120
-
121
106
  # Sets create identifier, which is used to decide if the _json_create_
122
107
  # hook of a class should be called; initial value is +json_class+:
123
108
  # JSON.create_id # => 'json_class'
124
109
  def self.create_id=(new_value)
125
- Thread.current[CREATE_ID_TLS_KEY] = new_value.dup.freeze
110
+ Thread.current[:"JSON.create_id"] = new_value.dup.freeze
126
111
  end
127
112
 
128
113
  # Returns the current create identifier.
129
114
  # See also JSON.create_id=.
130
115
  def self.create_id
131
- Thread.current[CREATE_ID_TLS_KEY] || DEFAULT_CREATE_ID
116
+ Thread.current[:"JSON.create_id"] || 'json_class'
132
117
  end
133
118
 
134
119
  NaN = 0.0/0
@@ -158,7 +143,23 @@ module JSON
158
143
  # :startdoc:
159
144
 
160
145
  # This exception is raised if a generator or unparser error occurs.
161
- 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
+
162
163
  # For backwards compatibility
163
164
  UnparserError = GeneratorError # :nodoc:
164
165
 
@@ -166,6 +167,30 @@ module JSON
166
167
  # system. Usually this means that the iconv library is not installed.
167
168
  class MissingUnicodeSupport < JSONError; end
168
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
+
169
194
  module_function
170
195
 
171
196
  # :call-seq:
@@ -196,17 +221,17 @@ module JSON
196
221
  # {Parsing \JSON}[#module-JSON-label-Parsing+JSON].
197
222
  #
198
223
  # 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
224
+ # source = <<~JSON
225
+ # {
226
+ # "name": "Dave",
227
+ # "age" :40,
228
+ # "hats": [
229
+ # "Cattleman's",
230
+ # "Panama",
231
+ # "Tophat"
232
+ # ]
233
+ # }
234
+ # JSON
210
235
  # ruby = JSON.parse(source)
211
236
  # ruby # => {"name"=>"Dave", "age"=>40, "hats"=>["Cattleman's", "Panama", "Tophat"]}
212
237
  #
@@ -216,8 +241,8 @@ module JSON
216
241
  # # Raises JSON::ParserError (783: unexpected token at ''):
217
242
  # JSON.parse('')
218
243
  #
219
- def parse(source, opts = {})
220
- Parser.new(source, **(opts||{})).parse
244
+ def parse(source, opts = nil)
245
+ Parser.parse(source, opts)
221
246
  end
222
247
 
223
248
  # :call-seq:
@@ -231,12 +256,13 @@ module JSON
231
256
  # - Option +max_nesting+, if not provided, defaults to +false+,
232
257
  # which disables checking for nesting depth.
233
258
  # - Option +allow_nan+, if not provided, defaults to +true+.
234
- def parse!(source, opts = {})
235
- opts = {
259
+ def parse!(source, opts = nil)
260
+ options = {
236
261
  :max_nesting => false,
237
262
  :allow_nan => true
238
- }.merge(opts)
239
- Parser.new(source, **(opts||{})).parse
263
+ }
264
+ options.merge!(opts) if opts
265
+ Parser.new(source, options).parse
240
266
  end
241
267
 
242
268
  # :call-seq:
@@ -246,8 +272,8 @@ module JSON
246
272
  # parse(File.read(path), opts)
247
273
  #
248
274
  # See method #parse.
249
- def load_file(filespec, opts = {})
250
- parse(File.read(filespec), opts)
275
+ def load_file(filespec, opts = nil)
276
+ parse(File.read(filespec, encoding: Encoding::UTF_8), opts)
251
277
  end
252
278
 
253
279
  # :call-seq:
@@ -257,8 +283,8 @@ module JSON
257
283
  # JSON.parse!(File.read(path, opts))
258
284
  #
259
285
  # See method #parse!
260
- def load_file!(filespec, opts = {})
261
- parse!(File.read(filespec), opts)
286
+ def load_file!(filespec, opts = nil)
287
+ parse!(File.read(filespec, encoding: Encoding::UTF_8), opts)
262
288
  end
263
289
 
264
290
  # :call-seq:
@@ -299,11 +325,10 @@ module JSON
299
325
  #
300
326
  def generate(obj, opts = nil)
301
327
  if State === opts
302
- state = opts
328
+ opts.generate(obj)
303
329
  else
304
- state = State.new(opts)
330
+ State.generate(obj, opts, nil)
305
331
  end
306
- state.generate(obj)
307
332
  end
308
333
 
309
334
  # :stopdoc:
@@ -396,6 +421,20 @@ module JSON
396
421
  module_function :pretty_unparse
397
422
  # :startdoc:
398
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
+
399
438
  class << self
400
439
  # Sets or returns default options for the JSON.load method.
401
440
  # Initially:
@@ -404,17 +443,179 @@ module JSON
404
443
  attr_accessor :load_default_options
405
444
  end
406
445
  self.load_default_options = {
407
- :max_nesting => false,
408
446
  :allow_nan => true,
409
- :allow_blank => true,
410
- :create_additions => true,
447
+ :allow_blank => true,
448
+ :create_additions => nil,
411
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
412
603
 
413
604
  # :call-seq:
414
605
  # JSON.load(source, proc = nil, options = {}) -> object
415
606
  #
416
607
  # Returns the Ruby objects created by parsing the given +source+.
417
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
+ #
418
619
  # - Argument +source+ must be, or be convertible to, a \String:
419
620
  # - If +source+ responds to instance method +to_str+,
420
621
  # <tt>source.to_str</tt> becomes the source.
@@ -429,9 +630,6 @@ module JSON
429
630
  # - Argument +proc+, if given, must be a \Proc that accepts one argument.
430
631
  # It will be called recursively with each result (depth-first order).
431
632
  # 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
633
  # - Argument +opts+, if given, contains a \Hash of options for the parsing.
436
634
  # See {Parsing Options}[#module-JSON-label-Parsing+Options].
437
635
  # The default options can be changed via method JSON.load_default_options=.
@@ -442,17 +640,17 @@ module JSON
442
640
  # <tt>parse(source, opts)</tt>; see #parse.
443
641
  #
444
642
  # Source for following examples:
445
- # source = <<-EOT
446
- # {
447
- # "name": "Dave",
448
- # "age" :40,
449
- # "hats": [
450
- # "Cattleman's",
451
- # "Panama",
452
- # "Tophat"
453
- # ]
454
- # }
455
- # EOT
643
+ # source = <<~JSON
644
+ # {
645
+ # "name": "Dave",
646
+ # "age" :40,
647
+ # "hats": [
648
+ # "Cattleman's",
649
+ # "Panama",
650
+ # "Tophat"
651
+ # ]
652
+ # }
653
+ # JSON
456
654
  #
457
655
  # Load a \String:
458
656
  # ruby = JSON.load(source)
@@ -538,15 +736,23 @@ module JSON
538
736
  # #<Admin:0x00000000064c41f8
539
737
  # @attributes={"type"=>"Admin", "password"=>"0wn3d"}>}
540
738
  #
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
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)
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
549
754
  end
755
+
550
756
  if opts[:allow_blank] && (source.nil? || source.empty?)
551
757
  source = 'null'
552
758
  end
@@ -576,13 +782,12 @@ module JSON
576
782
  # Sets or returns the default options for the JSON.dump method.
577
783
  # Initially:
578
784
  # opts = JSON.dump_default_options
579
- # opts # => {:max_nesting=>false, :allow_nan=>true, :script_safe=>false}
785
+ # opts # => {:max_nesting=>false, :allow_nan=>true}
580
786
  attr_accessor :dump_default_options
581
787
  end
582
788
  self.dump_default_options = {
583
789
  :max_nesting => false,
584
790
  :allow_nan => true,
585
- :script_safe => false,
586
791
  }
587
792
 
588
793
  # :call-seq:
@@ -613,26 +818,35 @@ module JSON
613
818
  # Output:
614
819
  # {"foo":[0,1],"bar":{"baz":2,"bat":3},"bam":"bad"}
615
820
  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
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
623
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
838
+ end
839
+ end
840
+
624
841
  opts = JSON.dump_default_options
625
842
  opts = opts.merge(:max_nesting => limit) if limit
626
843
  opts = merge_dump_options(opts, **kwargs) if kwargs
627
- result = generate(obj, opts)
628
- if anIO
629
- anIO.write result
630
- anIO
631
- else
632
- result
844
+
845
+ begin
846
+ State.generate(obj, opts, anIO)
847
+ rescue JSON::NestingError
848
+ raise ArgumentError, "exceed depth limit"
633
849
  end
634
- rescue JSON::NestingError
635
- raise ArgumentError, "exceed depth limit"
636
850
  end
637
851
 
638
852
  # Encodes string using String.encode.
@@ -648,6 +862,82 @@ module JSON
648
862
  class << self
649
863
  private :merge_dump_options
650
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
651
941
  end
652
942
 
653
943
  module ::Kernel
@@ -678,11 +968,16 @@ module ::Kernel
678
968
  # The _opts_ argument is passed through to generate/parse respectively. See
679
969
  # generate and parse for their documentation.
680
970
  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)
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
685
978
  end
979
+
980
+ JSON.generate(object, args.first)
686
981
  end
687
982
  end
688
983