json 2.7.3 → 2.12.0
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 +4 -4
- data/CHANGES.md +114 -0
- data/LEGAL +0 -52
- data/README.md +61 -57
- data/ext/json/ext/fbuffer/fbuffer.h +156 -66
- data/ext/json/ext/generator/extconf.rb +29 -0
- data/ext/json/ext/generator/generator.c +1166 -478
- data/ext/json/ext/generator/simd.h +112 -0
- data/ext/json/ext/parser/extconf.rb +4 -27
- data/ext/json/ext/parser/parser.c +1138 -1971
- data/ext/json/ext/vendor/fpconv.c +479 -0
- data/ext/json/ext/vendor/jeaiii-ltoa.h +267 -0
- data/json.gemspec +7 -5
- data/lib/json/add/bigdecimal.rb +1 -1
- data/lib/json/add/symbol.rb +7 -2
- data/lib/json/common.rb +594 -219
- data/lib/json/ext/generator/state.rb +2 -31
- data/lib/json/ext.rb +28 -11
- data/lib/json/{pure → truffle_ruby}/generator.rb +264 -154
- data/lib/json/version.rb +1 -1
- data/lib/json.rb +15 -20
- metadata +11 -17
- data/ext/json/ext/generator/generator.h +0 -129
- data/ext/json/ext/parser/parser.h +0 -60
- data/ext/json/ext/parser/parser.rl +0 -997
- data/lib/json/pure/parser.rb +0 -331
- data/lib/json/pure.rb +0 -16
data/lib/json/common.rb
CHANGED
@@ -1,11 +1,115 @@
|
|
1
|
-
#frozen_string_literal: true
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
2
3
|
require 'json/version'
|
3
4
|
|
4
5
|
module JSON
|
5
6
|
autoload :GenericObject, 'json/generic_object'
|
6
7
|
|
7
|
-
|
8
|
-
|
8
|
+
module ParserOptions # :nodoc:
|
9
|
+
class << self
|
10
|
+
def prepare(opts)
|
11
|
+
if opts[:object_class] || opts[:array_class]
|
12
|
+
opts = opts.dup
|
13
|
+
on_load = opts[:on_load]
|
14
|
+
|
15
|
+
on_load = object_class_proc(opts[:object_class], on_load) if opts[:object_class]
|
16
|
+
on_load = array_class_proc(opts[:array_class], on_load) if opts[:array_class]
|
17
|
+
opts[:on_load] = on_load
|
18
|
+
end
|
19
|
+
|
20
|
+
if opts.fetch(:create_additions, false) != false
|
21
|
+
opts = create_additions_proc(opts)
|
22
|
+
end
|
23
|
+
|
24
|
+
opts
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def object_class_proc(object_class, on_load)
|
30
|
+
->(obj) do
|
31
|
+
if Hash === obj
|
32
|
+
object = object_class.new
|
33
|
+
obj.each { |k, v| object[k] = v }
|
34
|
+
obj = object
|
35
|
+
end
|
36
|
+
on_load.nil? ? obj : on_load.call(obj)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def array_class_proc(array_class, on_load)
|
41
|
+
->(obj) do
|
42
|
+
if Array === obj
|
43
|
+
array = array_class.new
|
44
|
+
obj.each { |v| array << v }
|
45
|
+
obj = array
|
46
|
+
end
|
47
|
+
on_load.nil? ? obj : on_load.call(obj)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# TODO: exctract :create_additions support to another gem for version 3.0
|
52
|
+
def create_additions_proc(opts)
|
53
|
+
if opts[:symbolize_names]
|
54
|
+
raise ArgumentError, "options :symbolize_names and :create_additions cannot be used in conjunction"
|
55
|
+
end
|
56
|
+
|
57
|
+
opts = opts.dup
|
58
|
+
create_additions = opts.fetch(:create_additions, false)
|
59
|
+
on_load = opts[:on_load]
|
60
|
+
object_class = opts[:object_class] || Hash
|
61
|
+
|
62
|
+
opts[:on_load] = ->(object) do
|
63
|
+
case object
|
64
|
+
when String
|
65
|
+
opts[:match_string]&.each do |pattern, klass|
|
66
|
+
if match = pattern.match(object)
|
67
|
+
create_additions_warning if create_additions.nil?
|
68
|
+
object = klass.json_create(object)
|
69
|
+
break
|
70
|
+
end
|
71
|
+
end
|
72
|
+
when object_class
|
73
|
+
if opts[:create_additions] != false
|
74
|
+
if class_name = object[JSON.create_id]
|
75
|
+
klass = JSON.deep_const_get(class_name)
|
76
|
+
if (klass.respond_to?(:json_creatable?) && klass.json_creatable?) || klass.respond_to?(:json_create)
|
77
|
+
create_additions_warning if create_additions.nil?
|
78
|
+
object = klass.json_create(object)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
on_load.nil? ? object : on_load.call(object)
|
85
|
+
end
|
86
|
+
|
87
|
+
opts
|
88
|
+
end
|
89
|
+
|
90
|
+
GEM_ROOT = File.expand_path("../../../", __FILE__) + "/"
|
91
|
+
def create_additions_warning
|
92
|
+
message = "JSON.load implicit support for `create_additions: true` is deprecated " \
|
93
|
+
"and will be removed in 3.0, use JSON.unsafe_load or explicitly " \
|
94
|
+
"pass `create_additions: true`"
|
95
|
+
|
96
|
+
uplevel = 4
|
97
|
+
caller_locations(uplevel, 10).each do |frame|
|
98
|
+
if frame.path.nil? || frame.path.start_with?(GEM_ROOT) || frame.path.end_with?("/truffle/cext_ruby.rb", ".c")
|
99
|
+
uplevel += 1
|
100
|
+
else
|
101
|
+
break
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
if RUBY_VERSION >= "3.0"
|
106
|
+
warn(message, uplevel: uplevel - 1, category: :deprecated)
|
107
|
+
else
|
108
|
+
warn(message, uplevel: uplevel - 1)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
9
113
|
|
10
114
|
class << self
|
11
115
|
# :call-seq:
|
@@ -19,22 +123,20 @@ module JSON
|
|
19
123
|
# Otherwise, calls JSON.generate with +object+ and +opts+ (see method #generate):
|
20
124
|
# ruby = [0, 1, nil]
|
21
125
|
# JSON[ruby] # => '[0,1,null]'
|
22
|
-
def [](object, opts =
|
126
|
+
def [](object, opts = nil)
|
23
127
|
if object.is_a?(String)
|
24
128
|
return JSON.parse(object, opts)
|
25
129
|
elsif object.respond_to?(:to_str)
|
26
130
|
str = object.to_str
|
27
131
|
if str.is_a?(String)
|
28
|
-
return JSON.parse(
|
132
|
+
return JSON.parse(str, opts)
|
29
133
|
end
|
30
134
|
end
|
31
135
|
|
32
136
|
JSON.generate(object, opts)
|
33
137
|
end
|
34
138
|
|
35
|
-
# Returns the JSON parser class that is used by JSON.
|
36
|
-
# JSON::Ext::Parser or JSON::Pure::Parser:
|
37
|
-
# JSON.parser # => JSON::Ext::Parser
|
139
|
+
# Returns the JSON parser class that is used by JSON.
|
38
140
|
attr_reader :parser
|
39
141
|
|
40
142
|
# Set the JSON parser class _parser_ to be used by JSON.
|
@@ -49,18 +151,9 @@ module JSON
|
|
49
151
|
# level (absolute namespace path?). If there doesn't exist a constant at
|
50
152
|
# the given path, an ArgumentError is raised.
|
51
153
|
def deep_const_get(path) # :nodoc:
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
when p.const_defined?(c, true) then p.const_get(c)
|
56
|
-
else
|
57
|
-
begin
|
58
|
-
p.const_missing(c)
|
59
|
-
rescue NameError => e
|
60
|
-
raise ArgumentError, "can't get const #{path}: #{e}"
|
61
|
-
end
|
62
|
-
end
|
63
|
-
end
|
154
|
+
Object.const_get(path)
|
155
|
+
rescue NameError => e
|
156
|
+
raise ArgumentError, "can't get const #{path}: #{e}"
|
64
157
|
end
|
65
158
|
|
66
159
|
# Set the module _generator_ to be used by JSON.
|
@@ -69,7 +162,7 @@ module JSON
|
|
69
162
|
@generator = generator
|
70
163
|
generator_methods = generator::GeneratorMethods
|
71
164
|
for const in generator_methods.constants
|
72
|
-
klass =
|
165
|
+
klass = const_get(const)
|
73
166
|
modul = generator_methods.const_get(const)
|
74
167
|
klass.class_eval do
|
75
168
|
instance_methods(false).each do |m|
|
@@ -80,41 +173,38 @@ module JSON
|
|
80
173
|
end
|
81
174
|
self.state = generator::State
|
82
175
|
const_set :State, self.state
|
83
|
-
const_set :SAFE_STATE_PROTOTYPE, State.new # for JRuby
|
84
|
-
const_set :FAST_STATE_PROTOTYPE, create_fast_state
|
85
|
-
const_set :PRETTY_STATE_PROTOTYPE, create_pretty_state
|
86
176
|
ensure
|
87
177
|
$VERBOSE = old
|
88
178
|
end
|
89
179
|
|
90
|
-
|
91
|
-
State.new(
|
92
|
-
:indent => '',
|
93
|
-
:space => '',
|
94
|
-
:object_nl => "",
|
95
|
-
:array_nl => "",
|
96
|
-
:max_nesting => false
|
97
|
-
)
|
98
|
-
end
|
99
|
-
|
100
|
-
def create_pretty_state
|
101
|
-
State.new(
|
102
|
-
:indent => ' ',
|
103
|
-
:space => ' ',
|
104
|
-
:object_nl => "\n",
|
105
|
-
:array_nl => "\n"
|
106
|
-
)
|
107
|
-
end
|
108
|
-
|
109
|
-
# Returns the JSON generator module that is used by JSON. This is
|
110
|
-
# either JSON::Ext::Generator or JSON::Pure::Generator:
|
111
|
-
# JSON.generator # => JSON::Ext::Generator
|
180
|
+
# Returns the JSON generator module that is used by JSON.
|
112
181
|
attr_reader :generator
|
113
182
|
|
114
|
-
# Sets or Returns the JSON generator state class that is used by JSON.
|
115
|
-
# either JSON::Ext::Generator::State or JSON::Pure::Generator::State:
|
116
|
-
# JSON.state # => JSON::Ext::Generator::State
|
183
|
+
# Sets or Returns the JSON generator state class that is used by JSON.
|
117
184
|
attr_accessor :state
|
185
|
+
|
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
|
118
208
|
end
|
119
209
|
|
120
210
|
# Sets create identifier, which is used to decide if the _json_create_
|
@@ -130,40 +220,68 @@ module JSON
|
|
130
220
|
Thread.current[:"JSON.create_id"] || 'json_class'
|
131
221
|
end
|
132
222
|
|
133
|
-
NaN =
|
223
|
+
NaN = Float::NAN
|
134
224
|
|
135
|
-
Infinity =
|
225
|
+
Infinity = Float::INFINITY
|
136
226
|
|
137
227
|
MinusInfinity = -Infinity
|
138
228
|
|
139
229
|
# The base exception for JSON errors.
|
140
|
-
class JSONError < StandardError
|
141
|
-
def self.wrap(exception)
|
142
|
-
obj = new("Wrapped(#{exception.class}): #{exception.message.inspect}")
|
143
|
-
obj.set_backtrace exception.backtrace
|
144
|
-
obj
|
145
|
-
end
|
146
|
-
end
|
230
|
+
class JSONError < StandardError; end
|
147
231
|
|
148
232
|
# This exception is raised if a parser error occurs.
|
149
|
-
class ParserError < JSONError
|
233
|
+
class ParserError < JSONError
|
234
|
+
attr_reader :line, :column
|
235
|
+
end
|
150
236
|
|
151
237
|
# This exception is raised if the nesting of parsed data structures is too
|
152
238
|
# deep.
|
153
239
|
class NestingError < ParserError; end
|
154
240
|
|
155
|
-
# :stopdoc:
|
156
|
-
class CircularDatastructure < NestingError; end
|
157
|
-
# :startdoc:
|
158
|
-
|
159
241
|
# This exception is raised if a generator or unparser error occurs.
|
160
|
-
class GeneratorError < JSONError
|
161
|
-
|
162
|
-
|
242
|
+
class GeneratorError < JSONError
|
243
|
+
attr_reader :invalid_object
|
244
|
+
|
245
|
+
def initialize(message, invalid_object = nil)
|
246
|
+
super(message)
|
247
|
+
@invalid_object = invalid_object
|
248
|
+
end
|
163
249
|
|
164
|
-
|
165
|
-
|
166
|
-
|
250
|
+
def detailed_message(...)
|
251
|
+
# Exception#detailed_message doesn't exist until Ruby 3.2
|
252
|
+
super_message = defined?(super) ? super : message
|
253
|
+
|
254
|
+
if @invalid_object.nil?
|
255
|
+
super_message
|
256
|
+
else
|
257
|
+
"#{super_message}\nInvalid object: #{@invalid_object.inspect}"
|
258
|
+
end
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
# Fragment of JSON document that is to be included as is:
|
263
|
+
# fragment = JSON::Fragment.new("[1, 2, 3]")
|
264
|
+
# JSON.generate({ count: 3, items: fragments })
|
265
|
+
#
|
266
|
+
# This allows to easily assemble multiple JSON fragments that have
|
267
|
+
# been persisted somewhere without having to parse them nor resorting
|
268
|
+
# to string interpolation.
|
269
|
+
#
|
270
|
+
# Note: no validation is performed on the provided string. It is the
|
271
|
+
# responsability of the caller to ensure the string contains valid JSON.
|
272
|
+
Fragment = Struct.new(:json) do
|
273
|
+
def initialize(json)
|
274
|
+
unless string = String.try_convert(json)
|
275
|
+
raise TypeError, " no implicit conversion of #{json.class} into String"
|
276
|
+
end
|
277
|
+
|
278
|
+
super(string)
|
279
|
+
end
|
280
|
+
|
281
|
+
def to_json(state = nil, *)
|
282
|
+
json
|
283
|
+
end
|
284
|
+
end
|
167
285
|
|
168
286
|
module_function
|
169
287
|
|
@@ -195,17 +313,17 @@ module JSON
|
|
195
313
|
# {Parsing \JSON}[#module-JSON-label-Parsing+JSON].
|
196
314
|
#
|
197
315
|
# Parses nested JSON objects:
|
198
|
-
# source =
|
199
|
-
#
|
200
|
-
#
|
201
|
-
#
|
202
|
-
#
|
203
|
-
#
|
204
|
-
#
|
205
|
-
#
|
206
|
-
#
|
207
|
-
#
|
208
|
-
#
|
316
|
+
# source = <<~JSON
|
317
|
+
# {
|
318
|
+
# "name": "Dave",
|
319
|
+
# "age" :40,
|
320
|
+
# "hats": [
|
321
|
+
# "Cattleman's",
|
322
|
+
# "Panama",
|
323
|
+
# "Tophat"
|
324
|
+
# ]
|
325
|
+
# }
|
326
|
+
# JSON
|
209
327
|
# ruby = JSON.parse(source)
|
210
328
|
# ruby # => {"name"=>"Dave", "age"=>40, "hats"=>["Cattleman's", "Panama", "Tophat"]}
|
211
329
|
#
|
@@ -216,13 +334,16 @@ module JSON
|
|
216
334
|
# JSON.parse('')
|
217
335
|
#
|
218
336
|
def parse(source, opts = nil)
|
219
|
-
|
220
|
-
|
221
|
-
else
|
222
|
-
Parser.new(source, opts).parse
|
223
|
-
end
|
337
|
+
opts = ParserOptions.prepare(opts) unless opts.nil?
|
338
|
+
Parser.parse(source, opts)
|
224
339
|
end
|
225
340
|
|
341
|
+
PARSE_L_OPTIONS = {
|
342
|
+
max_nesting: false,
|
343
|
+
allow_nan: true,
|
344
|
+
}.freeze
|
345
|
+
private_constant :PARSE_L_OPTIONS
|
346
|
+
|
226
347
|
# :call-seq:
|
227
348
|
# JSON.parse!(source, opts) -> object
|
228
349
|
#
|
@@ -234,12 +355,12 @@ module JSON
|
|
234
355
|
# - Option +max_nesting+, if not provided, defaults to +false+,
|
235
356
|
# which disables checking for nesting depth.
|
236
357
|
# - Option +allow_nan+, if not provided, defaults to +true+.
|
237
|
-
def parse!(source, opts =
|
238
|
-
opts
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
358
|
+
def parse!(source, opts = nil)
|
359
|
+
if opts.nil?
|
360
|
+
parse(source, PARSE_L_OPTIONS)
|
361
|
+
else
|
362
|
+
parse(source, PARSE_L_OPTIONS.merge(opts))
|
363
|
+
end
|
243
364
|
end
|
244
365
|
|
245
366
|
# :call-seq:
|
@@ -249,8 +370,8 @@ module JSON
|
|
249
370
|
# parse(File.read(path), opts)
|
250
371
|
#
|
251
372
|
# See method #parse.
|
252
|
-
def load_file(filespec, opts =
|
253
|
-
parse(File.read(filespec), opts)
|
373
|
+
def load_file(filespec, opts = nil)
|
374
|
+
parse(File.read(filespec, encoding: Encoding::UTF_8), opts)
|
254
375
|
end
|
255
376
|
|
256
377
|
# :call-seq:
|
@@ -260,8 +381,8 @@ module JSON
|
|
260
381
|
# JSON.parse!(File.read(path, opts))
|
261
382
|
#
|
262
383
|
# See method #parse!
|
263
|
-
def load_file!(filespec, opts =
|
264
|
-
parse!(File.read(filespec), opts)
|
384
|
+
def load_file!(filespec, opts = nil)
|
385
|
+
parse!(File.read(filespec, encoding: Encoding::UTF_8), opts)
|
265
386
|
end
|
266
387
|
|
267
388
|
# :call-seq:
|
@@ -302,20 +423,12 @@ module JSON
|
|
302
423
|
#
|
303
424
|
def generate(obj, opts = nil)
|
304
425
|
if State === opts
|
305
|
-
|
426
|
+
opts.generate(obj)
|
306
427
|
else
|
307
|
-
|
428
|
+
State.generate(obj, opts, nil)
|
308
429
|
end
|
309
|
-
state.generate(obj)
|
310
430
|
end
|
311
431
|
|
312
|
-
# :stopdoc:
|
313
|
-
# I want to deprecate these later, so I'll first be silent about them, and
|
314
|
-
# later delete them.
|
315
|
-
alias unparse generate
|
316
|
-
module_function :unparse
|
317
|
-
# :startdoc:
|
318
|
-
|
319
432
|
# :call-seq:
|
320
433
|
# JSON.fast_generate(obj, opts) -> new_string
|
321
434
|
#
|
@@ -330,19 +443,21 @@ module JSON
|
|
330
443
|
# # Raises SystemStackError (stack level too deep):
|
331
444
|
# JSON.fast_generate(a)
|
332
445
|
def fast_generate(obj, opts = nil)
|
333
|
-
if
|
334
|
-
|
446
|
+
if RUBY_VERSION >= "3.0"
|
447
|
+
warn "JSON.fast_generate is deprecated and will be removed in json 3.0.0, just use JSON.generate", uplevel: 1, category: :deprecated
|
335
448
|
else
|
336
|
-
|
449
|
+
warn "JSON.fast_generate is deprecated and will be removed in json 3.0.0, just use JSON.generate", uplevel: 1
|
337
450
|
end
|
338
|
-
|
451
|
+
generate(obj, opts)
|
339
452
|
end
|
340
453
|
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
454
|
+
PRETTY_GENERATE_OPTIONS = {
|
455
|
+
indent: ' ',
|
456
|
+
space: ' ',
|
457
|
+
object_nl: "\n",
|
458
|
+
array_nl: "\n",
|
459
|
+
}.freeze
|
460
|
+
private_constant :PRETTY_GENERATE_OPTIONS
|
346
461
|
|
347
462
|
# :call-seq:
|
348
463
|
# JSON.pretty_generate(obj, opts = nil) -> new_string
|
@@ -375,49 +490,59 @@ module JSON
|
|
375
490
|
# }
|
376
491
|
#
|
377
492
|
def pretty_generate(obj, opts = nil)
|
378
|
-
if State === opts
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
end
|
493
|
+
return state.generate(obj) if State === opts
|
494
|
+
|
495
|
+
options = PRETTY_GENERATE_OPTIONS
|
496
|
+
|
383
497
|
if opts
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
498
|
+
unless opts.is_a?(Hash)
|
499
|
+
if opts.respond_to? :to_hash
|
500
|
+
opts = opts.to_hash
|
501
|
+
elsif opts.respond_to? :to_h
|
502
|
+
opts = opts.to_h
|
503
|
+
else
|
504
|
+
raise TypeError, "can't convert #{opts.class} into Hash"
|
505
|
+
end
|
390
506
|
end
|
391
|
-
|
507
|
+
options = options.merge(opts)
|
392
508
|
end
|
393
|
-
|
509
|
+
|
510
|
+
State.generate(obj, options, nil)
|
394
511
|
end
|
395
512
|
|
396
|
-
#
|
397
|
-
#
|
398
|
-
|
399
|
-
|
400
|
-
|
513
|
+
# Sets or returns default options for the JSON.unsafe_load method.
|
514
|
+
# Initially:
|
515
|
+
# opts = JSON.load_default_options
|
516
|
+
# opts # => {:max_nesting=>false, :allow_nan=>true, :allow_blank=>true, :create_additions=>true}
|
517
|
+
deprecated_singleton_attr_accessor :unsafe_load_default_options
|
401
518
|
|
402
|
-
|
403
|
-
# Sets or returns default options for the JSON.load method.
|
404
|
-
# Initially:
|
405
|
-
# opts = JSON.load_default_options
|
406
|
-
# opts # => {:max_nesting=>false, :allow_nan=>true, :allow_blank=>true, :create_additions=>true}
|
407
|
-
attr_accessor :load_default_options
|
408
|
-
end
|
409
|
-
self.load_default_options = {
|
519
|
+
@unsafe_load_default_options = {
|
410
520
|
:max_nesting => false,
|
411
521
|
:allow_nan => true,
|
412
522
|
:allow_blank => true,
|
413
523
|
:create_additions => true,
|
414
524
|
}
|
415
525
|
|
526
|
+
# Sets or returns default options for the JSON.load method.
|
527
|
+
# Initially:
|
528
|
+
# opts = JSON.load_default_options
|
529
|
+
# opts # => {:max_nesting=>false, :allow_nan=>true, :allow_blank=>true, :create_additions=>true}
|
530
|
+
deprecated_singleton_attr_accessor :load_default_options
|
531
|
+
|
532
|
+
@load_default_options = {
|
533
|
+
:allow_nan => true,
|
534
|
+
:allow_blank => true,
|
535
|
+
:create_additions => nil,
|
536
|
+
}
|
416
537
|
# :call-seq:
|
417
|
-
# JSON.
|
538
|
+
# JSON.unsafe_load(source, proc = nil, options = {}) -> object
|
418
539
|
#
|
419
540
|
# Returns the Ruby objects created by parsing the given +source+.
|
420
541
|
#
|
542
|
+
# BEWARE: This method is meant to serialise data from trusted user input,
|
543
|
+
# like from your own database server or clients under your control, it could
|
544
|
+
# be dangerous to allow untrusted users to pass JSON sources into it.
|
545
|
+
#
|
421
546
|
# - Argument +source+ must be, or be convertible to, a \String:
|
422
547
|
# - If +source+ responds to instance method +to_str+,
|
423
548
|
# <tt>source.to_str</tt> becomes the source.
|
@@ -432,12 +557,9 @@ module JSON
|
|
432
557
|
# - Argument +proc+, if given, must be a \Proc that accepts one argument.
|
433
558
|
# It will be called recursively with each result (depth-first order).
|
434
559
|
# See details below.
|
435
|
-
# BEWARE: This method is meant to serialise data from trusted user input,
|
436
|
-
# like from your own database server or clients under your control, it could
|
437
|
-
# be dangerous to allow untrusted users to pass JSON sources into it.
|
438
560
|
# - Argument +opts+, if given, contains a \Hash of options for the parsing.
|
439
561
|
# See {Parsing Options}[#module-JSON-label-Parsing+Options].
|
440
|
-
# The default options can be changed via method JSON.
|
562
|
+
# The default options can be changed via method JSON.unsafe_load_default_options=.
|
441
563
|
#
|
442
564
|
# ---
|
443
565
|
#
|
@@ -445,17 +567,177 @@ module JSON
|
|
445
567
|
# <tt>parse(source, opts)</tt>; see #parse.
|
446
568
|
#
|
447
569
|
# Source for following examples:
|
448
|
-
# source =
|
570
|
+
# source = <<~JSON
|
571
|
+
# {
|
572
|
+
# "name": "Dave",
|
573
|
+
# "age" :40,
|
574
|
+
# "hats": [
|
575
|
+
# "Cattleman's",
|
576
|
+
# "Panama",
|
577
|
+
# "Tophat"
|
578
|
+
# ]
|
579
|
+
# }
|
580
|
+
# JSON
|
581
|
+
#
|
582
|
+
# Load a \String:
|
583
|
+
# ruby = JSON.unsafe_load(source)
|
584
|
+
# ruby # => {"name"=>"Dave", "age"=>40, "hats"=>["Cattleman's", "Panama", "Tophat"]}
|
585
|
+
#
|
586
|
+
# Load an \IO object:
|
587
|
+
# require 'stringio'
|
588
|
+
# object = JSON.unsafe_load(StringIO.new(source))
|
589
|
+
# object # => {"name"=>"Dave", "age"=>40, "hats"=>["Cattleman's", "Panama", "Tophat"]}
|
590
|
+
#
|
591
|
+
# Load a \File object:
|
592
|
+
# path = 't.json'
|
593
|
+
# File.write(path, source)
|
594
|
+
# File.open(path) do |file|
|
595
|
+
# JSON.unsafe_load(file)
|
596
|
+
# end # => {"name"=>"Dave", "age"=>40, "hats"=>["Cattleman's", "Panama", "Tophat"]}
|
597
|
+
#
|
598
|
+
# ---
|
599
|
+
#
|
600
|
+
# When +proc+ is given:
|
601
|
+
# - Modifies +source+ as above.
|
602
|
+
# - Gets the +result+ from calling <tt>parse(source, opts)</tt>.
|
603
|
+
# - Recursively calls <tt>proc(result)</tt>.
|
604
|
+
# - Returns the final result.
|
605
|
+
#
|
606
|
+
# Example:
|
607
|
+
# require 'json'
|
608
|
+
#
|
609
|
+
# # Some classes for the example.
|
610
|
+
# class Base
|
611
|
+
# def initialize(attributes)
|
612
|
+
# @attributes = attributes
|
613
|
+
# end
|
614
|
+
# end
|
615
|
+
# class User < Base; end
|
616
|
+
# class Account < Base; end
|
617
|
+
# class Admin < Base; end
|
618
|
+
# # The JSON source.
|
619
|
+
# json = <<-EOF
|
449
620
|
# {
|
450
|
-
#
|
451
|
-
#
|
452
|
-
#
|
453
|
-
#
|
454
|
-
#
|
455
|
-
#
|
456
|
-
#
|
621
|
+
# "users": [
|
622
|
+
# {"type": "User", "username": "jane", "email": "jane@example.com"},
|
623
|
+
# {"type": "User", "username": "john", "email": "john@example.com"}
|
624
|
+
# ],
|
625
|
+
# "accounts": [
|
626
|
+
# {"account": {"type": "Account", "paid": true, "account_id": "1234"}},
|
627
|
+
# {"account": {"type": "Account", "paid": false, "account_id": "1235"}}
|
628
|
+
# ],
|
629
|
+
# "admins": {"type": "Admin", "password": "0wn3d"}
|
457
630
|
# }
|
458
|
-
#
|
631
|
+
# EOF
|
632
|
+
# # Deserializer method.
|
633
|
+
# def deserialize_obj(obj, safe_types = %w(User Account Admin))
|
634
|
+
# type = obj.is_a?(Hash) && obj["type"]
|
635
|
+
# safe_types.include?(type) ? Object.const_get(type).new(obj) : obj
|
636
|
+
# end
|
637
|
+
# # Call to JSON.unsafe_load
|
638
|
+
# ruby = JSON.unsafe_load(json, proc {|obj|
|
639
|
+
# case obj
|
640
|
+
# when Hash
|
641
|
+
# obj.each {|k, v| obj[k] = deserialize_obj v }
|
642
|
+
# when Array
|
643
|
+
# obj.map! {|v| deserialize_obj v }
|
644
|
+
# end
|
645
|
+
# })
|
646
|
+
# pp ruby
|
647
|
+
# Output:
|
648
|
+
# {"users"=>
|
649
|
+
# [#<User:0x00000000064c4c98
|
650
|
+
# @attributes=
|
651
|
+
# {"type"=>"User", "username"=>"jane", "email"=>"jane@example.com"}>,
|
652
|
+
# #<User:0x00000000064c4bd0
|
653
|
+
# @attributes=
|
654
|
+
# {"type"=>"User", "username"=>"john", "email"=>"john@example.com"}>],
|
655
|
+
# "accounts"=>
|
656
|
+
# [{"account"=>
|
657
|
+
# #<Account:0x00000000064c4928
|
658
|
+
# @attributes={"type"=>"Account", "paid"=>true, "account_id"=>"1234"}>},
|
659
|
+
# {"account"=>
|
660
|
+
# #<Account:0x00000000064c4680
|
661
|
+
# @attributes={"type"=>"Account", "paid"=>false, "account_id"=>"1235"}>}],
|
662
|
+
# "admins"=>
|
663
|
+
# #<Admin:0x00000000064c41f8
|
664
|
+
# @attributes={"type"=>"Admin", "password"=>"0wn3d"}>}
|
665
|
+
#
|
666
|
+
def unsafe_load(source, proc = nil, options = nil)
|
667
|
+
opts = if options.nil?
|
668
|
+
_unsafe_load_default_options
|
669
|
+
else
|
670
|
+
_unsafe_load_default_options.merge(options)
|
671
|
+
end
|
672
|
+
|
673
|
+
unless source.is_a?(String)
|
674
|
+
if source.respond_to? :to_str
|
675
|
+
source = source.to_str
|
676
|
+
elsif source.respond_to? :to_io
|
677
|
+
source = source.to_io.read
|
678
|
+
elsif source.respond_to?(:read)
|
679
|
+
source = source.read
|
680
|
+
end
|
681
|
+
end
|
682
|
+
|
683
|
+
if opts[:allow_blank] && (source.nil? || source.empty?)
|
684
|
+
source = 'null'
|
685
|
+
end
|
686
|
+
result = parse(source, opts)
|
687
|
+
recurse_proc(result, &proc) if proc
|
688
|
+
result
|
689
|
+
end
|
690
|
+
|
691
|
+
# :call-seq:
|
692
|
+
# JSON.load(source, proc = nil, options = {}) -> object
|
693
|
+
#
|
694
|
+
# Returns the Ruby objects created by parsing the given +source+.
|
695
|
+
#
|
696
|
+
# BEWARE: This method is meant to serialise data from trusted user input,
|
697
|
+
# like from your own database server or clients under your control, it could
|
698
|
+
# be dangerous to allow untrusted users to pass JSON sources into it.
|
699
|
+
# If you must use it, use JSON.unsafe_load instead to make it clear.
|
700
|
+
#
|
701
|
+
# Since JSON version 2.8.0, `load` emits a deprecation warning when a
|
702
|
+
# non native type is deserialized, without `create_additions` being explicitly
|
703
|
+
# enabled, and in JSON version 3.0, `load` will have `create_additions` disabled
|
704
|
+
# by default.
|
705
|
+
#
|
706
|
+
# - Argument +source+ must be, or be convertible to, a \String:
|
707
|
+
# - If +source+ responds to instance method +to_str+,
|
708
|
+
# <tt>source.to_str</tt> becomes the source.
|
709
|
+
# - If +source+ responds to instance method +to_io+,
|
710
|
+
# <tt>source.to_io.read</tt> becomes the source.
|
711
|
+
# - If +source+ responds to instance method +read+,
|
712
|
+
# <tt>source.read</tt> becomes the source.
|
713
|
+
# - If both of the following are true, source becomes the \String <tt>'null'</tt>:
|
714
|
+
# - Option +allow_blank+ specifies a truthy value.
|
715
|
+
# - The source, as defined above, is +nil+ or the empty \String <tt>''</tt>.
|
716
|
+
# - Otherwise, +source+ remains the source.
|
717
|
+
# - Argument +proc+, if given, must be a \Proc that accepts one argument.
|
718
|
+
# It will be called recursively with each result (depth-first order).
|
719
|
+
# See details below.
|
720
|
+
# - Argument +opts+, if given, contains a \Hash of options for the parsing.
|
721
|
+
# See {Parsing Options}[#module-JSON-label-Parsing+Options].
|
722
|
+
# The default options can be changed via method JSON.load_default_options=.
|
723
|
+
#
|
724
|
+
# ---
|
725
|
+
#
|
726
|
+
# When no +proc+ is given, modifies +source+ as above and returns the result of
|
727
|
+
# <tt>parse(source, opts)</tt>; see #parse.
|
728
|
+
#
|
729
|
+
# Source for following examples:
|
730
|
+
# source = <<~JSON
|
731
|
+
# {
|
732
|
+
# "name": "Dave",
|
733
|
+
# "age" :40,
|
734
|
+
# "hats": [
|
735
|
+
# "Cattleman's",
|
736
|
+
# "Panama",
|
737
|
+
# "Tophat"
|
738
|
+
# ]
|
739
|
+
# }
|
740
|
+
# JSON
|
459
741
|
#
|
460
742
|
# Load a \String:
|
461
743
|
# ruby = JSON.load(source)
|
@@ -543,9 +825,9 @@ module JSON
|
|
543
825
|
#
|
544
826
|
def load(source, proc = nil, options = nil)
|
545
827
|
opts = if options.nil?
|
546
|
-
|
828
|
+
_load_default_options
|
547
829
|
else
|
548
|
-
|
830
|
+
_load_default_options.merge(options)
|
549
831
|
end
|
550
832
|
|
551
833
|
unless source.is_a?(String)
|
@@ -561,36 +843,21 @@ module JSON
|
|
561
843
|
if opts[:allow_blank] && (source.nil? || source.empty?)
|
562
844
|
source = 'null'
|
563
845
|
end
|
564
|
-
result = parse(source, opts)
|
565
|
-
recurse_proc(result, &proc) if proc
|
566
|
-
result
|
567
|
-
end
|
568
846
|
|
569
|
-
|
570
|
-
|
571
|
-
|
572
|
-
when Array
|
573
|
-
result.each { |x| recurse_proc x, &proc }
|
574
|
-
proc.call result
|
575
|
-
when Hash
|
576
|
-
result.each { |x, y| recurse_proc x, &proc; recurse_proc y, &proc }
|
577
|
-
proc.call result
|
578
|
-
else
|
579
|
-
proc.call result
|
847
|
+
if proc
|
848
|
+
opts = opts.dup
|
849
|
+
opts[:on_load] = proc.to_proc
|
580
850
|
end
|
581
|
-
end
|
582
|
-
|
583
|
-
alias restore load
|
584
|
-
module_function :restore
|
585
851
|
|
586
|
-
|
587
|
-
# Sets or returns the default options for the JSON.dump method.
|
588
|
-
# Initially:
|
589
|
-
# opts = JSON.dump_default_options
|
590
|
-
# opts # => {:max_nesting=>false, :allow_nan=>true}
|
591
|
-
attr_accessor :dump_default_options
|
852
|
+
parse(source, opts)
|
592
853
|
end
|
593
|
-
|
854
|
+
|
855
|
+
# Sets or returns the default options for the JSON.dump method.
|
856
|
+
# Initially:
|
857
|
+
# opts = JSON.dump_default_options
|
858
|
+
# opts # => {:max_nesting=>false, :allow_nan=>true}
|
859
|
+
deprecated_singleton_attr_accessor :dump_default_options
|
860
|
+
@dump_default_options = {
|
594
861
|
:max_nesting => false,
|
595
862
|
:allow_nan => true,
|
596
863
|
}
|
@@ -643,36 +910,152 @@ module JSON
|
|
643
910
|
end
|
644
911
|
end
|
645
912
|
|
646
|
-
opts = JSON.
|
913
|
+
opts = JSON._dump_default_options
|
647
914
|
opts = opts.merge(:max_nesting => limit) if limit
|
648
|
-
opts =
|
915
|
+
opts = opts.merge(kwargs) if kwargs
|
649
916
|
|
650
|
-
|
651
|
-
generate(obj, opts)
|
917
|
+
begin
|
918
|
+
State.generate(obj, opts, anIO)
|
652
919
|
rescue JSON::NestingError
|
653
920
|
raise ArgumentError, "exceed depth limit"
|
654
921
|
end
|
922
|
+
end
|
655
923
|
|
656
|
-
|
657
|
-
|
924
|
+
# :stopdoc:
|
925
|
+
# All these were meant to be deprecated circa 2009, but were just set as undocumented
|
926
|
+
# so usage still exist in the wild.
|
927
|
+
def unparse(...)
|
928
|
+
if RUBY_VERSION >= "3.0"
|
929
|
+
warn "JSON.unparse is deprecated and will be removed in json 3.0.0, just use JSON.generate", uplevel: 1, category: :deprecated
|
658
930
|
else
|
659
|
-
|
660
|
-
anIO
|
931
|
+
warn "JSON.unparse is deprecated and will be removed in json 3.0.0, just use JSON.generate", uplevel: 1
|
661
932
|
end
|
933
|
+
generate(...)
|
662
934
|
end
|
935
|
+
module_function :unparse
|
663
936
|
|
664
|
-
|
665
|
-
|
666
|
-
|
937
|
+
def fast_unparse(...)
|
938
|
+
if RUBY_VERSION >= "3.0"
|
939
|
+
warn "JSON.fast_unparse is deprecated and will be removed in json 3.0.0, just use JSON.generate", uplevel: 1, category: :deprecated
|
940
|
+
else
|
941
|
+
warn "JSON.fast_unparse is deprecated and will be removed in json 3.0.0, just use JSON.generate", uplevel: 1
|
942
|
+
end
|
943
|
+
generate(...)
|
667
944
|
end
|
945
|
+
module_function :fast_unparse
|
668
946
|
|
669
|
-
def
|
670
|
-
|
671
|
-
|
947
|
+
def pretty_unparse(...)
|
948
|
+
if RUBY_VERSION >= "3.0"
|
949
|
+
warn "JSON.pretty_unparse is deprecated and will be removed in json 3.0.0, just use JSON.pretty_generate", uplevel: 1, category: :deprecated
|
950
|
+
else
|
951
|
+
warn "JSON.pretty_unparse is deprecated and will be removed in json 3.0.0, just use JSON.pretty_generate", uplevel: 1
|
952
|
+
end
|
953
|
+
pretty_generate(...)
|
672
954
|
end
|
955
|
+
module_function :fast_unparse
|
956
|
+
|
957
|
+
def restore(...)
|
958
|
+
if RUBY_VERSION >= "3.0"
|
959
|
+
warn "JSON.restore is deprecated and will be removed in json 3.0.0, just use JSON.load", uplevel: 1, category: :deprecated
|
960
|
+
else
|
961
|
+
warn "JSON.restore is deprecated and will be removed in json 3.0.0, just use JSON.load", uplevel: 1
|
962
|
+
end
|
963
|
+
load(...)
|
964
|
+
end
|
965
|
+
module_function :restore
|
673
966
|
|
674
967
|
class << self
|
675
|
-
private
|
968
|
+
private
|
969
|
+
|
970
|
+
def const_missing(const_name)
|
971
|
+
case const_name
|
972
|
+
when :PRETTY_STATE_PROTOTYPE
|
973
|
+
if RUBY_VERSION >= "3.0"
|
974
|
+
warn "JSON::PRETTY_STATE_PROTOTYPE is deprecated and will be removed in json 3.0.0, just use JSON.pretty_generate", uplevel: 1, category: :deprecated
|
975
|
+
else
|
976
|
+
warn "JSON::PRETTY_STATE_PROTOTYPE is deprecated and will be removed in json 3.0.0, just use JSON.pretty_generate", uplevel: 1
|
977
|
+
end
|
978
|
+
state.new(PRETTY_GENERATE_OPTIONS)
|
979
|
+
else
|
980
|
+
super
|
981
|
+
end
|
982
|
+
end
|
983
|
+
end
|
984
|
+
# :startdoc:
|
985
|
+
|
986
|
+
# JSON::Coder holds a parser and generator configuration.
|
987
|
+
#
|
988
|
+
# module MyApp
|
989
|
+
# JSONC_CODER = JSON::Coder.new(
|
990
|
+
# allow_trailing_comma: true
|
991
|
+
# )
|
992
|
+
# end
|
993
|
+
#
|
994
|
+
# MyApp::JSONC_CODER.load(document)
|
995
|
+
#
|
996
|
+
class Coder
|
997
|
+
# :call-seq:
|
998
|
+
# JSON.new(options = nil, &block)
|
999
|
+
#
|
1000
|
+
# Argument +options+, if given, contains a \Hash of options for both parsing and generating.
|
1001
|
+
# See {Parsing Options}[#module-JSON-label-Parsing+Options], and {Generating Options}[#module-JSON-label-Generating+Options].
|
1002
|
+
#
|
1003
|
+
# For generation, the <tt>strict: true</tt> option is always set. When a Ruby object with no native \JSON counterpart is
|
1004
|
+
# encoutered, the block provided to the initialize method is invoked, and must return a Ruby object that has a native
|
1005
|
+
# \JSON counterpart:
|
1006
|
+
#
|
1007
|
+
# module MyApp
|
1008
|
+
# API_JSON_CODER = JSON::Coder.new do |object|
|
1009
|
+
# case object
|
1010
|
+
# when Time
|
1011
|
+
# object.iso8601(3)
|
1012
|
+
# else
|
1013
|
+
# object # Unknown type, will raise
|
1014
|
+
# end
|
1015
|
+
# end
|
1016
|
+
# end
|
1017
|
+
#
|
1018
|
+
# puts MyApp::API_JSON_CODER.dump(Time.now.utc) # => "2025-01-21T08:41:44.286Z"
|
1019
|
+
#
|
1020
|
+
def initialize(options = nil, &as_json)
|
1021
|
+
if options.nil?
|
1022
|
+
options = { strict: true }
|
1023
|
+
else
|
1024
|
+
options = options.dup
|
1025
|
+
options[:strict] = true
|
1026
|
+
end
|
1027
|
+
options[:as_json] = as_json if as_json
|
1028
|
+
|
1029
|
+
@state = State.new(options).freeze
|
1030
|
+
@parser_config = Ext::Parser::Config.new(ParserOptions.prepare(options))
|
1031
|
+
end
|
1032
|
+
|
1033
|
+
# call-seq:
|
1034
|
+
# dump(object) -> String
|
1035
|
+
# dump(object, io) -> io
|
1036
|
+
#
|
1037
|
+
# Serialize the given object into a \JSON document.
|
1038
|
+
def dump(object, io = nil)
|
1039
|
+
@state.generate_new(object, io)
|
1040
|
+
end
|
1041
|
+
alias_method :generate, :dump
|
1042
|
+
|
1043
|
+
# call-seq:
|
1044
|
+
# load(string) -> Object
|
1045
|
+
#
|
1046
|
+
# Parse the given \JSON document and return an equivalent Ruby object.
|
1047
|
+
def load(source)
|
1048
|
+
@parser_config.parse(source)
|
1049
|
+
end
|
1050
|
+
alias_method :parse, :load
|
1051
|
+
|
1052
|
+
# call-seq:
|
1053
|
+
# load(path) -> Object
|
1054
|
+
#
|
1055
|
+
# Parse the given \JSON document and return an equivalent Ruby object.
|
1056
|
+
def load_file(path)
|
1057
|
+
load(File.read(path, encoding: Encoding::UTF_8))
|
1058
|
+
end
|
676
1059
|
end
|
677
1060
|
end
|
678
1061
|
|
@@ -682,6 +1065,12 @@ module ::Kernel
|
|
682
1065
|
# Outputs _objs_ to STDOUT as JSON strings in the shortest form, that is in
|
683
1066
|
# one line.
|
684
1067
|
def j(*objs)
|
1068
|
+
if RUBY_VERSION >= "3.0"
|
1069
|
+
warn "Kernel#j is deprecated and will be removed in json 3.0.0", uplevel: 1, category: :deprecated
|
1070
|
+
else
|
1071
|
+
warn "Kernel#j is deprecated and will be removed in json 3.0.0", uplevel: 1
|
1072
|
+
end
|
1073
|
+
|
685
1074
|
objs.each do |obj|
|
686
1075
|
puts JSON::generate(obj, :allow_nan => true, :max_nesting => false)
|
687
1076
|
end
|
@@ -691,6 +1080,12 @@ module ::Kernel
|
|
691
1080
|
# Outputs _objs_ to STDOUT as JSON strings in a pretty format, with
|
692
1081
|
# indentation and over many lines.
|
693
1082
|
def jj(*objs)
|
1083
|
+
if RUBY_VERSION >= "3.0"
|
1084
|
+
warn "Kernel#jj is deprecated and will be removed in json 3.0.0", uplevel: 1, category: :deprecated
|
1085
|
+
else
|
1086
|
+
warn "Kernel#jj is deprecated and will be removed in json 3.0.0", uplevel: 1
|
1087
|
+
end
|
1088
|
+
|
694
1089
|
objs.each do |obj|
|
695
1090
|
puts JSON::pretty_generate(obj, :allow_nan => true, :max_nesting => false)
|
696
1091
|
end
|
@@ -703,27 +1098,7 @@ module ::Kernel
|
|
703
1098
|
#
|
704
1099
|
# The _opts_ argument is passed through to generate/parse respectively. See
|
705
1100
|
# generate and parse for their documentation.
|
706
|
-
def JSON(object,
|
707
|
-
|
708
|
-
return JSON.parse(object, args.first)
|
709
|
-
elsif object.respond_to?(:to_str)
|
710
|
-
str = object.to_str
|
711
|
-
if str.is_a?(String)
|
712
|
-
return JSON.parse(object.to_str, args.first)
|
713
|
-
end
|
714
|
-
end
|
715
|
-
|
716
|
-
JSON.generate(object, args.first)
|
717
|
-
end
|
718
|
-
end
|
719
|
-
|
720
|
-
# Extends any Class to include _json_creatable?_ method.
|
721
|
-
class ::Class
|
722
|
-
# Returns true if this class can be used to create an instance
|
723
|
-
# from a serialised JSON string. The class has to implement a class
|
724
|
-
# method _json_create_ that expects a hash as first parameter. The hash
|
725
|
-
# should include the required data.
|
726
|
-
def json_creatable?
|
727
|
-
respond_to?(:json_create)
|
1101
|
+
def JSON(object, opts = nil)
|
1102
|
+
JSON[object, opts]
|
728
1103
|
end
|
729
1104
|
end
|