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