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