json 1.2.0 → 2.19.8

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 (145) hide show
  1. checksums.yaml +7 -0
  2. data/BSDL +22 -0
  3. data/CHANGES.md +783 -0
  4. data/COPYING +14 -16
  5. data/LEGAL +20 -0
  6. data/README.md +310 -0
  7. data/ext/json/ext/fbuffer/fbuffer.h +260 -0
  8. data/ext/json/ext/generator/extconf.rb +15 -8
  9. data/ext/json/ext/generator/generator.c +1675 -613
  10. data/ext/json/ext/json.h +116 -0
  11. data/ext/json/ext/parser/extconf.rb +16 -7
  12. data/ext/json/ext/parser/parser.c +1649 -1772
  13. data/ext/json/ext/simd/conf.rb +24 -0
  14. data/ext/json/ext/simd/simd.h +208 -0
  15. data/ext/json/ext/vendor/fpconv.c +480 -0
  16. data/ext/json/ext/vendor/jeaiii-ltoa.h +267 -0
  17. data/ext/json/ext/vendor/ryu.h +819 -0
  18. data/json.gemspec +62 -0
  19. data/lib/json/add/bigdecimal.rb +58 -0
  20. data/lib/json/add/complex.rb +51 -0
  21. data/lib/json/add/core.rb +11 -133
  22. data/lib/json/add/date.rb +54 -0
  23. data/lib/json/add/date_time.rb +67 -0
  24. data/lib/json/add/exception.rb +49 -0
  25. data/lib/json/add/ostruct.rb +54 -0
  26. data/lib/json/add/range.rb +54 -0
  27. data/lib/json/add/rational.rb +49 -0
  28. data/lib/json/add/regexp.rb +48 -0
  29. data/lib/json/add/set.rb +48 -0
  30. data/lib/json/add/string.rb +35 -0
  31. data/lib/json/add/struct.rb +52 -0
  32. data/lib/json/add/symbol.rb +52 -0
  33. data/lib/json/add/time.rb +52 -0
  34. data/lib/json/common.rb +1056 -254
  35. data/lib/json/ext/generator/state.rb +103 -0
  36. data/lib/json/ext.rb +35 -5
  37. data/lib/json/generic_object.rb +67 -0
  38. data/lib/json/truffle_ruby/generator.rb +755 -0
  39. data/lib/json/version.rb +3 -6
  40. data/lib/json.rb +671 -6
  41. metadata +68 -159
  42. data/CHANGES +0 -136
  43. data/GPL +0 -340
  44. data/README +0 -360
  45. data/Rakefile +0 -287
  46. data/TODO +0 -1
  47. data/VERSION +0 -1
  48. data/benchmarks/data-p4-3GHz-ruby18/GeneratorBenchmarkComparison.log +0 -52
  49. data/benchmarks/data-p4-3GHz-ruby18/GeneratorBenchmarkExt#generator_fast-autocorrelation.dat +0 -1000
  50. data/benchmarks/data-p4-3GHz-ruby18/GeneratorBenchmarkExt#generator_fast.dat +0 -1001
  51. data/benchmarks/data-p4-3GHz-ruby18/GeneratorBenchmarkExt#generator_pretty-autocorrelation.dat +0 -900
  52. data/benchmarks/data-p4-3GHz-ruby18/GeneratorBenchmarkExt#generator_pretty.dat +0 -901
  53. data/benchmarks/data-p4-3GHz-ruby18/GeneratorBenchmarkExt#generator_safe-autocorrelation.dat +0 -1000
  54. data/benchmarks/data-p4-3GHz-ruby18/GeneratorBenchmarkExt#generator_safe.dat +0 -1001
  55. data/benchmarks/data-p4-3GHz-ruby18/GeneratorBenchmarkExt.log +0 -261
  56. data/benchmarks/data-p4-3GHz-ruby18/GeneratorBenchmarkPure#generator_fast-autocorrelation.dat +0 -1000
  57. data/benchmarks/data-p4-3GHz-ruby18/GeneratorBenchmarkPure#generator_fast.dat +0 -1001
  58. data/benchmarks/data-p4-3GHz-ruby18/GeneratorBenchmarkPure#generator_pretty-autocorrelation.dat +0 -1000
  59. data/benchmarks/data-p4-3GHz-ruby18/GeneratorBenchmarkPure#generator_pretty.dat +0 -1001
  60. data/benchmarks/data-p4-3GHz-ruby18/GeneratorBenchmarkPure#generator_safe-autocorrelation.dat +0 -1000
  61. data/benchmarks/data-p4-3GHz-ruby18/GeneratorBenchmarkPure#generator_safe.dat +0 -1001
  62. data/benchmarks/data-p4-3GHz-ruby18/GeneratorBenchmarkPure.log +0 -262
  63. data/benchmarks/data-p4-3GHz-ruby18/GeneratorBenchmarkRails#generator-autocorrelation.dat +0 -1000
  64. data/benchmarks/data-p4-3GHz-ruby18/GeneratorBenchmarkRails#generator.dat +0 -1001
  65. data/benchmarks/data-p4-3GHz-ruby18/GeneratorBenchmarkRails.log +0 -82
  66. data/benchmarks/data-p4-3GHz-ruby18/ParserBenchmarkComparison.log +0 -34
  67. data/benchmarks/data-p4-3GHz-ruby18/ParserBenchmarkExt#parser-autocorrelation.dat +0 -900
  68. data/benchmarks/data-p4-3GHz-ruby18/ParserBenchmarkExt#parser.dat +0 -901
  69. data/benchmarks/data-p4-3GHz-ruby18/ParserBenchmarkExt.log +0 -81
  70. data/benchmarks/data-p4-3GHz-ruby18/ParserBenchmarkPure#parser-autocorrelation.dat +0 -1000
  71. data/benchmarks/data-p4-3GHz-ruby18/ParserBenchmarkPure#parser.dat +0 -1001
  72. data/benchmarks/data-p4-3GHz-ruby18/ParserBenchmarkPure.log +0 -82
  73. data/benchmarks/data-p4-3GHz-ruby18/ParserBenchmarkRails#parser-autocorrelation.dat +0 -1000
  74. data/benchmarks/data-p4-3GHz-ruby18/ParserBenchmarkRails#parser.dat +0 -1001
  75. data/benchmarks/data-p4-3GHz-ruby18/ParserBenchmarkRails.log +0 -82
  76. data/benchmarks/data-p4-3GHz-ruby18/ParserBenchmarkYAML#parser-autocorrelation.dat +0 -1000
  77. data/benchmarks/data-p4-3GHz-ruby18/ParserBenchmarkYAML#parser.dat +0 -1001
  78. data/benchmarks/data-p4-3GHz-ruby18/ParserBenchmarkYAML.log +0 -82
  79. data/benchmarks/generator_benchmark.rb +0 -165
  80. data/benchmarks/parser_benchmark.rb +0 -197
  81. data/bin/edit_json.rb +0 -9
  82. data/bin/prettify_json.rb +0 -75
  83. data/data/example.json +0 -1
  84. data/data/index.html +0 -38
  85. data/data/prototype.js +0 -4184
  86. data/ext/json/ext/generator/unicode.c +0 -180
  87. data/ext/json/ext/generator/unicode.h +0 -53
  88. data/ext/json/ext/parser/parser.rl +0 -737
  89. data/ext/json/ext/parser/unicode.c +0 -154
  90. data/ext/json/ext/parser/unicode.h +0 -58
  91. data/install.rb +0 -26
  92. data/lib/json/Array.xpm +0 -21
  93. data/lib/json/FalseClass.xpm +0 -21
  94. data/lib/json/Hash.xpm +0 -21
  95. data/lib/json/Key.xpm +0 -73
  96. data/lib/json/NilClass.xpm +0 -21
  97. data/lib/json/Numeric.xpm +0 -28
  98. data/lib/json/String.xpm +0 -96
  99. data/lib/json/TrueClass.xpm +0 -21
  100. data/lib/json/add/rails.rb +0 -58
  101. data/lib/json/editor.rb +0 -1371
  102. data/lib/json/json.xpm +0 -1499
  103. data/lib/json/pure/generator.rb +0 -443
  104. data/lib/json/pure/parser.rb +0 -303
  105. data/lib/json/pure.rb +0 -77
  106. data/tests/fixtures/fail1.json +0 -1
  107. data/tests/fixtures/fail10.json +0 -1
  108. data/tests/fixtures/fail11.json +0 -1
  109. data/tests/fixtures/fail12.json +0 -1
  110. data/tests/fixtures/fail13.json +0 -1
  111. data/tests/fixtures/fail14.json +0 -1
  112. data/tests/fixtures/fail18.json +0 -1
  113. data/tests/fixtures/fail19.json +0 -1
  114. data/tests/fixtures/fail2.json +0 -1
  115. data/tests/fixtures/fail20.json +0 -1
  116. data/tests/fixtures/fail21.json +0 -1
  117. data/tests/fixtures/fail22.json +0 -1
  118. data/tests/fixtures/fail23.json +0 -1
  119. data/tests/fixtures/fail24.json +0 -1
  120. data/tests/fixtures/fail25.json +0 -1
  121. data/tests/fixtures/fail27.json +0 -2
  122. data/tests/fixtures/fail28.json +0 -2
  123. data/tests/fixtures/fail3.json +0 -1
  124. data/tests/fixtures/fail4.json +0 -1
  125. data/tests/fixtures/fail5.json +0 -1
  126. data/tests/fixtures/fail6.json +0 -1
  127. data/tests/fixtures/fail7.json +0 -1
  128. data/tests/fixtures/fail8.json +0 -1
  129. data/tests/fixtures/fail9.json +0 -1
  130. data/tests/fixtures/pass1.json +0 -56
  131. data/tests/fixtures/pass15.json +0 -1
  132. data/tests/fixtures/pass16.json +0 -1
  133. data/tests/fixtures/pass17.json +0 -1
  134. data/tests/fixtures/pass2.json +0 -1
  135. data/tests/fixtures/pass26.json +0 -1
  136. data/tests/fixtures/pass3.json +0 -6
  137. data/tests/test_json.rb +0 -320
  138. data/tests/test_json_addition.rb +0 -164
  139. data/tests/test_json_encoding.rb +0 -67
  140. data/tests/test_json_fixtures.rb +0 -34
  141. data/tests/test_json_generate.rb +0 -120
  142. data/tests/test_json_rails.rb +0 -146
  143. data/tests/test_json_unicode.rb +0 -62
  144. data/tools/fuzz.rb +0 -139
  145. data/tools/server.rb +0 -61
data/lib/json/common.rb CHANGED
@@ -1,326 +1,1126 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'json/version'
2
- require 'iconv'
3
4
 
4
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: extract :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_path = object[JSON.create_id]
75
+ klass = begin
76
+ Object.const_get(class_path)
77
+ rescue NameError => e
78
+ raise ArgumentError, "can't get const #{class_path}: #{e}"
79
+ end
80
+
81
+ if klass.respond_to?(:json_creatable?) ? klass.json_creatable? : klass.respond_to?(:json_create)
82
+ create_additions_warning if create_additions.nil?
83
+ object = klass.json_create(object)
84
+ end
85
+ end
86
+ end
87
+ end
88
+
89
+ on_load.nil? ? object : on_load.call(object)
90
+ end
91
+
92
+ opts
93
+ end
94
+
95
+ def create_additions_warning
96
+ JSON.deprecation_warning "JSON.load implicit support for `create_additions: true` is deprecated " \
97
+ "and will be removed in 3.0, use JSON.unsafe_load or explicitly " \
98
+ "pass `create_additions: true`"
99
+ end
100
+ end
101
+ end
102
+
5
103
  class << self
6
- # If _object_ is string-like parse the string and return the parsed result
7
- # as a Ruby data structure. Otherwise generate a JSON text from the Ruby
8
- # data structure object and return it.
9
- #
10
- # The _opts_ argument is passed through to generate/parse respectively, see
11
- # generate and parse for their documentation.
12
- def [](object, opts = {})
13
- if object.respond_to? :to_str
14
- JSON.parse(object.to_str, opts => {})
104
+ def deprecation_warning(message, uplevel = 3) # :nodoc:
105
+ gem_root = File.expand_path("..", __dir__) + "/"
106
+ caller_locations(uplevel, 10).each do |frame|
107
+ if frame.path.nil? || frame.path.start_with?(gem_root) || frame.path.end_with?("/truffle/cext_ruby.rb", ".c")
108
+ uplevel += 1
109
+ else
110
+ break
111
+ end
112
+ end
113
+
114
+ if RUBY_VERSION >= "3.0"
115
+ warn(message, uplevel: uplevel, category: :deprecated)
15
116
  else
16
- JSON.generate(object, opts => {})
117
+ warn(message, uplevel: uplevel)
17
118
  end
18
119
  end
19
120
 
20
- # Returns the JSON parser class, that is used by JSON. This might be either
21
- # JSON::Ext::Parser or JSON::Pure::Parser.
121
+ # :call-seq:
122
+ # JSON[object] -> new_array or new_string
123
+ #
124
+ # If +object+ is a \String,
125
+ # calls JSON.parse with +object+ and +opts+ (see method #parse):
126
+ # json = '[0, 1, null]'
127
+ # JSON[json]# => [0, 1, nil]
128
+ #
129
+ # Otherwise, calls JSON.generate with +object+ and +opts+ (see method #generate):
130
+ # ruby = [0, 1, nil]
131
+ # JSON[ruby] # => '[0,1,null]'
132
+ def [](object, opts = nil)
133
+ if object.is_a?(String)
134
+ return JSON.parse(object, opts)
135
+ elsif object.respond_to?(:to_str)
136
+ str = object.to_str
137
+ if str.is_a?(String)
138
+ return JSON.parse(str, opts)
139
+ end
140
+ end
141
+
142
+ JSON.generate(object, opts)
143
+ end
144
+
145
+ # Returns the JSON parser class that is used by JSON.
22
146
  attr_reader :parser
23
147
 
24
148
  # Set the JSON parser class _parser_ to be used by JSON.
25
149
  def parser=(parser) # :nodoc:
26
150
  @parser = parser
27
- remove_const :Parser if const_defined? :Parser
151
+ remove_const :Parser if const_defined?(:Parser, false)
28
152
  const_set :Parser, parser
29
153
  end
30
154
 
31
- # Return the constant located at _path_. The format of _path_ has to be
32
- # either ::A::B::C or A::B::C. In any case A has to be located at the top
33
- # level (absolute namespace path?). If there doesn't exist a constant at
34
- # the given path, an ArgumentError is raised.
35
- def deep_const_get(path) # :nodoc:
36
- path = path.to_s
37
- path.split(/::/).inject(Object) do |p, c|
38
- case
39
- when c.empty? then p
40
- when p.const_defined?(c) then p.const_get(c)
41
- else raise ArgumentError, "can't find const #{path}"
42
- end
43
- end
44
- end
45
-
46
155
  # Set the module _generator_ to be used by JSON.
47
156
  def generator=(generator) # :nodoc:
157
+ old, $VERBOSE = $VERBOSE, nil
48
158
  @generator = generator
49
- generator_methods = generator::GeneratorMethods
50
- for const in generator_methods.constants
51
- klass = deep_const_get(const)
52
- modul = generator_methods.const_get(const)
53
- klass.class_eval do
54
- instance_methods(false).each do |m|
55
- m.to_s == 'to_json' and remove_method m
159
+ if generator.const_defined?(:GeneratorMethods)
160
+ generator_methods = generator::GeneratorMethods
161
+ for const in generator_methods.constants
162
+ klass = const_get(const)
163
+ modul = generator_methods.const_get(const)
164
+ klass.class_eval do
165
+ instance_methods(false).each do |m|
166
+ m.to_s == 'to_json' and remove_method m
167
+ end
168
+ include modul
56
169
  end
57
- include modul
58
170
  end
59
171
  end
60
172
  self.state = generator::State
61
- const_set :State, self.state
173
+ const_set :State, state
174
+ ensure
175
+ $VERBOSE = old
62
176
  end
63
177
 
64
- # Returns the JSON generator modul, that is used by JSON. This might be
65
- # either JSON::Ext::Generator or JSON::Pure::Generator.
178
+ # Returns the JSON generator module that is used by JSON.
66
179
  attr_reader :generator
67
180
 
68
- # Returns the JSON generator state class, that is used by JSON. This might
69
- # be either JSON::Ext::Generator::State or JSON::Pure::Generator::State.
181
+ # Sets or Returns the JSON generator state class that is used by JSON.
70
182
  attr_accessor :state
71
183
 
72
- # This is create identifier, that is used to decide, if the _json_create_
73
- # hook of a class should be called. It defaults to 'json_class'.
74
- attr_accessor :create_id
184
+ private
185
+
186
+ # Called from the extension when a hash has both string and symbol keys
187
+ def on_mixed_keys_hash(hash, do_raise)
188
+ set = {}
189
+ hash.each_key do |key|
190
+ key_str = key.to_s
191
+
192
+ if set[key_str]
193
+ message = "detected duplicate key #{key_str.inspect} in #{hash.inspect}"
194
+ if do_raise
195
+ raise GeneratorError, message
196
+ else
197
+ deprecation_warning("#{message}.\nThis will raise an error in json 3.0 unless enabled via `allow_duplicate_key: true`")
198
+ end
199
+ else
200
+ set[key_str] = true
201
+ end
202
+ end
203
+ end
204
+
205
+ def deprecated_singleton_attr_accessor(*attrs)
206
+ args = RUBY_VERSION >= "3.0" ? ", category: :deprecated" : ""
207
+ attrs.each do |attr|
208
+ singleton_class.class_eval <<~RUBY
209
+ def #{attr}
210
+ warn "JSON.#{attr} is deprecated and will be removed in json 3.0.0", uplevel: 1 #{args}
211
+ @#{attr}
212
+ end
213
+
214
+ def #{attr}=(val)
215
+ warn "JSON.#{attr}= is deprecated and will be removed in json 3.0.0", uplevel: 1 #{args}
216
+ @#{attr} = val
217
+ end
218
+
219
+ def _#{attr}
220
+ @#{attr}
221
+ end
222
+ RUBY
223
+ end
224
+ end
225
+ end
226
+
227
+ # Sets create identifier, which is used to decide if the _json_create_
228
+ # hook of a class should be called; initial value is +json_class+:
229
+ # JSON.create_id # => 'json_class'
230
+ def self.create_id=(new_value)
231
+ Thread.current[:"JSON.create_id"] = new_value.dup.freeze
232
+ end
233
+
234
+ # Returns the current create identifier.
235
+ # See also JSON.create_id=.
236
+ def self.create_id
237
+ Thread.current[:"JSON.create_id"] || 'json_class'
75
238
  end
76
- self.create_id = 'json_class'
77
239
 
78
- NaN = 0.0/0
240
+ NaN = Float::NAN
79
241
 
80
- Infinity = 1.0/0
242
+ Infinity = Float::INFINITY
81
243
 
82
244
  MinusInfinity = -Infinity
83
245
 
84
246
  # The base exception for JSON errors.
85
247
  class JSONError < StandardError; end
86
248
 
87
- # This exception is raised, if a parser error occurs.
88
- class ParserError < JSONError; end
249
+ # This exception is raised if a parser error occurs.
250
+ class ParserError < JSONError
251
+ attr_reader :line, :column
252
+ end
89
253
 
90
- # This exception is raised, if the nesting of parsed datastructures is too
254
+ # This exception is raised if the nesting of parsed data structures is too
91
255
  # deep.
92
256
  class NestingError < ParserError; end
93
257
 
94
- # This exception is raised, if a generator or unparser error occurs.
95
- class GeneratorError < JSONError; end
96
- # For backwards compatibility
97
- UnparserError = GeneratorError
258
+ # This exception is raised if a generator or unparser error occurs.
259
+ class GeneratorError < JSONError
260
+ attr_reader :invalid_object
98
261
 
99
- # If a circular data structure is encountered while unparsing
100
- # this exception is raised.
101
- class CircularDatastructure < GeneratorError; end
262
+ def initialize(message, invalid_object = nil)
263
+ super(message)
264
+ @invalid_object = invalid_object
265
+ end
102
266
 
103
- # This exception is raised, if the required unicode support is missing on the
104
- # system. Usually this means, that the iconv library is not installed.
105
- class MissingUnicodeSupport < JSONError; end
267
+ def detailed_message(...)
268
+ # Exception#detailed_message doesn't exist until Ruby 3.2
269
+ super_message = defined?(super) ? super : message
106
270
 
107
- module_function
271
+ if @invalid_object.nil?
272
+ super_message
273
+ else
274
+ "#{super_message}\nInvalid object: #{@invalid_object.inspect}"
275
+ end
276
+ end
277
+ end
108
278
 
109
- # Parse the JSON document _source_ into a Ruby data structure and return it.
110
- #
111
- # _opts_ can have the following
112
- # keys:
113
- # * *max_nesting*: The maximum depth of nesting allowed in the parsed data
114
- # structures. Disable depth checking with :max_nesting => false, it defaults
115
- # to 19.
116
- # * *allow_nan*: If set to true, allow NaN, Infinity and -Infinity in
117
- # defiance of RFC 4627 to be parsed by the Parser. This option defaults
118
- # to false.
119
- # * *create_additions*: If set to false, the Parser doesn't create
120
- # additions even if a matchin class and create_id was found. This option
121
- # defaults to true.
122
- def parse(source, opts = {})
123
- JSON.parser.new(source, opts).parse
279
+ # Fragment of JSON document that is to be included as is:
280
+ # fragment = JSON::Fragment.new("[1, 2, 3]")
281
+ # JSON.generate({ count: 3, items: fragments })
282
+ #
283
+ # This allows to easily assemble multiple JSON fragments that have
284
+ # been persisted somewhere without having to parse them nor resorting
285
+ # to string interpolation.
286
+ #
287
+ # Note: no validation is performed on the provided string. It is the
288
+ # responsibility of the caller to ensure the string contains valid JSON.
289
+ Fragment = Struct.new(:json) do
290
+ def initialize(json)
291
+ unless string = String.try_convert(json)
292
+ raise TypeError, " no implicit conversion of #{json.class} into String"
293
+ end
294
+
295
+ super(string)
296
+ end
297
+
298
+ def to_json(state = nil, *)
299
+ json
300
+ end
124
301
  end
125
302
 
126
- # Parse the JSON document _source_ into a Ruby data structure and return it.
127
- # The bang version of the parse method, defaults to the more dangerous values
128
- # for the _opts_ hash, so be sure only to parse trusted _source_ documents.
129
- #
130
- # _opts_ can have the following keys:
131
- # * *max_nesting*: The maximum depth of nesting allowed in the parsed data
132
- # structures. Enable depth checking with :max_nesting => anInteger. The parse!
133
- # methods defaults to not doing max depth checking: This can be dangerous,
134
- # if someone wants to fill up your stack.
135
- # * *allow_nan*: If set to true, allow NaN, Infinity, and -Infinity in
136
- # defiance of RFC 4627 to be parsed by the Parser. This option defaults
137
- # to true.
138
- # * *create_additions*: If set to false, the Parser doesn't create
139
- # additions even if a matchin class and create_id was found. This option
140
- # defaults to true.
141
- def parse!(source, opts = {})
142
- opts = {
143
- :max_nesting => false,
144
- :allow_nan => true
145
- }.update(opts)
146
- JSON.parser.new(source, opts).parse
303
+ module_function
304
+
305
+ # :call-seq:
306
+ # JSON.parse(source, opts) -> object
307
+ #
308
+ # Returns the Ruby objects created by parsing the given +source+.
309
+ #
310
+ # Argument +source+ contains the \String to be parsed.
311
+ #
312
+ # Argument +opts+, if given, contains a \Hash of options for the parsing.
313
+ # See {Parsing Options}[#module-JSON-label-Parsing+Options].
314
+ #
315
+ # ---
316
+ #
317
+ # When +source+ is a \JSON array, returns a Ruby \Array:
318
+ # source = '["foo", 1.0, true, false, null]'
319
+ # ruby = JSON.parse(source)
320
+ # ruby # => ["foo", 1.0, true, false, nil]
321
+ # ruby.class # => Array
322
+ #
323
+ # When +source+ is a \JSON object, returns a Ruby \Hash:
324
+ # source = '{"a": "foo", "b": 1.0, "c": true, "d": false, "e": null}'
325
+ # ruby = JSON.parse(source)
326
+ # ruby # => {"a"=>"foo", "b"=>1.0, "c"=>true, "d"=>false, "e"=>nil}
327
+ # ruby.class # => Hash
328
+ #
329
+ # For examples of parsing for all \JSON data types, see
330
+ # {Parsing \JSON}[#module-JSON-label-Parsing+JSON].
331
+ #
332
+ # Parses nested JSON objects:
333
+ # source = <<~JSON
334
+ # {
335
+ # "name": "Dave",
336
+ # "age" :40,
337
+ # "hats": [
338
+ # "Cattleman's",
339
+ # "Panama",
340
+ # "Tophat"
341
+ # ]
342
+ # }
343
+ # JSON
344
+ # ruby = JSON.parse(source)
345
+ # ruby # => {"name"=>"Dave", "age"=>40, "hats"=>["Cattleman's", "Panama", "Tophat"]}
346
+ #
347
+ # ---
348
+ #
349
+ # Raises an exception if +source+ is not valid JSON:
350
+ # # Raises JSON::ParserError (783: unexpected token at ''):
351
+ # JSON.parse('')
352
+ #
353
+ def parse(source, opts = nil)
354
+ opts = ParserOptions.prepare(opts) unless opts.nil?
355
+ Parser.parse(source, opts)
147
356
  end
148
357
 
149
- # Generate a JSON document from the Ruby data structure _obj_ and return
150
- # it. _state_ is * a JSON::State object,
151
- # * or a Hash like object (responding to to_hash),
152
- # * an object convertible into a hash by a to_h method,
153
- # that is used as or to configure a State object.
154
- #
155
- # It defaults to a state object, that creates the shortest possible JSON text
156
- # in one line, checks for circular data structures and doesn't allow NaN,
157
- # Infinity, and -Infinity.
158
- #
159
- # A _state_ hash can have the following keys:
160
- # * *indent*: a string used to indent levels (default: ''),
161
- # * *space*: a string that is put after, a : or , delimiter (default: ''),
162
- # * *space_before*: a string that is put before a : pair delimiter (default: ''),
163
- # * *object_nl*: a string that is put at the end of a JSON object (default: ''),
164
- # * *array_nl*: a string that is put at the end of a JSON array (default: ''),
165
- # * *check_circular*: true if checking for circular data structures
166
- # should be done (the default), false otherwise.
167
- # * *allow_nan*: true if NaN, Infinity, and -Infinity should be
168
- # generated, otherwise an exception is thrown, if these values are
169
- # encountered. This options defaults to false.
170
- # * *max_nesting*: The maximum depth of nesting allowed in the data
171
- # structures from which JSON is to be generated. Disable depth checking
172
- # with :max_nesting => false, it defaults to 19.
173
- #
174
- # See also the fast_generate for the fastest creation method with the least
175
- # amount of sanity checks, and the pretty_generate method for some
176
- # defaults for a pretty output.
177
- def generate(obj, state = nil)
178
- if state
179
- state = State.from_state(state)
358
+ PARSE_L_OPTIONS = {
359
+ max_nesting: false,
360
+ allow_nan: true,
361
+ }.freeze
362
+ private_constant :PARSE_L_OPTIONS
363
+
364
+ # :call-seq:
365
+ # JSON.parse!(source, opts) -> object
366
+ #
367
+ # Calls
368
+ # parse(source, opts)
369
+ # with +source+ and possibly modified +opts+.
370
+ #
371
+ # Differences from JSON.parse:
372
+ # - Option +max_nesting+, if not provided, defaults to +false+,
373
+ # which disables checking for nesting depth.
374
+ # - Option +allow_nan+, if not provided, defaults to +true+.
375
+ def parse!(source, opts = nil)
376
+ if opts.nil?
377
+ parse(source, PARSE_L_OPTIONS)
180
378
  else
181
- state = State.new
379
+ parse(source, PARSE_L_OPTIONS.merge(opts))
182
380
  end
183
- result = obj.to_json(state)
184
- if result !~ /\A\s*(?:\[.*\]|\{.*\})\s*\Z/m
185
- raise GeneratorError, "only generation of JSON objects or arrays allowed"
186
- end
187
- result
188
381
  end
189
382
 
190
- # :stopdoc:
191
- # I want to deprecate these later, so I'll first be silent about them, and
192
- # later delete them.
193
- alias unparse generate
194
- module_function :unparse
195
- # :startdoc:
383
+ # :call-seq:
384
+ # JSON.load_file(path, opts={}) -> object
385
+ #
386
+ # Calls:
387
+ # parse(File.read(path), opts)
388
+ #
389
+ # See method #parse.
390
+ def load_file(filespec, opts = nil)
391
+ parse(File.read(filespec, encoding: Encoding::UTF_8), opts)
392
+ end
196
393
 
197
- # Generate a JSON document from the Ruby data structure _obj_ and return it.
198
- # This method disables the checks for circles in Ruby objects.
394
+ # :call-seq:
395
+ # JSON.load_file!(path, opts = {})
396
+ #
397
+ # Calls:
398
+ # JSON.parse!(File.read(path, opts))
399
+ #
400
+ # See method #parse!
401
+ def load_file!(filespec, opts = nil)
402
+ parse!(File.read(filespec, encoding: Encoding::UTF_8), opts)
403
+ end
404
+
405
+ # :call-seq:
406
+ # JSON.generate(obj, opts = nil) -> new_string
407
+ #
408
+ # Returns a \String containing the generated \JSON data.
409
+ #
410
+ # See also JSON.pretty_generate.
411
+ #
412
+ # Argument +obj+ is the Ruby object to be converted to \JSON.
413
+ #
414
+ # Argument +opts+, if given, contains a \Hash of options for the generation.
415
+ # See {Generating Options}[#module-JSON-label-Generating+Options].
416
+ #
417
+ # ---
418
+ #
419
+ # When +obj+ is an \Array, returns a \String containing a \JSON array:
420
+ # obj = ["foo", 1.0, true, false, nil]
421
+ # json = JSON.generate(obj)
422
+ # json # => '["foo",1.0,true,false,null]'
423
+ #
424
+ # When +obj+ is a \Hash, returns a \String containing a \JSON object:
425
+ # obj = {foo: 0, bar: 's', baz: :bat}
426
+ # json = JSON.generate(obj)
427
+ # json # => '{"foo":0,"bar":"s","baz":"bat"}'
428
+ #
429
+ # For examples of generating from other Ruby objects, see
430
+ # {Generating \JSON from Other Objects}[#module-JSON-label-Generating+JSON+from+Other+Objects].
431
+ #
432
+ # ---
433
+ #
434
+ # Raises an exception if any formatting option is not a \String.
199
435
  #
200
- # *WARNING*: Be careful not to pass any Ruby data structures with circles as
201
- # _obj_ argument, because this will cause JSON to go into an infinite loop.
202
- def fast_generate(obj)
203
- result = obj.to_json(nil)
204
- if result !~ /\A(?:\[.*\]|\{.*\})\Z/
205
- raise GeneratorError, "only generation of JSON objects or arrays allowed"
436
+ # Raises an exception if +obj+ contains circular references:
437
+ # a = []; b = []; a.push(b); b.push(a)
438
+ # # Raises JSON::NestingError (nesting of 100 is too deep):
439
+ # JSON.generate(a)
440
+ #
441
+ def generate(obj, opts = nil)
442
+ if State === opts
443
+ opts.generate(obj)
444
+ else
445
+ State.generate(obj, opts, nil)
206
446
  end
207
- result
208
447
  end
209
448
 
210
- # :stopdoc:
211
- # I want to deprecate these later, so I'll first be silent about them, and later delete them.
212
- alias fast_unparse fast_generate
213
- module_function :fast_unparse
214
- # :startdoc:
449
+ # :call-seq:
450
+ # JSON.fast_generate(obj, opts) -> new_string
451
+ #
452
+ # Arguments +obj+ and +opts+ here are the same as
453
+ # arguments +obj+ and +opts+ in JSON.generate.
454
+ #
455
+ # By default, generates \JSON data without checking
456
+ # for circular references in +obj+ (option +max_nesting+ set to +false+, disabled).
457
+ #
458
+ # Raises an exception if +obj+ contains circular references:
459
+ # a = []; b = []; a.push(b); b.push(a)
460
+ # # Raises SystemStackError (stack level too deep):
461
+ # JSON.fast_generate(a)
462
+ def fast_generate(obj, opts = nil)
463
+ if RUBY_VERSION >= "3.0"
464
+ warn "JSON.fast_generate is deprecated and will be removed in json 3.0.0, just use JSON.generate", uplevel: 1, category: :deprecated
465
+ else
466
+ warn "JSON.fast_generate is deprecated and will be removed in json 3.0.0, just use JSON.generate", uplevel: 1
467
+ end
468
+ generate(obj, opts)
469
+ end
470
+
471
+ PRETTY_GENERATE_OPTIONS = {
472
+ indent: ' ',
473
+ space: ' ',
474
+ object_nl: "\n",
475
+ array_nl: "\n",
476
+ }.freeze
477
+ private_constant :PRETTY_GENERATE_OPTIONS
215
478
 
216
- # Generate a JSON document from the Ruby data structure _obj_ and return it.
217
- # The returned document is a prettier form of the document returned by
218
- # #unparse.
479
+ # :call-seq:
480
+ # JSON.pretty_generate(obj, opts = nil) -> new_string
481
+ #
482
+ # Arguments +obj+ and +opts+ here are the same as
483
+ # arguments +obj+ and +opts+ in JSON.generate.
484
+ #
485
+ # Default options are:
486
+ # {
487
+ # indent: ' ', # Two spaces
488
+ # space: ' ', # One space
489
+ # array_nl: "\n", # Newline
490
+ # object_nl: "\n" # Newline
491
+ # }
492
+ #
493
+ # Example:
494
+ # obj = {foo: [:bar, :baz], bat: {bam: 0, bad: 1}}
495
+ # json = JSON.pretty_generate(obj)
496
+ # puts json
497
+ # Output:
498
+ # {
499
+ # "foo": [
500
+ # "bar",
501
+ # "baz"
502
+ # ],
503
+ # "bat": {
504
+ # "bam": 0,
505
+ # "bad": 1
506
+ # }
507
+ # }
219
508
  #
220
- # The _opts_ argument can be used to configure the generator, see the
221
- # generate method for a more detailed explanation.
222
509
  def pretty_generate(obj, opts = nil)
223
- state = JSON.state.new(
224
- :indent => ' ',
225
- :space => ' ',
226
- :object_nl => "\n",
227
- :array_nl => "\n",
228
- :check_circular => true
229
- )
510
+ return opts.generate(obj) if State === opts
511
+
512
+ options = PRETTY_GENERATE_OPTIONS
513
+
230
514
  if opts
231
- if opts.respond_to? :to_hash
232
- opts = opts.to_hash
233
- elsif opts.respond_to? :to_h
234
- opts = opts.to_h
515
+ unless opts.is_a?(Hash)
516
+ if opts.respond_to? :to_hash
517
+ opts = opts.to_hash
518
+ elsif opts.respond_to? :to_h
519
+ opts = opts.to_h
520
+ else
521
+ raise TypeError, "can't convert #{opts.class} into Hash"
522
+ end
523
+ end
524
+ options = options.merge(opts)
525
+ end
526
+
527
+ State.generate(obj, options, nil)
528
+ end
529
+
530
+ # Sets or returns default options for the JSON.unsafe_load method.
531
+ # Initially:
532
+ # opts = JSON.load_default_options
533
+ # opts # => {:max_nesting=>false, :allow_nan=>true, :allow_blank=>true, :create_additions=>true}
534
+ deprecated_singleton_attr_accessor :unsafe_load_default_options
535
+
536
+ @unsafe_load_default_options = {
537
+ :max_nesting => false,
538
+ :allow_nan => true,
539
+ :allow_blank => true,
540
+ :create_additions => true,
541
+ }
542
+
543
+ # Sets or returns default options for the JSON.load method.
544
+ # Initially:
545
+ # opts = JSON.load_default_options
546
+ # opts # => {:max_nesting=>false, :allow_nan=>true, :allow_blank=>true, :create_additions=>true}
547
+ deprecated_singleton_attr_accessor :load_default_options
548
+
549
+ @load_default_options = {
550
+ :allow_nan => true,
551
+ :allow_blank => true,
552
+ :create_additions => nil,
553
+ }
554
+ # :call-seq:
555
+ # JSON.unsafe_load(source, options = {}) -> object
556
+ # JSON.unsafe_load(source, proc = nil, options = {}) -> object
557
+ #
558
+ # Returns the Ruby objects created by parsing the given +source+.
559
+ #
560
+ # BEWARE: This method is meant to serialise data from trusted user input,
561
+ # like from your own database server or clients under your control, it could
562
+ # be dangerous to allow untrusted users to pass JSON sources into it.
563
+ #
564
+ # - Argument +source+ must be, or be convertible to, a \String:
565
+ # - If +source+ responds to instance method +to_str+,
566
+ # <tt>source.to_str</tt> becomes the source.
567
+ # - If +source+ responds to instance method +to_io+,
568
+ # <tt>source.to_io.read</tt> becomes the source.
569
+ # - If +source+ responds to instance method +read+,
570
+ # <tt>source.read</tt> becomes the source.
571
+ # - If both of the following are true, source becomes the \String <tt>'null'</tt>:
572
+ # - Option +allow_blank+ specifies a truthy value.
573
+ # - The source, as defined above, is +nil+ or the empty \String <tt>''</tt>.
574
+ # - Otherwise, +source+ remains the source.
575
+ # - Argument +proc+, if given, must be a \Proc that accepts one argument.
576
+ # It will be called recursively with each result (depth-first order).
577
+ # See details below.
578
+ # - Argument +opts+, if given, contains a \Hash of options for the parsing.
579
+ # See {Parsing Options}[#module-JSON-label-Parsing+Options].
580
+ # The default options can be changed via method JSON.unsafe_load_default_options=.
581
+ #
582
+ # ---
583
+ #
584
+ # When no +proc+ is given, modifies +source+ as above and returns the result of
585
+ # <tt>parse(source, opts)</tt>; see #parse.
586
+ #
587
+ # Source for following examples:
588
+ # source = <<~JSON
589
+ # {
590
+ # "name": "Dave",
591
+ # "age" :40,
592
+ # "hats": [
593
+ # "Cattleman's",
594
+ # "Panama",
595
+ # "Tophat"
596
+ # ]
597
+ # }
598
+ # JSON
599
+ #
600
+ # Load a \String:
601
+ # ruby = JSON.unsafe_load(source)
602
+ # ruby # => {"name"=>"Dave", "age"=>40, "hats"=>["Cattleman's", "Panama", "Tophat"]}
603
+ #
604
+ # Load an \IO object:
605
+ # require 'stringio'
606
+ # object = JSON.unsafe_load(StringIO.new(source))
607
+ # object # => {"name"=>"Dave", "age"=>40, "hats"=>["Cattleman's", "Panama", "Tophat"]}
608
+ #
609
+ # Load a \File object:
610
+ # path = 't.json'
611
+ # File.write(path, source)
612
+ # File.open(path) do |file|
613
+ # JSON.unsafe_load(file)
614
+ # end # => {"name"=>"Dave", "age"=>40, "hats"=>["Cattleman's", "Panama", "Tophat"]}
615
+ #
616
+ # ---
617
+ #
618
+ # When +proc+ is given:
619
+ # - Modifies +source+ as above.
620
+ # - Gets the +result+ from calling <tt>parse(source, opts)</tt>.
621
+ # - Recursively calls <tt>proc(result)</tt>.
622
+ # - Returns the final result.
623
+ #
624
+ # Example:
625
+ # require 'json'
626
+ #
627
+ # # Some classes for the example.
628
+ # class Base
629
+ # def initialize(attributes)
630
+ # @attributes = attributes
631
+ # end
632
+ # end
633
+ # class User < Base; end
634
+ # class Account < Base; end
635
+ # class Admin < Base; end
636
+ # # The JSON source.
637
+ # json = <<-EOF
638
+ # {
639
+ # "users": [
640
+ # {"type": "User", "username": "jane", "email": "jane@example.com"},
641
+ # {"type": "User", "username": "john", "email": "john@example.com"}
642
+ # ],
643
+ # "accounts": [
644
+ # {"account": {"type": "Account", "paid": true, "account_id": "1234"}},
645
+ # {"account": {"type": "Account", "paid": false, "account_id": "1235"}}
646
+ # ],
647
+ # "admins": {"type": "Admin", "password": "0wn3d"}
648
+ # }
649
+ # EOF
650
+ # # Deserializer method.
651
+ # def deserialize_obj(obj, safe_types = %w(User Account Admin))
652
+ # type = obj.is_a?(Hash) && obj["type"]
653
+ # safe_types.include?(type) ? Object.const_get(type).new(obj) : obj
654
+ # end
655
+ # # Call to JSON.unsafe_load
656
+ # ruby = JSON.unsafe_load(json, proc {|obj|
657
+ # case obj
658
+ # when Hash
659
+ # obj.each {|k, v| obj[k] = deserialize_obj v }
660
+ # when Array
661
+ # obj.map! {|v| deserialize_obj v }
662
+ # end
663
+ # obj
664
+ # })
665
+ # pp ruby
666
+ # Output:
667
+ # {"users"=>
668
+ # [#<User:0x00000000064c4c98
669
+ # @attributes=
670
+ # {"type"=>"User", "username"=>"jane", "email"=>"jane@example.com"}>,
671
+ # #<User:0x00000000064c4bd0
672
+ # @attributes=
673
+ # {"type"=>"User", "username"=>"john", "email"=>"john@example.com"}>],
674
+ # "accounts"=>
675
+ # [{"account"=>
676
+ # #<Account:0x00000000064c4928
677
+ # @attributes={"type"=>"Account", "paid"=>true, "account_id"=>"1234"}>},
678
+ # {"account"=>
679
+ # #<Account:0x00000000064c4680
680
+ # @attributes={"type"=>"Account", "paid"=>false, "account_id"=>"1235"}>}],
681
+ # "admins"=>
682
+ # #<Admin:0x00000000064c41f8
683
+ # @attributes={"type"=>"Admin", "password"=>"0wn3d"}>}
684
+ #
685
+ def unsafe_load(source, proc = nil, options = nil)
686
+ opts = if options.nil?
687
+ if proc && proc.is_a?(Hash)
688
+ options, proc = proc, nil
689
+ options
235
690
  else
236
- raise TypeError, "can't convert #{opts.class} into Hash"
691
+ _unsafe_load_default_options
237
692
  end
238
- state.configure(opts)
693
+ else
694
+ _unsafe_load_default_options.merge(options)
239
695
  end
240
- result = obj.to_json(state)
241
- if result !~ /\A\s*(?:\[.*\]|\{.*\})\s*\Z/m
242
- raise GeneratorError, "only generation of JSON objects or arrays allowed"
696
+
697
+ unless source.is_a?(String)
698
+ if source.respond_to? :to_str
699
+ source = source.to_str
700
+ elsif source.respond_to? :to_io
701
+ source = source.to_io.read
702
+ elsif source.respond_to?(:read)
703
+ source = source.read
704
+ end
705
+ end
706
+
707
+ if opts[:allow_blank] && (source.nil? || source.empty?)
708
+ source = 'null'
709
+ end
710
+
711
+ if proc
712
+ opts = opts.dup
713
+ opts[:on_load] = proc.to_proc
714
+ end
715
+
716
+ parse(source, opts)
717
+ end
718
+
719
+ # :call-seq:
720
+ # JSON.load(source, options = {}) -> object
721
+ # JSON.load(source, proc = nil, options = {}) -> object
722
+ #
723
+ # Returns the Ruby objects created by parsing the given +source+.
724
+ #
725
+ # BEWARE: This method is meant to serialise data from trusted user input,
726
+ # like from your own database server or clients under your control, it could
727
+ # be dangerous to allow untrusted users to pass JSON sources into it.
728
+ # If you must use it, use JSON.unsafe_load instead to make it clear.
729
+ #
730
+ # Since JSON version 2.8.0, `load` emits a deprecation warning when a
731
+ # non native type is deserialized, without `create_additions` being explicitly
732
+ # enabled, and in JSON version 3.0, `load` will have `create_additions` disabled
733
+ # by default.
734
+ #
735
+ # - Argument +source+ must be, or be convertible to, a \String:
736
+ # - If +source+ responds to instance method +to_str+,
737
+ # <tt>source.to_str</tt> becomes the source.
738
+ # - If +source+ responds to instance method +to_io+,
739
+ # <tt>source.to_io.read</tt> becomes the source.
740
+ # - If +source+ responds to instance method +read+,
741
+ # <tt>source.read</tt> becomes the source.
742
+ # - If both of the following are true, source becomes the \String <tt>'null'</tt>:
743
+ # - Option +allow_blank+ specifies a truthy value.
744
+ # - The source, as defined above, is +nil+ or the empty \String <tt>''</tt>.
745
+ # - Otherwise, +source+ remains the source.
746
+ # - Argument +proc+, if given, must be a \Proc that accepts one argument.
747
+ # It will be called recursively with each result (depth-first order).
748
+ # See details below.
749
+ # - Argument +opts+, if given, contains a \Hash of options for the parsing.
750
+ # See {Parsing Options}[#module-JSON-label-Parsing+Options].
751
+ # The default options can be changed via method JSON.load_default_options=.
752
+ #
753
+ # ---
754
+ #
755
+ # When no +proc+ is given, modifies +source+ as above and returns the result of
756
+ # <tt>parse(source, opts)</tt>; see #parse.
757
+ #
758
+ # Source for following examples:
759
+ # source = <<~JSON
760
+ # {
761
+ # "name": "Dave",
762
+ # "age" :40,
763
+ # "hats": [
764
+ # "Cattleman's",
765
+ # "Panama",
766
+ # "Tophat"
767
+ # ]
768
+ # }
769
+ # JSON
770
+ #
771
+ # Load a \String:
772
+ # ruby = JSON.load(source)
773
+ # ruby # => {"name"=>"Dave", "age"=>40, "hats"=>["Cattleman's", "Panama", "Tophat"]}
774
+ #
775
+ # Load an \IO object:
776
+ # require 'stringio'
777
+ # object = JSON.load(StringIO.new(source))
778
+ # object # => {"name"=>"Dave", "age"=>40, "hats"=>["Cattleman's", "Panama", "Tophat"]}
779
+ #
780
+ # Load a \File object:
781
+ # path = 't.json'
782
+ # File.write(path, source)
783
+ # File.open(path) do |file|
784
+ # JSON.load(file)
785
+ # end # => {"name"=>"Dave", "age"=>40, "hats"=>["Cattleman's", "Panama", "Tophat"]}
786
+ #
787
+ # ---
788
+ #
789
+ # When +proc+ is given:
790
+ # - Modifies +source+ as above.
791
+ # - Gets the +result+ from calling <tt>parse(source, opts)</tt>.
792
+ # - Recursively calls <tt>proc(result)</tt>.
793
+ # - Returns the final result.
794
+ #
795
+ # Example:
796
+ # require 'json'
797
+ #
798
+ # # Some classes for the example.
799
+ # class Base
800
+ # def initialize(attributes)
801
+ # @attributes = attributes
802
+ # end
803
+ # end
804
+ # class User < Base; end
805
+ # class Account < Base; end
806
+ # class Admin < Base; end
807
+ # # The JSON source.
808
+ # json = <<-EOF
809
+ # {
810
+ # "users": [
811
+ # {"type": "User", "username": "jane", "email": "jane@example.com"},
812
+ # {"type": "User", "username": "john", "email": "john@example.com"}
813
+ # ],
814
+ # "accounts": [
815
+ # {"account": {"type": "Account", "paid": true, "account_id": "1234"}},
816
+ # {"account": {"type": "Account", "paid": false, "account_id": "1235"}}
817
+ # ],
818
+ # "admins": {"type": "Admin", "password": "0wn3d"}
819
+ # }
820
+ # EOF
821
+ # # Deserializer method.
822
+ # def deserialize_obj(obj, safe_types = %w(User Account Admin))
823
+ # type = obj.is_a?(Hash) && obj["type"]
824
+ # safe_types.include?(type) ? Object.const_get(type).new(obj) : obj
825
+ # end
826
+ # # Call to JSON.load
827
+ # ruby = JSON.load(json, proc {|obj|
828
+ # case obj
829
+ # when Hash
830
+ # obj.each {|k, v| obj[k] = deserialize_obj v }
831
+ # when Array
832
+ # obj.map! {|v| deserialize_obj v }
833
+ # end
834
+ # obj
835
+ # })
836
+ # pp ruby
837
+ # Output:
838
+ # {"users"=>
839
+ # [#<User:0x00000000064c4c98
840
+ # @attributes=
841
+ # {"type"=>"User", "username"=>"jane", "email"=>"jane@example.com"}>,
842
+ # #<User:0x00000000064c4bd0
843
+ # @attributes=
844
+ # {"type"=>"User", "username"=>"john", "email"=>"john@example.com"}>],
845
+ # "accounts"=>
846
+ # [{"account"=>
847
+ # #<Account:0x00000000064c4928
848
+ # @attributes={"type"=>"Account", "paid"=>true, "account_id"=>"1234"}>},
849
+ # {"account"=>
850
+ # #<Account:0x00000000064c4680
851
+ # @attributes={"type"=>"Account", "paid"=>false, "account_id"=>"1235"}>}],
852
+ # "admins"=>
853
+ # #<Admin:0x00000000064c41f8
854
+ # @attributes={"type"=>"Admin", "password"=>"0wn3d"}>}
855
+ #
856
+ def load(source, proc = nil, options = nil)
857
+ if proc && options.nil? && proc.is_a?(Hash)
858
+ options = proc
859
+ proc = nil
860
+ end
861
+
862
+ opts = if options.nil?
863
+ if proc && proc.is_a?(Hash)
864
+ options, proc = proc, nil
865
+ options
866
+ else
867
+ _load_default_options
868
+ end
869
+ else
870
+ _load_default_options.merge(options)
871
+ end
872
+
873
+ unless source.is_a?(String)
874
+ if source.respond_to? :to_str
875
+ source = source.to_str
876
+ elsif source.respond_to? :to_io
877
+ source = source.to_io.read
878
+ elsif source.respond_to?(:read)
879
+ source = source.read
880
+ end
881
+ end
882
+
883
+ if opts[:allow_blank] && (source.nil? || (String === source && source.empty?))
884
+ source = 'null'
885
+ end
886
+
887
+ if proc
888
+ opts = opts.dup
889
+ opts[:on_load] = proc.to_proc
890
+ end
891
+
892
+ parse(source, opts)
893
+ end
894
+
895
+ # Sets or returns the default options for the JSON.dump method.
896
+ # Initially:
897
+ # opts = JSON.dump_default_options
898
+ # opts # => {:max_nesting=>false, :allow_nan=>true}
899
+ deprecated_singleton_attr_accessor :dump_default_options
900
+ @dump_default_options = {
901
+ :max_nesting => false,
902
+ :allow_nan => true,
903
+ }
904
+
905
+ # :call-seq:
906
+ # JSON.dump(obj, io = nil, limit = nil)
907
+ #
908
+ # Dumps +obj+ as a \JSON string, i.e. calls generate on the object and returns the result.
909
+ #
910
+ # The default options can be changed via method JSON.dump_default_options.
911
+ #
912
+ # - Argument +io+, if given, should respond to method +write+;
913
+ # the \JSON \String is written to +io+, and +io+ is returned.
914
+ # If +io+ is not given, the \JSON \String is returned.
915
+ # - Argument +limit+, if given, is passed to JSON.generate as option +max_nesting+.
916
+ #
917
+ # ---
918
+ #
919
+ # When argument +io+ is not given, returns the \JSON \String generated from +obj+:
920
+ # obj = {foo: [0, 1], bar: {baz: 2, bat: 3}, bam: :bad}
921
+ # json = JSON.dump(obj)
922
+ # json # => "{\"foo\":[0,1],\"bar\":{\"baz\":2,\"bat\":3},\"bam\":\"bad\"}"
923
+ #
924
+ # When argument +io+ is given, writes the \JSON \String to +io+ and returns +io+:
925
+ # path = 't.json'
926
+ # File.open(path, 'w') do |file|
927
+ # JSON.dump(obj, file)
928
+ # end # => #<File:t.json (closed)>
929
+ # puts File.read(path)
930
+ # Output:
931
+ # {"foo":[0,1],"bar":{"baz":2,"bat":3},"bam":"bad"}
932
+ def dump(obj, anIO = nil, limit = nil, kwargs = nil)
933
+ if kwargs.nil?
934
+ if limit.nil?
935
+ if anIO.is_a?(Hash)
936
+ kwargs = anIO
937
+ anIO = nil
938
+ end
939
+ elsif limit.is_a?(Hash)
940
+ kwargs = limit
941
+ limit = nil
942
+ end
943
+ end
944
+
945
+ unless anIO.nil?
946
+ if anIO.respond_to?(:to_io)
947
+ anIO = anIO.to_io
948
+ elsif limit.nil? && !anIO.respond_to?(:write)
949
+ anIO, limit = nil, anIO
950
+ end
951
+ end
952
+
953
+ opts = JSON._dump_default_options
954
+ opts = opts.merge(:max_nesting => limit) if limit
955
+ opts = opts.merge(kwargs) if kwargs
956
+
957
+ begin
958
+ State.generate(obj, opts, anIO)
959
+ rescue JSON::NestingError
960
+ raise ArgumentError, "exceed depth limit"
243
961
  end
244
- result
245
962
  end
246
963
 
247
964
  # :stopdoc:
248
- # I want to deprecate these later, so I'll first be silent about them, and later delete them.
249
- alias pretty_unparse pretty_generate
250
- module_function :pretty_unparse
251
- # :startdoc:
965
+ # All these were meant to be deprecated circa 2009, but were just set as undocumented
966
+ # so usage still exist in the wild.
967
+ def unparse(...)
968
+ if RUBY_VERSION >= "3.0"
969
+ warn "JSON.unparse is deprecated and will be removed in json 3.0.0, just use JSON.generate", uplevel: 1, category: :deprecated
970
+ else
971
+ warn "JSON.unparse is deprecated and will be removed in json 3.0.0, just use JSON.generate", uplevel: 1
972
+ end
973
+ generate(...)
974
+ end
975
+ module_function :unparse
252
976
 
253
- # Load a ruby data structure from a JSON _source_ and return it. A source can
254
- # either be a string-like object, an IO like object, or an object responding
255
- # to the read method. If _proc_ was given, it will be called with any nested
256
- # Ruby object as an argument recursively in depth first order.
257
- #
258
- # This method is part of the implementation of the load/dump interface of
259
- # Marshal and YAML.
260
- def load(source, proc = nil)
261
- if source.respond_to? :to_str
262
- source = source.to_str
263
- elsif source.respond_to? :to_io
264
- source = source.to_io.read
977
+ def fast_unparse(...)
978
+ if RUBY_VERSION >= "3.0"
979
+ warn "JSON.fast_unparse is deprecated and will be removed in json 3.0.0, just use JSON.generate", uplevel: 1, category: :deprecated
265
980
  else
266
- source = source.read
981
+ warn "JSON.fast_unparse is deprecated and will be removed in json 3.0.0, just use JSON.generate", uplevel: 1
267
982
  end
268
- result = parse(source, :max_nesting => false, :allow_nan => true)
269
- recurse_proc(result, &proc) if proc
270
- result
983
+ generate(...)
271
984
  end
985
+ module_function :fast_unparse
272
986
 
273
- def recurse_proc(result, &proc)
274
- case result
275
- when Array
276
- result.each { |x| recurse_proc x, &proc }
277
- proc.call result
278
- when Hash
279
- result.each { |x, y| recurse_proc x, &proc; recurse_proc y, &proc }
280
- proc.call result
987
+ def pretty_unparse(...)
988
+ if RUBY_VERSION >= "3.0"
989
+ warn "JSON.pretty_unparse is deprecated and will be removed in json 3.0.0, just use JSON.pretty_generate", uplevel: 1, category: :deprecated
281
990
  else
282
- proc.call result
991
+ warn "JSON.pretty_unparse is deprecated and will be removed in json 3.0.0, just use JSON.pretty_generate", uplevel: 1
283
992
  end
993
+ pretty_generate(...)
284
994
  end
995
+ module_function :fast_unparse
285
996
 
286
- alias restore load
997
+ def restore(...)
998
+ if RUBY_VERSION >= "3.0"
999
+ warn "JSON.restore is deprecated and will be removed in json 3.0.0, just use JSON.load", uplevel: 1, category: :deprecated
1000
+ else
1001
+ warn "JSON.restore is deprecated and will be removed in json 3.0.0, just use JSON.load", uplevel: 1
1002
+ end
1003
+ load(...)
1004
+ end
287
1005
  module_function :restore
288
1006
 
289
- # Dumps _obj_ as a JSON string, i.e. calls generate on the object and returns
290
- # the result.
1007
+ class << self
1008
+ private
1009
+
1010
+ def const_missing(const_name)
1011
+ case const_name
1012
+ when :PRETTY_STATE_PROTOTYPE
1013
+ if RUBY_VERSION >= "3.0"
1014
+ 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
1015
+ else
1016
+ warn "JSON::PRETTY_STATE_PROTOTYPE is deprecated and will be removed in json 3.0.0, just use JSON.pretty_generate", uplevel: 1
1017
+ end
1018
+ state.new(PRETTY_GENERATE_OPTIONS)
1019
+ else
1020
+ super
1021
+ end
1022
+ end
1023
+ end
1024
+ # :startdoc:
1025
+
1026
+ # JSON::Coder holds a parser and generator configuration.
291
1027
  #
292
- # If anIO (an IO like object or an object that responds to the write method)
293
- # was given, the resulting JSON is written to it.
1028
+ # module MyApp
1029
+ # JSONC_CODER = JSON::Coder.new(
1030
+ # allow_trailing_comma: true
1031
+ # )
1032
+ # end
294
1033
  #
295
- # If the number of nested arrays or objects exceeds _limit_ an ArgumentError
296
- # exception is raised. This argument is similar (but not exactly the
297
- # same!) to the _limit_ argument in Marshal.dump.
1034
+ # MyApp::JSONC_CODER.load(document)
298
1035
  #
299
- # This method is part of the implementation of the load/dump interface of
300
- # Marshal and YAML.
301
- def dump(obj, anIO = nil, limit = nil)
302
- if anIO and limit.nil?
303
- anIO = anIO.to_io if anIO.respond_to?(:to_io)
304
- unless anIO.respond_to?(:write)
305
- limit = anIO
306
- anIO = nil
1036
+ class Coder
1037
+ # :call-seq:
1038
+ # JSON.new(options = nil, &block)
1039
+ #
1040
+ # Argument +options+, if given, contains a \Hash of options for both parsing and generating.
1041
+ # See {Parsing Options}[rdoc-ref:JSON@Parsing+Options],
1042
+ # and {Generating Options}[rdoc-ref:JSON@Generating+Options].
1043
+ #
1044
+ # For generation, the <tt>strict: true</tt> option is always set. When a Ruby object with no native \JSON counterpart is
1045
+ # encountered, the block provided to the initialize method is invoked, and must return a Ruby object that has a native
1046
+ # \JSON counterpart:
1047
+ #
1048
+ # module MyApp
1049
+ # API_JSON_CODER = JSON::Coder.new do |object|
1050
+ # case object
1051
+ # when Time
1052
+ # object.iso8601(3)
1053
+ # else
1054
+ # object # Unknown type, will raise
1055
+ # end
1056
+ # end
1057
+ # end
1058
+ #
1059
+ # puts MyApp::API_JSON_CODER.dump(Time.now.utc) # => "2025-01-21T08:41:44.286Z"
1060
+ #
1061
+ def initialize(options = nil, &as_json)
1062
+ if options.nil?
1063
+ options = { strict: true }
1064
+ else
1065
+ options = options.dup
1066
+ options[:strict] = true
307
1067
  end
1068
+ options[:as_json] = as_json if as_json
1069
+
1070
+ @state = State.new(options).freeze
1071
+ @parser_config = Ext::Parser::Config.new(ParserOptions.prepare(options)).freeze
308
1072
  end
309
- limit ||= 0
310
- result = generate(obj, :allow_nan => true, :max_nesting => limit)
311
- if anIO
312
- anIO.write result
313
- anIO
314
- else
315
- result
1073
+
1074
+ # call-seq:
1075
+ # dump(object) -> String
1076
+ # dump(object, io) -> io
1077
+ #
1078
+ # Serialize the given object into a \JSON document.
1079
+ def dump(object, io = nil)
1080
+ @state.generate(object, io)
1081
+ end
1082
+ alias_method :generate, :dump
1083
+
1084
+ # call-seq:
1085
+ # load(string) -> Object
1086
+ #
1087
+ # Parse the given \JSON document and return an equivalent Ruby object.
1088
+ def load(source)
1089
+ @parser_config.parse(source)
1090
+ end
1091
+ alias_method :parse, :load
1092
+
1093
+ # call-seq:
1094
+ # load(path) -> Object
1095
+ #
1096
+ # Parse the given \JSON document and return an equivalent Ruby object.
1097
+ def load_file(path)
1098
+ load(File.read(path, encoding: Encoding::UTF_8))
316
1099
  end
317
- rescue JSON::NestingError
318
- raise ArgumentError, "exceed depth limit"
319
1100
  end
320
1101
 
321
- # Shortuct for iconv.
322
- def self.iconv(to, from, string)
323
- Iconv.iconv(to, from, string).first
1102
+ module GeneratorMethods
1103
+ # call-seq: to_json(*)
1104
+ #
1105
+ # Converts this object into a JSON string.
1106
+ # If this object doesn't directly maps to a JSON native type,
1107
+ # first convert it to a string (calling #to_s), then converts
1108
+ # it to a JSON string, and returns the result.
1109
+ # This is a fallback, if no special method #to_json was defined for some object.
1110
+ def to_json(state = nil, *)
1111
+ obj = case self
1112
+ when nil, false, true, Integer, Float, Array, Hash
1113
+ self
1114
+ else
1115
+ "#{self}"
1116
+ end
1117
+
1118
+ if state.nil?
1119
+ JSON::State._generate_no_fallback(obj, nil, nil)
1120
+ else
1121
+ JSON::State.from_state(state)._generate_no_fallback(obj)
1122
+ end
1123
+ end
324
1124
  end
325
1125
  end
326
1126
 
@@ -330,42 +1130,44 @@ module ::Kernel
330
1130
  # Outputs _objs_ to STDOUT as JSON strings in the shortest form, that is in
331
1131
  # one line.
332
1132
  def j(*objs)
1133
+ if RUBY_VERSION >= "3.0"
1134
+ warn "Kernel#j is deprecated and will be removed in json 3.0.0", uplevel: 1, category: :deprecated
1135
+ else
1136
+ warn "Kernel#j is deprecated and will be removed in json 3.0.0", uplevel: 1
1137
+ end
1138
+
333
1139
  objs.each do |obj|
334
- puts JSON::generate(obj, :allow_nan => true, :max_nesting => false)
1140
+ puts JSON.generate(obj, :allow_nan => true, :max_nesting => false)
335
1141
  end
336
1142
  nil
337
1143
  end
338
1144
 
339
- # Ouputs _objs_ to STDOUT as JSON strings in a pretty format, with
1145
+ # Outputs _objs_ to STDOUT as JSON strings in a pretty format, with
340
1146
  # indentation and over many lines.
341
1147
  def jj(*objs)
1148
+ if RUBY_VERSION >= "3.0"
1149
+ warn "Kernel#jj is deprecated and will be removed in json 3.0.0", uplevel: 1, category: :deprecated
1150
+ else
1151
+ warn "Kernel#jj is deprecated and will be removed in json 3.0.0", uplevel: 1
1152
+ end
1153
+
342
1154
  objs.each do |obj|
343
- puts JSON::pretty_generate(obj, :allow_nan => true, :max_nesting => false)
1155
+ puts JSON.pretty_generate(obj, :allow_nan => true, :max_nesting => false)
344
1156
  end
345
1157
  nil
346
1158
  end
347
1159
 
348
- # If _object_ is string-like parse the string and return the parsed result as
349
- # a Ruby data structure. Otherwise generate a JSON text from the Ruby data
1160
+ # If _object_ is string-like, parse the string and return the parsed result as
1161
+ # a Ruby data structure. Otherwise, generate a JSON text from the Ruby data
350
1162
  # structure object and return it.
351
1163
  #
352
- # The _opts_ argument is passed through to generate/parse respectively, see
1164
+ # The _opts_ argument is passed through to generate/parse respectively. See
353
1165
  # generate and parse for their documentation.
354
- def JSON(object, opts = {})
355
- if object.respond_to? :to_str
356
- JSON.parse(object.to_str, opts)
357
- else
358
- JSON.generate(object, opts)
359
- end
1166
+ def JSON(object, opts = nil)
1167
+ JSON[object, opts]
360
1168
  end
361
1169
  end
362
1170
 
363
- class ::Class
364
- # Returns true, if this class can be used to create an instance
365
- # from a serialised JSON string. The class has to implement a class
366
- # method _json_create_ that expects a hash as first parameter, which includes
367
- # the required data.
368
- def json_creatable?
369
- respond_to?(:json_create)
370
- end
1171
+ class Object
1172
+ include JSON::GeneratorMethods
371
1173
  end