json_pure 2.3.0 → 2.5.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.
Files changed (64) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGES.md +55 -0
  3. data/README.md +16 -0
  4. data/VERSION +1 -1
  5. data/json_pure.gemspec +49 -26
  6. data/lib/json.rb +549 -29
  7. data/lib/json/add/complex.rb +0 -1
  8. data/lib/json/add/rational.rb +0 -1
  9. data/lib/json/common.rb +370 -123
  10. data/lib/json/pure/generator.rb +29 -9
  11. data/lib/json/pure/parser.rb +22 -4
  12. data/lib/json/version.rb +1 -1
  13. data/tests/fixtures/fail29.json +1 -0
  14. data/tests/fixtures/fail30.json +1 -0
  15. data/tests/fixtures/fail31.json +1 -0
  16. data/tests/fixtures/fail32.json +1 -0
  17. data/tests/json_addition_test.rb +0 -4
  18. data/tests/json_common_interface_test.rb +43 -0
  19. data/tests/json_fixtures_test.rb +9 -1
  20. data/tests/json_generator_test.rb +16 -38
  21. data/tests/json_parser_test.rb +25 -0
  22. data/tests/lib/core_assertions.rb +763 -0
  23. data/tests/lib/envutil.rb +365 -0
  24. data/tests/lib/find_executable.rb +22 -0
  25. data/tests/lib/helper.rb +4 -0
  26. data/tests/ractor_test.rb +30 -0
  27. data/tests/test_helper.rb +3 -3
  28. metadata +27 -48
  29. data/.gitignore +0 -18
  30. data/.travis.yml +0 -24
  31. data/README-json-jruby.md +0 -33
  32. data/Rakefile +0 -413
  33. data/diagrams/.keep +0 -0
  34. data/ext/json/ext/fbuffer/fbuffer.h +0 -187
  35. data/ext/json/ext/generator/depend +0 -1
  36. data/ext/json/ext/generator/extconf.rb +0 -4
  37. data/ext/json/ext/generator/generator.c +0 -1499
  38. data/ext/json/ext/generator/generator.h +0 -171
  39. data/ext/json/ext/parser/depend +0 -1
  40. data/ext/json/ext/parser/extconf.rb +0 -6
  41. data/ext/json/ext/parser/parser.c +0 -2136
  42. data/ext/json/ext/parser/parser.h +0 -91
  43. data/ext/json/ext/parser/parser.rl +0 -896
  44. data/ext/json/extconf.rb +0 -2
  45. data/install.rb +0 -23
  46. data/java/src/json/ext/ByteListTranscoder.java +0 -166
  47. data/java/src/json/ext/Generator.java +0 -466
  48. data/java/src/json/ext/GeneratorMethods.java +0 -231
  49. data/java/src/json/ext/GeneratorService.java +0 -42
  50. data/java/src/json/ext/GeneratorState.java +0 -490
  51. data/java/src/json/ext/OptionsReader.java +0 -113
  52. data/java/src/json/ext/Parser.java +0 -2362
  53. data/java/src/json/ext/Parser.rl +0 -893
  54. data/java/src/json/ext/ParserService.java +0 -34
  55. data/java/src/json/ext/RuntimeInfo.java +0 -116
  56. data/java/src/json/ext/StringDecoder.java +0 -166
  57. data/java/src/json/ext/StringEncoder.java +0 -111
  58. data/java/src/json/ext/Utils.java +0 -88
  59. data/json-java.gemspec +0 -37
  60. data/json.gemspec +0 -0
  61. data/references/rfc7159.txt +0 -899
  62. data/tools/diff.sh +0 -18
  63. data/tools/fuzz.rb +0 -131
  64. data/tools/server.rb +0 -62
@@ -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,13 +419,13 @@ 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
 
408
- # Module that holds the extinding methods if, the String module is
428
+ # Module that holds the extending methods if, the String module is
409
429
  # included.
410
430
  module Extend
411
431
  # Raw Strings are JSON Objects (the raw bytes are stored in an
@@ -49,7 +49,7 @@ module JSON
49
49
  )+
50
50
  )mx
51
51
 
52
- UNPARSED = Object.new.freeze
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 if a matching class and create_id was found. This
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
@@ -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)
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: false
2
2
  module JSON
3
3
  # JSON version
4
- VERSION = '2.3.0'
4
+ VERSION = '2.5.1'
5
5
  VERSION_ARRAY = VERSION.split(/\./).map { |x| x.to_i } # :nodoc:
6
6
  VERSION_MAJOR = VERSION_ARRAY[0] # :nodoc:
7
7
  VERSION_MINOR = VERSION_ARRAY[1] # :nodoc:
@@ -0,0 +1 @@
1
+ {
@@ -0,0 +1 @@
1
+ [
@@ -0,0 +1 @@
1
+ [1, 2, 3,
@@ -0,0 +1 @@
1
+ {"foo": "bar"
@@ -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
@@ -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}.json')
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
@@ -48,36 +48,6 @@ EOT
48
48
  $VERBOSE = v
49
49
  end
50
50
 
51
- def test_remove_const_segv
52
- return if RUBY_ENGINE == 'jruby'
53
- stress = GC.stress
54
- const = JSON::SAFE_STATE_PROTOTYPE.dup
55
-
56
- bignum_too_long_to_embed_as_string = 1234567890123456789012345
57
- expect = bignum_too_long_to_embed_as_string.to_s
58
- GC.stress = true
59
-
60
- 10.times do |i|
61
- tmp = bignum_too_long_to_embed_as_string.to_json
62
- raise "'\#{expect}' is expected, but '\#{tmp}'" unless tmp == expect
63
- end
64
-
65
- silence do
66
- JSON.const_set :SAFE_STATE_PROTOTYPE, nil
67
- end
68
-
69
- 10.times do |i|
70
- assert_raise TypeError do
71
- bignum_too_long_to_embed_as_string.to_json
72
- end
73
- end
74
- ensure
75
- GC.stress = stress
76
- silence do
77
- JSON.const_set :SAFE_STATE_PROTOTYPE, const
78
- end
79
- end if JSON.const_defined?("Ext")
80
-
81
51
  def test_generate
82
52
  json = generate(@hash)
83
53
  assert_equal(parse(@json2), parse(json))
@@ -93,6 +63,11 @@ EOT
93
63
  end
94
64
 
95
65
  def test_generate_pretty
66
+ json = pretty_generate({})
67
+ assert_equal(<<'EOT'.chomp, json)
68
+ {
69
+ }
70
+ EOT
96
71
  json = pretty_generate(@hash)
97
72
  # hashes aren't (insertion) ordered on every ruby implementation
98
73
  # assert_equal(@json3, json)
@@ -167,13 +142,14 @@ EOT
167
142
  end
168
143
 
169
144
  def test_pretty_state
170
- state = PRETTY_STATE_PROTOTYPE.dup
145
+ state = JSON.create_pretty_state
171
146
  assert_equal({
172
147
  :allow_nan => false,
173
148
  :array_nl => "\n",
174
149
  :ascii_only => false,
175
150
  :buffer_initial_length => 1024,
176
151
  :depth => 0,
152
+ :escape_slash => false,
177
153
  :indent => " ",
178
154
  :max_nesting => 100,
179
155
  :object_nl => "\n",
@@ -183,13 +159,14 @@ EOT
183
159
  end
184
160
 
185
161
  def test_safe_state
186
- state = SAFE_STATE_PROTOTYPE.dup
162
+ state = JSON::State.new
187
163
  assert_equal({
188
164
  :allow_nan => false,
189
165
  :array_nl => "",
190
166
  :ascii_only => false,
191
167
  :buffer_initial_length => 1024,
192
168
  :depth => 0,
169
+ :escape_slash => false,
193
170
  :indent => "",
194
171
  :max_nesting => 100,
195
172
  :object_nl => "",
@@ -199,13 +176,14 @@ EOT
199
176
  end
200
177
 
201
178
  def test_fast_state
202
- state = FAST_STATE_PROTOTYPE.dup
179
+ state = JSON.create_fast_state
203
180
  assert_equal({
204
181
  :allow_nan => false,
205
182
  :array_nl => "",
206
183
  :ascii_only => false,
207
184
  :buffer_initial_length => 1024,
208
185
  :depth => 0,
186
+ :escape_slash => false,
209
187
  :indent => "",
210
188
  :max_nesting => 0,
211
189
  :object_nl => "",
@@ -234,12 +212,8 @@ EOT
234
212
 
235
213
  def test_depth
236
214
  ary = []; ary << ary
237
- assert_equal 0, JSON::SAFE_STATE_PROTOTYPE.depth
238
215
  assert_raise(JSON::NestingError) { generate(ary) }
239
- assert_equal 0, JSON::SAFE_STATE_PROTOTYPE.depth
240
- assert_equal 0, JSON::PRETTY_STATE_PROTOTYPE.depth
241
216
  assert_raise(JSON::NestingError) { JSON.pretty_generate(ary) }
242
- assert_equal 0, JSON::PRETTY_STATE_PROTOTYPE.depth
243
217
  s = JSON.state.new
244
218
  assert_equal 0, s.depth
245
219
  assert_raise(JSON::NestingError) { ary.to_json(s) }
@@ -258,7 +232,7 @@ EOT
258
232
  end
259
233
 
260
234
  def test_gc
261
- if respond_to?(:assert_in_out_err)
235
+ if respond_to?(:assert_in_out_err) && !(RUBY_PLATFORM =~ /java/)
262
236
  assert_in_out_err(%w[-rjson --disable-gems], <<-EOS, [], [])
263
237
  bignum_too_long_to_embed_as_string = 1234567890123456789012345
264
238
  expect = bignum_too_long_to_embed_as_string.to_s
@@ -394,6 +368,10 @@ EOT
394
368
  json = '["/"]'
395
369
  assert_equal json, generate(data)
396
370
  #
371
+ data = [ '/' ]
372
+ json = '["\/"]'
373
+ assert_equal json, generate(data, :escape_slash => true)
374
+ #
397
375
  data = ['"']
398
376
  json = '["\""]'
399
377
  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