json 2.3.1 → 2.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
- string.gsub!(/["\\\x0-\x1f]/) { MAP[$&] }
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
- string.gsub!(/["\\\x0-\x1f]/n) { MAP[$&] }
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
@@ -314,8 +332,10 @@ module JSON
314
332
  first = false
315
333
  }
316
334
  depth = state.depth -= 1
317
- result << state.object_nl
318
- result << state.indent * depth if indent
335
+ unless first
336
+ result << state.object_nl
337
+ result << state.indent * depth if indent
338
+ end
319
339
  result << '}'
320
340
  result
321
341
  end
@@ -399,9 +419,9 @@ module JSON
399
419
  string = encode(::Encoding::UTF_8)
400
420
  end
401
421
  if state.ascii_only?
402
- '"' << JSON.utf8_to_json_ascii(string) << '"'
422
+ '"' << JSON.utf8_to_json_ascii(string, state.escape_slash) << '"'
403
423
  else
404
- '"' << JSON.utf8_to_json(string) << '"'
424
+ '"' << JSON.utf8_to_json(string, state.escape_slash) << '"'
405
425
  end
406
426
  end
407
427
 
@@ -61,6 +61,8 @@ 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
@@ -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
@@ -242,8 +256,10 @@ module JSON
242
256
  @max_nesting.nonzero? && @current_nesting > @max_nesting
243
257
  result = @array_class.new
244
258
  delim = false
245
- until eos?
259
+ loop do
246
260
  case
261
+ when eos?
262
+ raise ParserError, "unexpected end of string while parsing array"
247
263
  when !UNPARSED.equal?(value = parse_value)
248
264
  delim = false
249
265
  result << value
@@ -274,8 +290,10 @@ module JSON
274
290
  @max_nesting.nonzero? && @current_nesting > @max_nesting
275
291
  result = @object_class.new
276
292
  delim = false
277
- until eos?
293
+ loop do
278
294
  case
295
+ when eos?
296
+ raise ParserError, "unexpected end of string while parsing object"
279
297
  when !UNPARSED.equal?(string = parse_string)
280
298
  skip(IGNORE)
281
299
  unless scan(PAIR_DELIMITER)
@@ -195,9 +195,5 @@ class JSONAdditionTest < Test::Unit::TestCase
195
195
  def test_set
196
196
  s = Set.new([:a, :b, :c, :a])
197
197
  assert_equal s, JSON.parse(JSON(s), :create_additions => true)
198
- ss = SortedSet.new([:d, :b, :a, :c])
199
- ss_again = JSON.parse(JSON(ss), :create_additions => true)
200
- assert_kind_of ss.class, ss_again
201
- assert_equal ss, ss_again
202
198
  end
203
199
  end
@@ -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
@@ -10,6 +10,7 @@ class JSONFixturesTest < Test::Unit::TestCase
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
@@ -49,7 +49,6 @@ EOT
49
49
  end
50
50
 
51
51
  def test_remove_const_segv
52
- return if RUBY_ENGINE == 'jruby'
53
52
  stress = GC.stress
54
53
  const = JSON::SAFE_STATE_PROTOTYPE.dup
55
54
 
@@ -76,7 +75,7 @@ EOT
76
75
  silence do
77
76
  JSON.const_set :SAFE_STATE_PROTOTYPE, const
78
77
  end
79
- end if JSON.const_defined?("Ext")
78
+ end if JSON.const_defined?("Ext") && RUBY_ENGINE != 'jruby'
80
79
 
81
80
  def test_generate
82
81
  json = generate(@hash)
@@ -93,6 +92,11 @@ EOT
93
92
  end
94
93
 
95
94
  def test_generate_pretty
95
+ json = pretty_generate({})
96
+ assert_equal(<<'EOT'.chomp, json)
97
+ {
98
+ }
99
+ EOT
96
100
  json = pretty_generate(@hash)
97
101
  # hashes aren't (insertion) ordered on every ruby implementation
98
102
  # assert_equal(@json3, json)
@@ -174,6 +178,7 @@ EOT
174
178
  :ascii_only => false,
175
179
  :buffer_initial_length => 1024,
176
180
  :depth => 0,
181
+ :escape_slash => false,
177
182
  :indent => " ",
178
183
  :max_nesting => 100,
179
184
  :object_nl => "\n",
@@ -190,6 +195,7 @@ EOT
190
195
  :ascii_only => false,
191
196
  :buffer_initial_length => 1024,
192
197
  :depth => 0,
198
+ :escape_slash => false,
193
199
  :indent => "",
194
200
  :max_nesting => 100,
195
201
  :object_nl => "",
@@ -206,6 +212,7 @@ EOT
206
212
  :ascii_only => false,
207
213
  :buffer_initial_length => 1024,
208
214
  :depth => 0,
215
+ :escape_slash => false,
209
216
  :indent => "",
210
217
  :max_nesting => 0,
211
218
  :object_nl => "",
@@ -394,6 +401,10 @@ EOT
394
401
  json = '["/"]'
395
402
  assert_equal json, generate(data)
396
403
  #
404
+ data = [ '/' ]
405
+ json = '["\/"]'
406
+ assert_equal json, generate(data, :escape_slash => true)
407
+ #
397
408
  data = ['"']
398
409
  json = '["\""]'
399
410
  assert_equal json, generate(data)
@@ -218,6 +218,17 @@ class JSONParserTest < Test::Unit::TestCase
218
218
  end
219
219
  end
220
220
 
221
+ def test_freeze
222
+ assert_predicate parse('{}', :freeze => true), :frozen?
223
+ assert_predicate parse('[]', :freeze => true), :frozen?
224
+ assert_predicate parse('"foo"', :freeze => true), :frozen?
225
+
226
+ if string_deduplication_available?
227
+ assert_same(-'foo', parse('"foo"', :freeze => true))
228
+ assert_same(-'foo', parse('{"foo": 1}', :freeze => true).keys.first)
229
+ end
230
+ end
231
+
221
232
  def test_parse_comments
222
233
  json = <<EOT
223
234
  {
@@ -293,6 +304,10 @@ EOT
293
304
  json = '["\\\'"]'
294
305
  data = ["'"]
295
306
  assert_equal data, parse(json)
307
+
308
+ json = '["\/"]'
309
+ data = [ '/' ]
310
+ assert_equal data, parse(json)
296
311
  end
297
312
 
298
313
  class SubArray < Array
@@ -464,6 +479,16 @@ EOT
464
479
 
465
480
  private
466
481
 
482
+ def string_deduplication_available?
483
+ r1 = rand.to_s
484
+ r2 = r1.dup
485
+ begin
486
+ (-r1).equal?(-r2)
487
+ rescue NoMethodError
488
+ false # No String#-@
489
+ end
490
+ end
491
+
467
492
  def assert_equal_float(expected, actual, delta = 1e-2)
468
493
  Array === expected and expected = expected.first
469
494
  Array === actual and actual = actual.first
@@ -1,12 +1,12 @@
1
1
  case ENV['JSON']
2
2
  when 'pure'
3
- $:.unshift 'lib'
3
+ $:.unshift File.join(__dir__, '../lib')
4
4
  require 'json/pure'
5
5
  when 'ext'
6
- $:.unshift 'ext', 'lib'
6
+ $:.unshift File.join(__dir__, '../ext'), File.join(__dir__, '../lib')
7
7
  require 'json/ext'
8
8
  else
9
- $:.unshift 'ext', 'lib'
9
+ $:.unshift File.join(__dir__, '../ext'), File.join(__dir__, '../lib')
10
10
  require 'json'
11
11
  end
12
12
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: json
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.3.1
4
+ version: 2.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Florian Frank
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-06-30 00:00:00.000000000 Z
11
+ date: 2020-12-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -58,6 +58,7 @@ files:
58
58
  - ".travis.yml"
59
59
  - CHANGES.md
60
60
  - Gemfile
61
+ - LICENSE
61
62
  - README-json-jruby.md
62
63
  - README.md
63
64
  - Rakefile
@@ -188,7 +189,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
188
189
  - !ruby/object:Gem::Version
189
190
  version: '0'
190
191
  requirements: []
191
- rubygems_version: 3.1.2
192
+ rubygems_version: 3.1.4
192
193
  signing_key:
193
194
  specification_version: 4
194
195
  summary: JSON Implementation for Ruby