json 2.1.0-java → 2.4.1-java
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/LICENSE +56 -0
- data/lib/json.rb +549 -29
- data/lib/json/add/bigdecimal.rb +2 -2
- data/lib/json/add/complex.rb +2 -3
- data/lib/json/add/ostruct.rb +1 -1
- data/lib/json/add/rational.rb +2 -3
- data/lib/json/add/regexp.rb +2 -2
- data/lib/json/add/set.rb +29 -0
- data/lib/json/common.rb +341 -115
- data/lib/json/ext/generator.jar +0 -0
- data/lib/json/ext/parser.jar +0 -0
- data/lib/json/pure/generator.rb +31 -10
- data/lib/json/pure/parser.rb +31 -5
- data/lib/json/version.rb +1 -1
- data/tests/fixtures/fail29.json +1 -0
- data/tests/fixtures/fail30.json +1 -0
- data/tests/fixtures/fail31.json +1 -0
- data/tests/fixtures/fail32.json +1 -0
- data/tests/json_addition_test.rb +6 -0
- data/tests/json_common_interface_test.rb +47 -4
- data/tests/json_fixtures_test.rb +9 -1
- data/tests/json_generator_test.rb +55 -0
- data/tests/json_parser_test.rb +40 -14
- data/tests/test_helper.rb +3 -7
- metadata +27 -15
data/lib/json/ext/generator.jar
CHANGED
Binary file
|
data/lib/json/ext/parser.jar
CHANGED
Binary file
|
data/lib/json/pure/generator.rb
CHANGED
@@ -37,20 +37,26 @@ module JSON
|
|
37
37
|
'\\' => '\\\\',
|
38
38
|
} # :nodoc:
|
39
39
|
|
40
|
+
ESCAPE_SLASH_MAP = MAP.merge(
|
41
|
+
'/' => '\\/',
|
42
|
+
)
|
43
|
+
|
40
44
|
# Convert a UTF8 encoded Ruby string _string_ to a JSON string, encoded with
|
41
45
|
# UTF16 big endian characters as \u????, and return it.
|
42
|
-
def utf8_to_json(string) # :nodoc:
|
46
|
+
def utf8_to_json(string, escape_slash = false) # :nodoc:
|
43
47
|
string = string.dup
|
44
48
|
string.force_encoding(::Encoding::ASCII_8BIT)
|
45
|
-
|
49
|
+
map = escape_slash ? ESCAPE_SLASH_MAP : MAP
|
50
|
+
string.gsub!(/[\/"\\\x0-\x1f]/) { map[$&] || $& }
|
46
51
|
string.force_encoding(::Encoding::UTF_8)
|
47
52
|
string
|
48
53
|
end
|
49
54
|
|
50
|
-
def utf8_to_json_ascii(string) # :nodoc:
|
55
|
+
def utf8_to_json_ascii(string, escape_slash = false) # :nodoc:
|
51
56
|
string = string.dup
|
52
57
|
string.force_encoding(::Encoding::ASCII_8BIT)
|
53
|
-
|
58
|
+
map = escape_slash ? ESCAPE_SLASH_MAP : MAP
|
59
|
+
string.gsub!(/[\/"\\\x0-\x1f]/n) { map[$&] || $& }
|
54
60
|
string.gsub!(/(
|
55
61
|
(?:
|
56
62
|
[\xc2-\xdf][\x80-\xbf] |
|
@@ -109,6 +115,7 @@ module JSON
|
|
109
115
|
# * *space_before*: a string that is put before a : pair delimiter (default: ''),
|
110
116
|
# * *object_nl*: a string that is put at the end of a JSON object (default: ''),
|
111
117
|
# * *array_nl*: a string that is put at the end of a JSON array (default: ''),
|
118
|
+
# * *escape_slash*: true if forward slash (/) should be escaped (default: false)
|
112
119
|
# * *check_circular*: is deprecated now, use the :max_nesting option instead,
|
113
120
|
# * *max_nesting*: sets the maximum level of data structure nesting in
|
114
121
|
# the generated JSON, max_nesting = 0 if no maximum should be checked.
|
@@ -123,6 +130,7 @@ module JSON
|
|
123
130
|
@array_nl = ''
|
124
131
|
@allow_nan = false
|
125
132
|
@ascii_only = false
|
133
|
+
@escape_slash = false
|
126
134
|
@buffer_initial_length = 1024
|
127
135
|
configure opts
|
128
136
|
end
|
@@ -148,6 +156,10 @@ module JSON
|
|
148
156
|
# the generated JSON, max_nesting = 0 if no maximum is checked.
|
149
157
|
attr_accessor :max_nesting
|
150
158
|
|
159
|
+
# If this attribute is set to true, forward slashes will be escaped in
|
160
|
+
# all json strings.
|
161
|
+
attr_accessor :escape_slash
|
162
|
+
|
151
163
|
# :stopdoc:
|
152
164
|
attr_reader :buffer_initial_length
|
153
165
|
|
@@ -187,6 +199,11 @@ module JSON
|
|
187
199
|
@ascii_only
|
188
200
|
end
|
189
201
|
|
202
|
+
# Returns true, if forward slashes are escaped. Otherwise returns false.
|
203
|
+
def escape_slash?
|
204
|
+
@escape_slash
|
205
|
+
end
|
206
|
+
|
190
207
|
# Configure this State instance with the Hash _opts_, and return
|
191
208
|
# itself.
|
192
209
|
def configure(opts)
|
@@ -209,6 +226,7 @@ module JSON
|
|
209
226
|
@ascii_only = opts[:ascii_only] if opts.key?(:ascii_only)
|
210
227
|
@depth = opts[:depth] || 0
|
211
228
|
@buffer_initial_length ||= opts[:buffer_initial_length]
|
229
|
+
@escape_slash = !!opts[:escape_slash] if opts.key?(:escape_slash)
|
212
230
|
|
213
231
|
if !opts.key?(:max_nesting) # defaults to 100
|
214
232
|
@max_nesting = 100
|
@@ -250,7 +268,8 @@ module JSON
|
|
250
268
|
if respond_to?(name)
|
251
269
|
__send__(name)
|
252
270
|
else
|
253
|
-
instance_variable_get("@#{name}")
|
271
|
+
instance_variable_get("@#{name}") if
|
272
|
+
instance_variables.include?("@#{name}".to_sym) # avoid warning
|
254
273
|
end
|
255
274
|
end
|
256
275
|
|
@@ -313,8 +332,10 @@ module JSON
|
|
313
332
|
first = false
|
314
333
|
}
|
315
334
|
depth = state.depth -= 1
|
316
|
-
|
317
|
-
|
335
|
+
unless first
|
336
|
+
result << state.object_nl
|
337
|
+
result << state.indent * depth if indent
|
338
|
+
end
|
318
339
|
result << '}'
|
319
340
|
result
|
320
341
|
end
|
@@ -398,13 +419,13 @@ module JSON
|
|
398
419
|
string = encode(::Encoding::UTF_8)
|
399
420
|
end
|
400
421
|
if state.ascii_only?
|
401
|
-
'"' << JSON.utf8_to_json_ascii(string) << '"'
|
422
|
+
'"' << JSON.utf8_to_json_ascii(string, state.escape_slash) << '"'
|
402
423
|
else
|
403
|
-
'"' << JSON.utf8_to_json(string) << '"'
|
424
|
+
'"' << JSON.utf8_to_json(string, state.escape_slash) << '"'
|
404
425
|
end
|
405
426
|
end
|
406
427
|
|
407
|
-
# Module that holds the
|
428
|
+
# Module that holds the extending methods if, the String module is
|
408
429
|
# included.
|
409
430
|
module Extend
|
410
431
|
# Raw Strings are JSON Objects (the raw bytes are stored in an
|
data/lib/json/pure/parser.rb
CHANGED
@@ -49,7 +49,7 @@ module JSON
|
|
49
49
|
)+
|
50
50
|
)mx
|
51
51
|
|
52
|
-
|
52
|
+
UNPARSED = Object.new.freeze
|
53
53
|
|
54
54
|
# Creates a new JSON::Pure::Parser instance for the string _source_.
|
55
55
|
#
|
@@ -61,12 +61,14 @@ module JSON
|
|
61
61
|
# * *allow_nan*: If set to true, allow NaN, Infinity and -Infinity in
|
62
62
|
# defiance of RFC 7159 to be parsed by the Parser. This option defaults
|
63
63
|
# to false.
|
64
|
+
# * *freeze*: If set to true, all parsed objects will be frozen. Parsed
|
65
|
+
# string will be deduplicated if possible.
|
64
66
|
# * *symbolize_names*: If set to true, returns symbols for the names
|
65
67
|
# (keys) in a JSON object. Otherwise strings are returned, which is
|
66
68
|
# also the default. It's not possible to use this option in
|
67
69
|
# conjunction with the *create_additions* option.
|
68
70
|
# * *create_additions*: If set to true, the Parser creates
|
69
|
-
# additions when
|
71
|
+
# additions when a matching class and create_id are found. This
|
70
72
|
# option defaults to false.
|
71
73
|
# * *object_class*: Defaults to Hash
|
72
74
|
# * *array_class*: Defaults to Array
|
@@ -86,6 +88,7 @@ module JSON
|
|
86
88
|
end
|
87
89
|
@allow_nan = !!opts[:allow_nan]
|
88
90
|
@symbolize_names = !!opts[:symbolize_names]
|
91
|
+
@freeze = !!opts[:freeze]
|
89
92
|
if opts.key?(:create_additions)
|
90
93
|
@create_additions = !!opts[:create_additions]
|
91
94
|
else
|
@@ -120,6 +123,7 @@ module JSON
|
|
120
123
|
obj = parse_value
|
121
124
|
UNPARSED.equal?(obj) and raise ParserError,
|
122
125
|
"source is not valid JSON!"
|
126
|
+
obj.freeze if @freeze
|
123
127
|
end
|
124
128
|
while !eos? && skip(IGNORE) do end
|
125
129
|
eos? or raise ParserError, "source is not valid JSON!"
|
@@ -161,6 +165,7 @@ module JSON
|
|
161
165
|
EMPTY_8BIT_STRING.force_encoding Encoding::ASCII_8BIT
|
162
166
|
end
|
163
167
|
|
168
|
+
STR_UMINUS = ''.respond_to?(:-@)
|
164
169
|
def parse_string
|
165
170
|
if scan(STRING)
|
166
171
|
return '' if self[1].empty?
|
@@ -180,6 +185,15 @@ module JSON
|
|
180
185
|
if string.respond_to?(:force_encoding)
|
181
186
|
string.force_encoding(::Encoding::UTF_8)
|
182
187
|
end
|
188
|
+
|
189
|
+
if @freeze
|
190
|
+
if STR_UMINUS
|
191
|
+
string = -string
|
192
|
+
else
|
193
|
+
string.freeze
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
183
197
|
if @create_additions and @match_string
|
184
198
|
for (regexp, klass) in @match_string
|
185
199
|
klass.json_creatable? or next
|
@@ -197,7 +211,15 @@ module JSON
|
|
197
211
|
def parse_value
|
198
212
|
case
|
199
213
|
when scan(FLOAT)
|
200
|
-
|
214
|
+
if @decimal_class then
|
215
|
+
if @decimal_class == BigDecimal then
|
216
|
+
BigDecimal(self[1])
|
217
|
+
else
|
218
|
+
@decimal_class.new(self[1]) || Float(self[1])
|
219
|
+
end
|
220
|
+
else
|
221
|
+
Float(self[1])
|
222
|
+
end
|
201
223
|
when scan(INTEGER)
|
202
224
|
Integer(self[1])
|
203
225
|
when scan(TRUE)
|
@@ -234,8 +256,10 @@ module JSON
|
|
234
256
|
@max_nesting.nonzero? && @current_nesting > @max_nesting
|
235
257
|
result = @array_class.new
|
236
258
|
delim = false
|
237
|
-
|
259
|
+
loop do
|
238
260
|
case
|
261
|
+
when eos?
|
262
|
+
raise ParserError, "unexpected end of string while parsing array"
|
239
263
|
when !UNPARSED.equal?(value = parse_value)
|
240
264
|
delim = false
|
241
265
|
result << value
|
@@ -266,8 +290,10 @@ module JSON
|
|
266
290
|
@max_nesting.nonzero? && @current_nesting > @max_nesting
|
267
291
|
result = @object_class.new
|
268
292
|
delim = false
|
269
|
-
|
293
|
+
loop do
|
270
294
|
case
|
295
|
+
when eos?
|
296
|
+
raise ParserError, "unexpected end of string while parsing object"
|
271
297
|
when !UNPARSED.equal?(string = parse_string)
|
272
298
|
skip(IGNORE)
|
273
299
|
unless scan(PAIR_DELIMITER)
|
data/lib/json/version.rb
CHANGED
@@ -0,0 +1 @@
|
|
1
|
+
{
|
@@ -0,0 +1 @@
|
|
1
|
+
[
|
@@ -0,0 +1 @@
|
|
1
|
+
[1, 2, 3,
|
@@ -0,0 +1 @@
|
|
1
|
+
{"foo": "bar"
|
data/tests/json_addition_test.rb
CHANGED
@@ -5,6 +5,7 @@ require 'json/add/complex'
|
|
5
5
|
require 'json/add/rational'
|
6
6
|
require 'json/add/bigdecimal'
|
7
7
|
require 'json/add/ostruct'
|
8
|
+
require 'json/add/set'
|
8
9
|
require 'date'
|
9
10
|
|
10
11
|
class JSONAdditionTest < Test::Unit::TestCase
|
@@ -190,4 +191,9 @@ class JSONAdditionTest < Test::Unit::TestCase
|
|
190
191
|
o.foo = { 'bar' => true }
|
191
192
|
assert_equal o, parse(JSON(o), :create_additions => true)
|
192
193
|
end
|
194
|
+
|
195
|
+
def test_set
|
196
|
+
s = Set.new([:a, :b, :c, :a])
|
197
|
+
assert_equal s, JSON.parse(JSON(s), :create_additions => true)
|
198
|
+
end
|
193
199
|
end
|
@@ -27,15 +27,15 @@ class JSONCommonInterfaceTest < Test::Unit::TestCase
|
|
27
27
|
end
|
28
28
|
|
29
29
|
def test_parser
|
30
|
-
assert_match
|
30
|
+
assert_match(/::Parser\z/, JSON.parser.name)
|
31
31
|
end
|
32
32
|
|
33
33
|
def test_generator
|
34
|
-
assert_match
|
34
|
+
assert_match(/::Generator\z/, JSON.generator.name)
|
35
35
|
end
|
36
36
|
|
37
37
|
def test_state
|
38
|
-
assert_match
|
38
|
+
assert_match(/::Generator::State\z/, JSON.state.name)
|
39
39
|
end
|
40
40
|
|
41
41
|
def test_create_id
|
@@ -56,7 +56,7 @@ class JSONCommonInterfaceTest < Test::Unit::TestCase
|
|
56
56
|
end
|
57
57
|
|
58
58
|
def test_parse_bang
|
59
|
-
assert_equal [ 1,
|
59
|
+
assert_equal [ 1, Infinity, 3, ], JSON.parse!('[ 1, Infinity, 3 ]')
|
60
60
|
end
|
61
61
|
|
62
62
|
def test_generate
|
@@ -123,4 +123,47 @@ class JSONCommonInterfaceTest < Test::Unit::TestCase
|
|
123
123
|
assert_equal @json, JSON(@hash)
|
124
124
|
assert_equal @hash, JSON(@json)
|
125
125
|
end
|
126
|
+
|
127
|
+
def test_load_file
|
128
|
+
test_load_shared(:load_file)
|
129
|
+
end
|
130
|
+
|
131
|
+
def test_load_file!
|
132
|
+
test_load_shared(:load_file!)
|
133
|
+
end
|
134
|
+
|
135
|
+
def test_load_file_with_option
|
136
|
+
test_load_file_with_option_shared(:load_file)
|
137
|
+
end
|
138
|
+
|
139
|
+
def test_load_file_with_option!
|
140
|
+
test_load_file_with_option_shared(:load_file!)
|
141
|
+
end
|
142
|
+
|
143
|
+
private
|
144
|
+
|
145
|
+
def test_load_shared(method_name)
|
146
|
+
temp_file_containing(@json) do |filespec|
|
147
|
+
assert_equal JSON.public_send(method_name, filespec), @hash
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
def test_load_file_with_option_shared(method_name)
|
152
|
+
temp_file_containing(@json) do |filespec|
|
153
|
+
parsed_object = JSON.public_send(method_name, filespec, symbolize_names: true)
|
154
|
+
key_classes = parsed_object.keys.map(&:class)
|
155
|
+
assert_include(key_classes, Symbol)
|
156
|
+
assert_not_include(key_classes, String)
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
def temp_file_containing(text, file_prefix = '')
|
161
|
+
raise "This method must be called with a code block." unless block_given?
|
162
|
+
|
163
|
+
Tempfile.create(file_prefix) do |file|
|
164
|
+
file << text
|
165
|
+
file.close
|
166
|
+
yield file.path
|
167
|
+
end
|
168
|
+
end
|
126
169
|
end
|
data/tests/json_fixtures_test.rb
CHANGED
@@ -3,13 +3,14 @@ require 'test_helper'
|
|
3
3
|
|
4
4
|
class JSONFixturesTest < Test::Unit::TestCase
|
5
5
|
def setup
|
6
|
-
fixtures = File.join(File.dirname(__FILE__), 'fixtures/{fail,pass}
|
6
|
+
fixtures = File.join(File.dirname(__FILE__), 'fixtures/{fail,pass}*.json')
|
7
7
|
passed, failed = Dir[fixtures].partition { |f| f['pass'] }
|
8
8
|
@passed = passed.inject([]) { |a, f| a << [ f, File.read(f) ] }.sort
|
9
9
|
@failed = failed.inject([]) { |a, f| a << [ f, File.read(f) ] }.sort
|
10
10
|
end
|
11
11
|
|
12
12
|
def test_passing
|
13
|
+
verbose_bak, $VERBOSE = $VERBOSE, nil
|
13
14
|
for name, source in @passed
|
14
15
|
begin
|
15
16
|
assert JSON.parse(source),
|
@@ -19,6 +20,8 @@ class JSONFixturesTest < Test::Unit::TestCase
|
|
19
20
|
raise e
|
20
21
|
end
|
21
22
|
end
|
23
|
+
ensure
|
24
|
+
$VERBOSE = verbose_bak
|
22
25
|
end
|
23
26
|
|
24
27
|
def test_failing
|
@@ -29,4 +32,9 @@ class JSONFixturesTest < Test::Unit::TestCase
|
|
29
32
|
end
|
30
33
|
end
|
31
34
|
end
|
35
|
+
|
36
|
+
def test_sanity
|
37
|
+
assert(@passed.size > 5)
|
38
|
+
assert(@failed.size > 20)
|
39
|
+
end
|
32
40
|
end
|
@@ -40,6 +40,43 @@ class JSONGeneratorTest < Test::Unit::TestCase
|
|
40
40
|
EOT
|
41
41
|
end
|
42
42
|
|
43
|
+
def silence
|
44
|
+
v = $VERBOSE
|
45
|
+
$VERBOSE = nil
|
46
|
+
yield
|
47
|
+
ensure
|
48
|
+
$VERBOSE = v
|
49
|
+
end
|
50
|
+
|
51
|
+
def test_remove_const_segv
|
52
|
+
stress = GC.stress
|
53
|
+
const = JSON::SAFE_STATE_PROTOTYPE.dup
|
54
|
+
|
55
|
+
bignum_too_long_to_embed_as_string = 1234567890123456789012345
|
56
|
+
expect = bignum_too_long_to_embed_as_string.to_s
|
57
|
+
GC.stress = true
|
58
|
+
|
59
|
+
10.times do |i|
|
60
|
+
tmp = bignum_too_long_to_embed_as_string.to_json
|
61
|
+
raise "'\#{expect}' is expected, but '\#{tmp}'" unless tmp == expect
|
62
|
+
end
|
63
|
+
|
64
|
+
silence do
|
65
|
+
JSON.const_set :SAFE_STATE_PROTOTYPE, nil
|
66
|
+
end
|
67
|
+
|
68
|
+
10.times do |i|
|
69
|
+
assert_raise TypeError do
|
70
|
+
bignum_too_long_to_embed_as_string.to_json
|
71
|
+
end
|
72
|
+
end
|
73
|
+
ensure
|
74
|
+
GC.stress = stress
|
75
|
+
silence do
|
76
|
+
JSON.const_set :SAFE_STATE_PROTOTYPE, const
|
77
|
+
end
|
78
|
+
end if JSON.const_defined?("Ext") && RUBY_ENGINE != 'jruby'
|
79
|
+
|
43
80
|
def test_generate
|
44
81
|
json = generate(@hash)
|
45
82
|
assert_equal(parse(@json2), parse(json))
|
@@ -55,6 +92,11 @@ EOT
|
|
55
92
|
end
|
56
93
|
|
57
94
|
def test_generate_pretty
|
95
|
+
json = pretty_generate({})
|
96
|
+
assert_equal(<<'EOT'.chomp, json)
|
97
|
+
{
|
98
|
+
}
|
99
|
+
EOT
|
58
100
|
json = pretty_generate(@hash)
|
59
101
|
# hashes aren't (insertion) ordered on every ruby implementation
|
60
102
|
# assert_equal(@json3, json)
|
@@ -136,6 +178,7 @@ EOT
|
|
136
178
|
:ascii_only => false,
|
137
179
|
:buffer_initial_length => 1024,
|
138
180
|
:depth => 0,
|
181
|
+
:escape_slash => false,
|
139
182
|
:indent => " ",
|
140
183
|
:max_nesting => 100,
|
141
184
|
:object_nl => "\n",
|
@@ -152,6 +195,7 @@ EOT
|
|
152
195
|
:ascii_only => false,
|
153
196
|
:buffer_initial_length => 1024,
|
154
197
|
:depth => 0,
|
198
|
+
:escape_slash => false,
|
155
199
|
:indent => "",
|
156
200
|
:max_nesting => 100,
|
157
201
|
:object_nl => "",
|
@@ -168,6 +212,7 @@ EOT
|
|
168
212
|
:ascii_only => false,
|
169
213
|
:buffer_initial_length => 1024,
|
170
214
|
:depth => 0,
|
215
|
+
:escape_slash => false,
|
171
216
|
:indent => "",
|
172
217
|
:max_nesting => 0,
|
173
218
|
:object_nl => "",
|
@@ -356,6 +401,10 @@ EOT
|
|
356
401
|
json = '["/"]'
|
357
402
|
assert_equal json, generate(data)
|
358
403
|
#
|
404
|
+
data = [ '/' ]
|
405
|
+
json = '["\/"]'
|
406
|
+
assert_equal json, generate(data, :escape_slash => true)
|
407
|
+
#
|
359
408
|
data = ['"']
|
360
409
|
json = '["\""]'
|
361
410
|
assert_equal json, generate(data)
|
@@ -374,4 +423,10 @@ EOT
|
|
374
423
|
assert_equal '["foo"]', JSON.generate([s.new('foo')])
|
375
424
|
end
|
376
425
|
end
|
426
|
+
|
427
|
+
if defined?(Encoding)
|
428
|
+
def test_nonutf8_encoding
|
429
|
+
assert_equal("\"5\u{b0}\"", "5\xb0".force_encoding("iso-8859-1").to_json)
|
430
|
+
end
|
431
|
+
end
|
377
432
|
end
|