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