json 1.8.6 → 2.7.2
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/{CHANGES → CHANGES.md} +292 -96
- data/LICENSE +56 -0
- data/README.md +185 -114
- data/ext/json/ext/fbuffer/fbuffer.h +0 -3
- data/ext/json/ext/generator/generator.c +328 -117
- data/ext/json/ext/generator/generator.h +8 -8
- data/ext/json/ext/parser/extconf.rb +29 -0
- data/ext/json/ext/parser/parser.c +540 -569
- data/ext/json/ext/parser/parser.h +10 -6
- data/ext/json/ext/parser/parser.rl +269 -261
- data/ext/json/extconf.rb +1 -1
- data/json.gemspec +0 -0
- data/lib/json/add/bigdecimal.rb +40 -10
- 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 +28 -5
- data/lib/json/add/time.rb +27 -6
- data/lib/json/common.rb +402 -188
- data/lib/json/ext.rb +0 -6
- data/lib/json/generic_object.rb +11 -6
- data/lib/json/pure/generator.rb +120 -137
- data/lib/json/pure/parser.rb +64 -86
- data/lib/json/pure.rb +2 -8
- data/lib/json/version.rb +2 -1
- data/lib/json.rb +559 -29
- metadata +18 -129
- data/.gitignore +0 -17
- data/.travis.yml +0 -18
- data/Gemfile +0 -7
- data/README-json-jruby.markdown +0 -33
- data/Rakefile +0 -402
- data/TODO +0 -1
- data/VERSION +0 -1
- data/data/example.json +0 -1
- data/data/index.html +0 -38
- data/data/prototype.js +0 -4184
- data/diagrams/.keep +0 -0
- data/install.rb +0 -23
- data/java/src/json/ext/ByteListTranscoder.java +0 -166
- data/java/src/json/ext/Generator.java +0 -446
- data/java/src/json/ext/GeneratorMethods.java +0 -231
- data/java/src/json/ext/GeneratorService.java +0 -42
- data/java/src/json/ext/GeneratorState.java +0 -542
- data/java/src/json/ext/OptionsReader.java +0 -113
- data/java/src/json/ext/Parser.java +0 -2644
- data/java/src/json/ext/Parser.rl +0 -968
- data/java/src/json/ext/ParserService.java +0 -34
- data/java/src/json/ext/RuntimeInfo.java +0 -120
- data/java/src/json/ext/StringDecoder.java +0 -166
- data/java/src/json/ext/StringEncoder.java +0 -111
- data/java/src/json/ext/Utils.java +0 -88
- data/json-java.gemspec +0 -38
- data/json_pure.gemspec +0 -37
- data/lib/json/ext/.keep +0 -0
- 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/tools/diff.sh +0 -18
- data/tools/fuzz.rb +0 -139
- data/tools/server.rb +0 -62
data/lib/json/common.rb
CHANGED
@@ -1,14 +1,24 @@
|
|
1
|
+
#frozen_string_literal: false
|
1
2
|
require 'json/version'
|
2
|
-
require 'json/generic_object'
|
3
3
|
|
4
4
|
module JSON
|
5
|
+
autoload :GenericObject, 'json/generic_object'
|
6
|
+
|
7
|
+
NOT_SET = Object.new.freeze
|
8
|
+
private_constant :NOT_SET
|
9
|
+
|
5
10
|
class << self
|
6
|
-
#
|
7
|
-
#
|
8
|
-
#
|
11
|
+
# :call-seq:
|
12
|
+
# JSON[object] -> new_array or new_string
|
13
|
+
#
|
14
|
+
# If +object+ is a \String,
|
15
|
+
# calls JSON.parse with +object+ and +opts+ (see method #parse):
|
16
|
+
# json = '[0, 1, null]'
|
17
|
+
# JSON[json]# => [0, 1, nil]
|
9
18
|
#
|
10
|
-
#
|
11
|
-
#
|
19
|
+
# Otherwise, calls JSON.generate with +object+ and +opts+ (see method #generate):
|
20
|
+
# ruby = [0, 1, nil]
|
21
|
+
# JSON[ruby] # => '[0,1,null]'
|
12
22
|
def [](object, opts = {})
|
13
23
|
if object.respond_to? :to_str
|
14
24
|
JSON.parse(object.to_str, opts)
|
@@ -18,13 +28,14 @@ module JSON
|
|
18
28
|
end
|
19
29
|
|
20
30
|
# Returns the JSON parser class that is used by JSON. This is either
|
21
|
-
# JSON::Ext::Parser or JSON::Pure::Parser
|
31
|
+
# JSON::Ext::Parser or JSON::Pure::Parser:
|
32
|
+
# JSON.parser # => JSON::Ext::Parser
|
22
33
|
attr_reader :parser
|
23
34
|
|
24
35
|
# Set the JSON parser class _parser_ to be used by JSON.
|
25
36
|
def parser=(parser) # :nodoc:
|
26
37
|
@parser = parser
|
27
|
-
remove_const :Parser if
|
38
|
+
remove_const :Parser if const_defined?(:Parser, false)
|
28
39
|
const_set :Parser, parser
|
29
40
|
end
|
30
41
|
|
@@ -35,8 +46,8 @@ module JSON
|
|
35
46
|
def deep_const_get(path) # :nodoc:
|
36
47
|
path.to_s.split(/::/).inject(Object) do |p, c|
|
37
48
|
case
|
38
|
-
when c.empty?
|
39
|
-
when
|
49
|
+
when c.empty? then p
|
50
|
+
when p.const_defined?(c, true) then p.const_get(c)
|
40
51
|
else
|
41
52
|
begin
|
42
53
|
p.const_missing(c)
|
@@ -64,37 +75,61 @@ module JSON
|
|
64
75
|
end
|
65
76
|
self.state = generator::State
|
66
77
|
const_set :State, self.state
|
67
|
-
const_set :SAFE_STATE_PROTOTYPE, State.new
|
68
|
-
const_set :FAST_STATE_PROTOTYPE,
|
78
|
+
const_set :SAFE_STATE_PROTOTYPE, State.new # for JRuby
|
79
|
+
const_set :FAST_STATE_PROTOTYPE, create_fast_state
|
80
|
+
const_set :PRETTY_STATE_PROTOTYPE, create_pretty_state
|
81
|
+
ensure
|
82
|
+
$VERBOSE = old
|
83
|
+
end
|
84
|
+
|
85
|
+
def create_fast_state
|
86
|
+
State.new(
|
69
87
|
:indent => '',
|
70
88
|
:space => '',
|
71
89
|
:object_nl => "",
|
72
90
|
:array_nl => "",
|
73
91
|
:max_nesting => false
|
74
92
|
)
|
75
|
-
|
93
|
+
end
|
94
|
+
|
95
|
+
def create_pretty_state
|
96
|
+
State.new(
|
76
97
|
:indent => ' ',
|
77
98
|
:space => ' ',
|
78
99
|
:object_nl => "\n",
|
79
100
|
:array_nl => "\n"
|
80
101
|
)
|
81
|
-
ensure
|
82
|
-
$VERBOSE = old
|
83
102
|
end
|
84
103
|
|
85
104
|
# Returns the JSON generator module that is used by JSON. This is
|
86
|
-
# either JSON::Ext::Generator or JSON::Pure::Generator
|
105
|
+
# either JSON::Ext::Generator or JSON::Pure::Generator:
|
106
|
+
# JSON.generator # => JSON::Ext::Generator
|
87
107
|
attr_reader :generator
|
88
108
|
|
89
|
-
# Returns the JSON generator state class that is used by JSON. This is
|
90
|
-
# either JSON::Ext::Generator::State or JSON::Pure::Generator::State
|
109
|
+
# Sets or Returns the JSON generator state class that is used by JSON. This is
|
110
|
+
# either JSON::Ext::Generator::State or JSON::Pure::Generator::State:
|
111
|
+
# JSON.state # => JSON::Ext::Generator::State
|
91
112
|
attr_accessor :state
|
113
|
+
end
|
92
114
|
|
93
|
-
|
94
|
-
|
95
|
-
|
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
|
+
# Sets create identifier, which is used to decide if the _json_create_
|
122
|
+
# hook of a class should be called; initial value is +json_class+:
|
123
|
+
# JSON.create_id # => 'json_class'
|
124
|
+
def self.create_id=(new_value)
|
125
|
+
Thread.current[CREATE_ID_TLS_KEY] = new_value.dup.freeze
|
126
|
+
end
|
127
|
+
|
128
|
+
# Returns the current create identifier.
|
129
|
+
# See also JSON.create_id=.
|
130
|
+
def self.create_id
|
131
|
+
Thread.current[CREATE_ID_TLS_KEY] || DEFAULT_CREATE_ID
|
96
132
|
end
|
97
|
-
self.create_id = 'json_class'
|
98
133
|
|
99
134
|
NaN = 0.0/0
|
100
135
|
|
@@ -125,7 +160,7 @@ module JSON
|
|
125
160
|
# This exception is raised if a generator or unparser error occurs.
|
126
161
|
class GeneratorError < JSONError; end
|
127
162
|
# For backwards compatibility
|
128
|
-
UnparserError = GeneratorError
|
163
|
+
UnparserError = GeneratorError # :nodoc:
|
129
164
|
|
130
165
|
# This exception is raised if the required unicode support is missing on the
|
131
166
|
# system. Usually this means that the iconv library is not installed.
|
@@ -133,92 +168,140 @@ module JSON
|
|
133
168
|
|
134
169
|
module_function
|
135
170
|
|
136
|
-
#
|
137
|
-
#
|
138
|
-
#
|
139
|
-
#
|
140
|
-
#
|
141
|
-
#
|
142
|
-
#
|
143
|
-
#
|
144
|
-
#
|
145
|
-
#
|
146
|
-
#
|
147
|
-
#
|
148
|
-
#
|
149
|
-
#
|
150
|
-
#
|
151
|
-
#
|
152
|
-
#
|
153
|
-
#
|
171
|
+
# :call-seq:
|
172
|
+
# JSON.parse(source, opts) -> object
|
173
|
+
#
|
174
|
+
# Returns the Ruby objects created by parsing the given +source+.
|
175
|
+
#
|
176
|
+
# Argument +source+ contains the \String to be parsed.
|
177
|
+
#
|
178
|
+
# Argument +opts+, if given, contains a \Hash of options for the parsing.
|
179
|
+
# See {Parsing Options}[#module-JSON-label-Parsing+Options].
|
180
|
+
#
|
181
|
+
# ---
|
182
|
+
#
|
183
|
+
# When +source+ is a \JSON array, returns a Ruby \Array:
|
184
|
+
# source = '["foo", 1.0, true, false, null]'
|
185
|
+
# ruby = JSON.parse(source)
|
186
|
+
# ruby # => ["foo", 1.0, true, false, nil]
|
187
|
+
# ruby.class # => Array
|
188
|
+
#
|
189
|
+
# When +source+ is a \JSON object, returns a Ruby \Hash:
|
190
|
+
# source = '{"a": "foo", "b": 1.0, "c": true, "d": false, "e": null}'
|
191
|
+
# ruby = JSON.parse(source)
|
192
|
+
# ruby # => {"a"=>"foo", "b"=>1.0, "c"=>true, "d"=>false, "e"=>nil}
|
193
|
+
# ruby.class # => Hash
|
194
|
+
#
|
195
|
+
# For examples of parsing for all \JSON data types, see
|
196
|
+
# {Parsing \JSON}[#module-JSON-label-Parsing+JSON].
|
197
|
+
#
|
198
|
+
# Parses nested JSON objects:
|
199
|
+
# source = <<-EOT
|
200
|
+
# {
|
201
|
+
# "name": "Dave",
|
202
|
+
# "age" :40,
|
203
|
+
# "hats": [
|
204
|
+
# "Cattleman's",
|
205
|
+
# "Panama",
|
206
|
+
# "Tophat"
|
207
|
+
# ]
|
208
|
+
# }
|
209
|
+
# EOT
|
210
|
+
# ruby = JSON.parse(source)
|
211
|
+
# ruby # => {"name"=>"Dave", "age"=>40, "hats"=>["Cattleman's", "Panama", "Tophat"]}
|
212
|
+
#
|
213
|
+
# ---
|
214
|
+
#
|
215
|
+
# Raises an exception if +source+ is not valid JSON:
|
216
|
+
# # Raises JSON::ParserError (783: unexpected token at ''):
|
217
|
+
# JSON.parse('')
|
218
|
+
#
|
154
219
|
def parse(source, opts = {})
|
155
|
-
Parser.new(source, opts).parse
|
220
|
+
Parser.new(source, **(opts||{})).parse
|
156
221
|
end
|
157
222
|
|
158
|
-
#
|
159
|
-
#
|
160
|
-
#
|
161
|
-
#
|
162
|
-
#
|
163
|
-
#
|
164
|
-
#
|
165
|
-
#
|
166
|
-
#
|
167
|
-
#
|
168
|
-
#
|
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.
|
223
|
+
# :call-seq:
|
224
|
+
# JSON.parse!(source, opts) -> object
|
225
|
+
#
|
226
|
+
# Calls
|
227
|
+
# parse(source, opts)
|
228
|
+
# with +source+ and possibly modified +opts+.
|
229
|
+
#
|
230
|
+
# Differences from JSON.parse:
|
231
|
+
# - Option +max_nesting+, if not provided, defaults to +false+,
|
232
|
+
# which disables checking for nesting depth.
|
233
|
+
# - Option +allow_nan+, if not provided, defaults to +true+.
|
173
234
|
def parse!(source, opts = {})
|
174
235
|
opts = {
|
175
236
|
:max_nesting => false,
|
176
237
|
:allow_nan => true
|
177
|
-
}.
|
178
|
-
Parser.new(source, opts).parse
|
238
|
+
}.merge(opts)
|
239
|
+
Parser.new(source, **(opts||{})).parse
|
240
|
+
end
|
241
|
+
|
242
|
+
# :call-seq:
|
243
|
+
# JSON.load_file(path, opts={}) -> object
|
244
|
+
#
|
245
|
+
# Calls:
|
246
|
+
# parse(File.read(path), opts)
|
247
|
+
#
|
248
|
+
# See method #parse.
|
249
|
+
def load_file(filespec, opts = {})
|
250
|
+
parse(File.read(filespec), opts)
|
251
|
+
end
|
252
|
+
|
253
|
+
# :call-seq:
|
254
|
+
# JSON.load_file!(path, opts = {})
|
255
|
+
#
|
256
|
+
# Calls:
|
257
|
+
# JSON.parse!(File.read(path, opts))
|
258
|
+
#
|
259
|
+
# See method #parse!
|
260
|
+
def load_file!(filespec, opts = {})
|
261
|
+
parse!(File.read(filespec), opts)
|
179
262
|
end
|
180
263
|
|
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
|
-
#
|
264
|
+
# :call-seq:
|
265
|
+
# JSON.generate(obj, opts = nil) -> new_string
|
266
|
+
#
|
267
|
+
# Returns a \String containing the generated \JSON data.
|
268
|
+
#
|
269
|
+
# See also JSON.fast_generate, JSON.pretty_generate.
|
270
|
+
#
|
271
|
+
# Argument +obj+ is the Ruby object to be converted to \JSON.
|
272
|
+
#
|
273
|
+
# Argument +opts+, if given, contains a \Hash of options for the generation.
|
274
|
+
# See {Generating Options}[#module-JSON-label-Generating+Options].
|
275
|
+
#
|
276
|
+
# ---
|
277
|
+
#
|
278
|
+
# When +obj+ is an \Array, returns a \String containing a \JSON array:
|
279
|
+
# obj = ["foo", 1.0, true, false, nil]
|
280
|
+
# json = JSON.generate(obj)
|
281
|
+
# json # => '["foo",1.0,true,false,null]'
|
282
|
+
#
|
283
|
+
# When +obj+ is a \Hash, returns a \String containing a \JSON object:
|
284
|
+
# obj = {foo: 0, bar: 's', baz: :bat}
|
285
|
+
# json = JSON.generate(obj)
|
286
|
+
# json # => '{"foo":0,"bar":"s","baz":"bat"}'
|
287
|
+
#
|
288
|
+
# For examples of generating from other Ruby objects, see
|
289
|
+
# {Generating \JSON from Other Objects}[#module-JSON-label-Generating+JSON+from+Other+Objects].
|
290
|
+
#
|
291
|
+
# ---
|
292
|
+
#
|
293
|
+
# Raises an exception if any formatting option is not a \String.
|
294
|
+
#
|
295
|
+
# Raises an exception if +obj+ contains circular references:
|
296
|
+
# a = []; b = []; a.push(b); b.push(a)
|
297
|
+
# # Raises JSON::NestingError (nesting of 100 is too deep):
|
298
|
+
# JSON.generate(a)
|
299
|
+
#
|
207
300
|
def generate(obj, opts = nil)
|
208
301
|
if State === opts
|
209
|
-
state
|
302
|
+
state = opts
|
210
303
|
else
|
211
|
-
state =
|
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)
|
304
|
+
state = State.new(opts)
|
222
305
|
end
|
223
306
|
state.generate(obj)
|
224
307
|
end
|
@@ -230,26 +313,24 @@ module JSON
|
|
230
313
|
module_function :unparse
|
231
314
|
# :startdoc:
|
232
315
|
|
233
|
-
#
|
234
|
-
#
|
316
|
+
# :call-seq:
|
317
|
+
# JSON.fast_generate(obj, opts) -> new_string
|
318
|
+
#
|
319
|
+
# Arguments +obj+ and +opts+ here are the same as
|
320
|
+
# arguments +obj+ and +opts+ in JSON.generate.
|
235
321
|
#
|
236
|
-
#
|
237
|
-
#
|
322
|
+
# By default, generates \JSON data without checking
|
323
|
+
# for circular references in +obj+ (option +max_nesting+ set to +false+, disabled).
|
324
|
+
#
|
325
|
+
# Raises an exception if +obj+ contains circular references:
|
326
|
+
# a = []; b = []; a.push(b); b.push(a)
|
327
|
+
# # Raises SystemStackError (stack level too deep):
|
328
|
+
# JSON.fast_generate(a)
|
238
329
|
def fast_generate(obj, opts = nil)
|
239
330
|
if State === opts
|
240
|
-
state
|
331
|
+
state = opts
|
241
332
|
else
|
242
|
-
state =
|
243
|
-
end
|
244
|
-
if opts
|
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)
|
333
|
+
state = JSON.create_fast_state.configure(opts)
|
253
334
|
end
|
254
335
|
state.generate(obj)
|
255
336
|
end
|
@@ -260,17 +341,41 @@ module JSON
|
|
260
341
|
module_function :fast_unparse
|
261
342
|
# :startdoc:
|
262
343
|
|
263
|
-
#
|
264
|
-
#
|
265
|
-
#
|
344
|
+
# :call-seq:
|
345
|
+
# JSON.pretty_generate(obj, opts = nil) -> new_string
|
346
|
+
#
|
347
|
+
# Arguments +obj+ and +opts+ here are the same as
|
348
|
+
# arguments +obj+ and +opts+ in JSON.generate.
|
349
|
+
#
|
350
|
+
# Default options are:
|
351
|
+
# {
|
352
|
+
# indent: ' ', # Two spaces
|
353
|
+
# space: ' ', # One space
|
354
|
+
# array_nl: "\n", # Newline
|
355
|
+
# object_nl: "\n" # Newline
|
356
|
+
# }
|
357
|
+
#
|
358
|
+
# Example:
|
359
|
+
# obj = {foo: [:bar, :baz], bat: {bam: 0, bad: 1}}
|
360
|
+
# json = JSON.pretty_generate(obj)
|
361
|
+
# puts json
|
362
|
+
# Output:
|
363
|
+
# {
|
364
|
+
# "foo": [
|
365
|
+
# "bar",
|
366
|
+
# "baz"
|
367
|
+
# ],
|
368
|
+
# "bat": {
|
369
|
+
# "bam": 0,
|
370
|
+
# "bad": 1
|
371
|
+
# }
|
372
|
+
# }
|
266
373
|
#
|
267
|
-
# The _opts_ argument can be used to configure the generator. See the
|
268
|
-
# generate method for a more detailed explanation.
|
269
374
|
def pretty_generate(obj, opts = nil)
|
270
375
|
if State === opts
|
271
376
|
state, opts = opts, nil
|
272
377
|
else
|
273
|
-
state =
|
378
|
+
state = JSON.create_pretty_state
|
274
379
|
end
|
275
380
|
if opts
|
276
381
|
if opts.respond_to? :to_hash
|
@@ -292,33 +397,147 @@ module JSON
|
|
292
397
|
# :startdoc:
|
293
398
|
|
294
399
|
class << self
|
295
|
-
#
|
296
|
-
#
|
297
|
-
#
|
298
|
-
#
|
400
|
+
# Sets or returns default options for the JSON.load method.
|
401
|
+
# Initially:
|
402
|
+
# opts = JSON.load_default_options
|
403
|
+
# opts # => {:max_nesting=>false, :allow_nan=>true, :allow_blank=>true, :create_additions=>true}
|
299
404
|
attr_accessor :load_default_options
|
300
405
|
end
|
301
406
|
self.load_default_options = {
|
302
407
|
:max_nesting => false,
|
303
408
|
:allow_nan => true,
|
304
|
-
:
|
409
|
+
:allow_blank => true,
|
305
410
|
:create_additions => true,
|
306
411
|
}
|
307
412
|
|
308
|
-
#
|
309
|
-
#
|
310
|
-
#
|
311
|
-
#
|
312
|
-
#
|
413
|
+
# :call-seq:
|
414
|
+
# JSON.load(source, proc = nil, options = {}) -> object
|
415
|
+
#
|
416
|
+
# Returns the Ruby objects created by parsing the given +source+.
|
417
|
+
#
|
418
|
+
# - Argument +source+ must be, or be convertible to, a \String:
|
419
|
+
# - If +source+ responds to instance method +to_str+,
|
420
|
+
# <tt>source.to_str</tt> becomes the source.
|
421
|
+
# - If +source+ responds to instance method +to_io+,
|
422
|
+
# <tt>source.to_io.read</tt> becomes the source.
|
423
|
+
# - If +source+ responds to instance method +read+,
|
424
|
+
# <tt>source.read</tt> becomes the source.
|
425
|
+
# - If both of the following are true, source becomes the \String <tt>'null'</tt>:
|
426
|
+
# - Option +allow_blank+ specifies a truthy value.
|
427
|
+
# - The source, as defined above, is +nil+ or the empty \String <tt>''</tt>.
|
428
|
+
# - Otherwise, +source+ remains the source.
|
429
|
+
# - Argument +proc+, if given, must be a \Proc that accepts one argument.
|
430
|
+
# It will be called recursively with each result (depth-first order).
|
431
|
+
# 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
|
+
# - Argument +opts+, if given, contains a \Hash of options for the parsing.
|
436
|
+
# See {Parsing Options}[#module-JSON-label-Parsing+Options].
|
437
|
+
# The default options can be changed via method JSON.load_default_options=.
|
438
|
+
#
|
439
|
+
# ---
|
440
|
+
#
|
441
|
+
# When no +proc+ is given, modifies +source+ as above and returns the result of
|
442
|
+
# <tt>parse(source, opts)</tt>; see #parse.
|
443
|
+
#
|
444
|
+
# Source for following examples:
|
445
|
+
# source = <<-EOT
|
446
|
+
# {
|
447
|
+
# "name": "Dave",
|
448
|
+
# "age" :40,
|
449
|
+
# "hats": [
|
450
|
+
# "Cattleman's",
|
451
|
+
# "Panama",
|
452
|
+
# "Tophat"
|
453
|
+
# ]
|
454
|
+
# }
|
455
|
+
# EOT
|
456
|
+
#
|
457
|
+
# Load a \String:
|
458
|
+
# ruby = JSON.load(source)
|
459
|
+
# ruby # => {"name"=>"Dave", "age"=>40, "hats"=>["Cattleman's", "Panama", "Tophat"]}
|
460
|
+
#
|
461
|
+
# Load an \IO object:
|
462
|
+
# require 'stringio'
|
463
|
+
# object = JSON.load(StringIO.new(source))
|
464
|
+
# object # => {"name"=>"Dave", "age"=>40, "hats"=>["Cattleman's", "Panama", "Tophat"]}
|
465
|
+
#
|
466
|
+
# Load a \File object:
|
467
|
+
# path = 't.json'
|
468
|
+
# File.write(path, source)
|
469
|
+
# File.open(path) do |file|
|
470
|
+
# JSON.load(file)
|
471
|
+
# end # => {"name"=>"Dave", "age"=>40, "hats"=>["Cattleman's", "Panama", "Tophat"]}
|
472
|
+
#
|
473
|
+
# ---
|
313
474
|
#
|
314
|
-
#
|
315
|
-
#
|
316
|
-
#
|
317
|
-
#
|
318
|
-
#
|
475
|
+
# When +proc+ is given:
|
476
|
+
# - Modifies +source+ as above.
|
477
|
+
# - Gets the +result+ from calling <tt>parse(source, opts)</tt>.
|
478
|
+
# - Recursively calls <tt>proc(result)</tt>.
|
479
|
+
# - Returns the final result.
|
480
|
+
#
|
481
|
+
# Example:
|
482
|
+
# require 'json'
|
483
|
+
#
|
484
|
+
# # Some classes for the example.
|
485
|
+
# class Base
|
486
|
+
# def initialize(attributes)
|
487
|
+
# @attributes = attributes
|
488
|
+
# end
|
489
|
+
# end
|
490
|
+
# class User < Base; end
|
491
|
+
# class Account < Base; end
|
492
|
+
# class Admin < Base; end
|
493
|
+
# # The JSON source.
|
494
|
+
# json = <<-EOF
|
495
|
+
# {
|
496
|
+
# "users": [
|
497
|
+
# {"type": "User", "username": "jane", "email": "jane@example.com"},
|
498
|
+
# {"type": "User", "username": "john", "email": "john@example.com"}
|
499
|
+
# ],
|
500
|
+
# "accounts": [
|
501
|
+
# {"account": {"type": "Account", "paid": true, "account_id": "1234"}},
|
502
|
+
# {"account": {"type": "Account", "paid": false, "account_id": "1235"}}
|
503
|
+
# ],
|
504
|
+
# "admins": {"type": "Admin", "password": "0wn3d"}
|
505
|
+
# }
|
506
|
+
# EOF
|
507
|
+
# # Deserializer method.
|
508
|
+
# def deserialize_obj(obj, safe_types = %w(User Account Admin))
|
509
|
+
# type = obj.is_a?(Hash) && obj["type"]
|
510
|
+
# safe_types.include?(type) ? Object.const_get(type).new(obj) : obj
|
511
|
+
# end
|
512
|
+
# # Call to JSON.load
|
513
|
+
# ruby = JSON.load(json, proc {|obj|
|
514
|
+
# case obj
|
515
|
+
# when Hash
|
516
|
+
# obj.each {|k, v| obj[k] = deserialize_obj v }
|
517
|
+
# when Array
|
518
|
+
# obj.map! {|v| deserialize_obj v }
|
519
|
+
# end
|
520
|
+
# })
|
521
|
+
# pp ruby
|
522
|
+
# Output:
|
523
|
+
# {"users"=>
|
524
|
+
# [#<User:0x00000000064c4c98
|
525
|
+
# @attributes=
|
526
|
+
# {"type"=>"User", "username"=>"jane", "email"=>"jane@example.com"}>,
|
527
|
+
# #<User:0x00000000064c4bd0
|
528
|
+
# @attributes=
|
529
|
+
# {"type"=>"User", "username"=>"john", "email"=>"john@example.com"}>],
|
530
|
+
# "accounts"=>
|
531
|
+
# [{"account"=>
|
532
|
+
# #<Account:0x00000000064c4928
|
533
|
+
# @attributes={"type"=>"Account", "paid"=>true, "account_id"=>"1234"}>},
|
534
|
+
# {"account"=>
|
535
|
+
# #<Account:0x00000000064c4680
|
536
|
+
# @attributes={"type"=>"Account", "paid"=>false, "account_id"=>"1235"}>}],
|
537
|
+
# "admins"=>
|
538
|
+
# #<Admin:0x00000000064c41f8
|
539
|
+
# @attributes={"type"=>"Admin", "password"=>"0wn3d"}>}
|
319
540
|
#
|
320
|
-
# This method is part of the implementation of the load/dump interface of
|
321
|
-
# Marshal and YAML.
|
322
541
|
def load(source, proc = nil, options = {})
|
323
542
|
opts = load_default_options.merge options
|
324
543
|
if source.respond_to? :to_str
|
@@ -328,7 +547,7 @@ module JSON
|
|
328
547
|
elsif source.respond_to?(:read)
|
329
548
|
source = source.read
|
330
549
|
end
|
331
|
-
if opts[:
|
550
|
+
if opts[:allow_blank] && (source.nil? || source.empty?)
|
332
551
|
source = 'null'
|
333
552
|
end
|
334
553
|
result = parse(source, opts)
|
@@ -337,7 +556,7 @@ module JSON
|
|
337
556
|
end
|
338
557
|
|
339
558
|
# Recursively calls passed _Proc_ if the parsed data structure is an _Array_ or _Hash_
|
340
|
-
def recurse_proc(result, &proc)
|
559
|
+
def recurse_proc(result, &proc) # :nodoc:
|
341
560
|
case result
|
342
561
|
when Array
|
343
562
|
result.each { |x| recurse_proc x, &proc }
|
@@ -354,43 +573,57 @@ module JSON
|
|
354
573
|
module_function :restore
|
355
574
|
|
356
575
|
class << self
|
357
|
-
#
|
358
|
-
#
|
359
|
-
#
|
360
|
-
#
|
576
|
+
# Sets or returns the default options for the JSON.dump method.
|
577
|
+
# Initially:
|
578
|
+
# opts = JSON.dump_default_options
|
579
|
+
# opts # => {:max_nesting=>false, :allow_nan=>true, :script_safe=>false}
|
361
580
|
attr_accessor :dump_default_options
|
362
581
|
end
|
363
582
|
self.dump_default_options = {
|
364
583
|
:max_nesting => false,
|
365
584
|
:allow_nan => true,
|
366
|
-
:
|
585
|
+
:script_safe => false,
|
367
586
|
}
|
368
587
|
|
369
|
-
#
|
370
|
-
#
|
588
|
+
# :call-seq:
|
589
|
+
# JSON.dump(obj, io = nil, limit = nil)
|
371
590
|
#
|
372
|
-
#
|
373
|
-
# was given, the resulting JSON is written to it.
|
591
|
+
# Dumps +obj+ as a \JSON string, i.e. calls generate on the object and returns the result.
|
374
592
|
#
|
375
|
-
#
|
376
|
-
# exception is raised. This argument is similar (but not exactly the
|
377
|
-
# same!) to the _limit_ argument in Marshal.dump.
|
593
|
+
# The default options can be changed via method JSON.dump_default_options.
|
378
594
|
#
|
379
|
-
#
|
380
|
-
#
|
595
|
+
# - Argument +io+, if given, should respond to method +write+;
|
596
|
+
# the \JSON \String is written to +io+, and +io+ is returned.
|
597
|
+
# If +io+ is not given, the \JSON \String is returned.
|
598
|
+
# - Argument +limit+, if given, is passed to JSON.generate as option +max_nesting+.
|
381
599
|
#
|
382
|
-
#
|
383
|
-
#
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
600
|
+
# ---
|
601
|
+
#
|
602
|
+
# When argument +io+ is not given, returns the \JSON \String generated from +obj+:
|
603
|
+
# obj = {foo: [0, 1], bar: {baz: 2, bat: 3}, bam: :bad}
|
604
|
+
# json = JSON.dump(obj)
|
605
|
+
# json # => "{\"foo\":[0,1],\"bar\":{\"baz\":2,\"bat\":3},\"bam\":\"bad\"}"
|
606
|
+
#
|
607
|
+
# When argument +io+ is given, writes the \JSON \String to +io+ and returns +io+:
|
608
|
+
# path = 't.json'
|
609
|
+
# File.open(path, 'w') do |file|
|
610
|
+
# JSON.dump(obj, file)
|
611
|
+
# end # => #<File:t.json (closed)>
|
612
|
+
# puts File.read(path)
|
613
|
+
# Output:
|
614
|
+
# {"foo":[0,1],"bar":{"baz":2,"bat":3},"bam":"bad"}
|
615
|
+
def dump(obj, anIO = nil, limit = nil, kwargs = nil)
|
616
|
+
io_limit_opt = [anIO, limit, kwargs].compact
|
617
|
+
kwargs = io_limit_opt.pop if io_limit_opt.last.is_a?(Hash)
|
618
|
+
anIO, limit = io_limit_opt
|
619
|
+
if anIO.respond_to?(:to_io)
|
620
|
+
anIO = anIO.to_io
|
621
|
+
elsif limit.nil? && !anIO.respond_to?(:write)
|
622
|
+
anIO, limit = nil, anIO
|
391
623
|
end
|
392
624
|
opts = JSON.dump_default_options
|
393
625
|
opts = opts.merge(:max_nesting => limit) if limit
|
626
|
+
opts = merge_dump_options(opts, **kwargs) if kwargs
|
394
627
|
result = generate(obj, opts)
|
395
628
|
if anIO
|
396
629
|
anIO.write result
|
@@ -402,37 +635,18 @@ module JSON
|
|
402
635
|
raise ArgumentError, "exceed depth limit"
|
403
636
|
end
|
404
637
|
|
405
|
-
#
|
406
|
-
def self.
|
407
|
-
|
408
|
-
break unless string[2 * i + 1]
|
409
|
-
string[2 * i], string[2 * i + 1] = string[2 * i + 1], string[2 * i]
|
410
|
-
end
|
411
|
-
string
|
638
|
+
# Encodes string using String.encode.
|
639
|
+
def self.iconv(to, from, string)
|
640
|
+
string.encode(to, from)
|
412
641
|
end
|
413
642
|
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
def self.iconv(to, from, string)
|
418
|
-
string.encode(to, from)
|
419
|
-
end
|
420
|
-
else
|
421
|
-
require 'iconv'
|
422
|
-
# Encodes string using _iconv_ library
|
423
|
-
def self.iconv(to, from, string)
|
424
|
-
Iconv.conv(to, from, string)
|
425
|
-
end
|
643
|
+
def merge_dump_options(opts, strict: NOT_SET)
|
644
|
+
opts = opts.merge(strict: strict) if NOT_SET != strict
|
645
|
+
opts
|
426
646
|
end
|
427
647
|
|
428
|
-
|
429
|
-
|
430
|
-
modul.const_defined?(constant)
|
431
|
-
end
|
432
|
-
else
|
433
|
-
def self.const_defined_in?(modul, constant)
|
434
|
-
modul.const_defined?(constant, false)
|
435
|
-
end
|
648
|
+
class << self
|
649
|
+
private :merge_dump_options
|
436
650
|
end
|
437
651
|
end
|
438
652
|
|