json 2.7.2 → 2.9.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/json/ext.rb CHANGED
@@ -1,14 +1,22 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'json/common'
2
4
 
3
5
  module JSON
4
6
  # This module holds all the modules/classes that implement JSON's
5
7
  # functionality as C extensions.
6
8
  module Ext
7
- require 'json/ext/parser'
8
- require 'json/ext/generator'
9
- $DEBUG and warn "Using Ext extension for JSON."
10
- JSON.parser = Parser
11
- JSON.generator = Generator
9
+ if RUBY_ENGINE == 'truffleruby'
10
+ require 'json/ext/parser'
11
+ require 'json/truffle_ruby/generator'
12
+ JSON.parser = Parser
13
+ JSON.generator = ::JSON::TruffleRuby::Generator
14
+ else
15
+ require 'json/ext/parser'
16
+ require 'json/ext/generator'
17
+ JSON.parser = Parser
18
+ JSON.generator = Generator
19
+ end
12
20
  end
13
21
 
14
22
  JSON_LOADED = true unless defined?(::JSON::JSON_LOADED)
@@ -1,4 +1,4 @@
1
- #frozen_string_literal: false
1
+ # frozen_string_literal: true
2
2
  begin
3
3
  require 'ostruct'
4
4
  rescue LoadError
@@ -1,103 +1,111 @@
1
- #frozen_string_literal: false
1
+ # frozen_string_literal: true
2
2
  module JSON
3
- MAP = {
4
- "\x0" => '\u0000',
5
- "\x1" => '\u0001',
6
- "\x2" => '\u0002',
7
- "\x3" => '\u0003',
8
- "\x4" => '\u0004',
9
- "\x5" => '\u0005',
10
- "\x6" => '\u0006',
11
- "\x7" => '\u0007',
12
- "\b" => '\b',
13
- "\t" => '\t',
14
- "\n" => '\n',
15
- "\xb" => '\u000b',
16
- "\f" => '\f',
17
- "\r" => '\r',
18
- "\xe" => '\u000e',
19
- "\xf" => '\u000f',
20
- "\x10" => '\u0010',
21
- "\x11" => '\u0011',
22
- "\x12" => '\u0012',
23
- "\x13" => '\u0013',
24
- "\x14" => '\u0014',
25
- "\x15" => '\u0015',
26
- "\x16" => '\u0016',
27
- "\x17" => '\u0017',
28
- "\x18" => '\u0018',
29
- "\x19" => '\u0019',
30
- "\x1a" => '\u001a',
31
- "\x1b" => '\u001b',
32
- "\x1c" => '\u001c',
33
- "\x1d" => '\u001d',
34
- "\x1e" => '\u001e',
35
- "\x1f" => '\u001f',
36
- '"' => '\"',
37
- '\\' => '\\\\',
38
- } # :nodoc:
39
-
40
- ESCAPE_PATTERN = /[\/"\\\x0-\x1f]/n # :nodoc:
41
-
42
- SCRIPT_SAFE_MAP = MAP.merge(
43
- '/' => '\\/',
44
- "\u2028".b => '\u2028',
45
- "\u2029".b => '\u2029',
46
- )
47
-
48
- SCRIPT_SAFE_ESCAPE_PATTERN = Regexp.union(ESCAPE_PATTERN, "\u2028".b, "\u2029".b)
49
-
50
- # Convert a UTF8 encoded Ruby string _string_ to a JSON string, encoded with
51
- # UTF16 big endian characters as \u????, and return it.
52
- def utf8_to_json(string, script_safe = false) # :nodoc:
53
- string = string.dup
54
- string.force_encoding(::Encoding::ASCII_8BIT)
55
- if script_safe
56
- string.gsub!(SCRIPT_SAFE_ESCAPE_PATTERN) { SCRIPT_SAFE_MAP[$&] || $& }
57
- else
58
- string.gsub!(ESCAPE_PATTERN) { MAP[$&] || $& }
59
- end
60
- string.force_encoding(::Encoding::UTF_8)
61
- string
62
- end
3
+ module TruffleRuby
4
+ module Generator
5
+ MAP = {
6
+ "\x0" => '\u0000',
7
+ "\x1" => '\u0001',
8
+ "\x2" => '\u0002',
9
+ "\x3" => '\u0003',
10
+ "\x4" => '\u0004',
11
+ "\x5" => '\u0005',
12
+ "\x6" => '\u0006',
13
+ "\x7" => '\u0007',
14
+ "\b" => '\b',
15
+ "\t" => '\t',
16
+ "\n" => '\n',
17
+ "\xb" => '\u000b',
18
+ "\f" => '\f',
19
+ "\r" => '\r',
20
+ "\xe" => '\u000e',
21
+ "\xf" => '\u000f',
22
+ "\x10" => '\u0010',
23
+ "\x11" => '\u0011',
24
+ "\x12" => '\u0012',
25
+ "\x13" => '\u0013',
26
+ "\x14" => '\u0014',
27
+ "\x15" => '\u0015',
28
+ "\x16" => '\u0016',
29
+ "\x17" => '\u0017',
30
+ "\x18" => '\u0018',
31
+ "\x19" => '\u0019',
32
+ "\x1a" => '\u001a',
33
+ "\x1b" => '\u001b',
34
+ "\x1c" => '\u001c',
35
+ "\x1d" => '\u001d',
36
+ "\x1e" => '\u001e',
37
+ "\x1f" => '\u001f',
38
+ '"' => '\"',
39
+ '\\' => '\\\\',
40
+ }.freeze # :nodoc:
41
+
42
+ ESCAPE_PATTERN = /[\/"\\\x0-\x1f]/n # :nodoc:
43
+
44
+ SCRIPT_SAFE_MAP = MAP.merge(
45
+ '/' => '\\/',
46
+ "\u2028".b => '\u2028',
47
+ "\u2029".b => '\u2029',
48
+ ).freeze
49
+
50
+ SCRIPT_SAFE_ESCAPE_PATTERN = Regexp.union(ESCAPE_PATTERN, "\u2028".b, "\u2029".b)
51
+
52
+ # Convert a UTF8 encoded Ruby string _string_ to a JSON string, encoded with
53
+ # UTF16 big endian characters as \u????, and return it.
54
+ def utf8_to_json(string, script_safe = false) # :nodoc:
55
+ string = string.b
56
+ if script_safe
57
+ string.gsub!(SCRIPT_SAFE_ESCAPE_PATTERN) { SCRIPT_SAFE_MAP[$&] || $& }
58
+ else
59
+ string.gsub!(ESCAPE_PATTERN) { MAP[$&] || $& }
60
+ end
61
+ string.force_encoding(::Encoding::UTF_8)
62
+ string
63
+ end
63
64
 
64
- def utf8_to_json_ascii(string, script_safe = false) # :nodoc:
65
- string = string.dup
66
- string.force_encoding(::Encoding::ASCII_8BIT)
67
- map = script_safe ? SCRIPT_SAFE_MAP : MAP
68
- string.gsub!(/[\/"\\\x0-\x1f]/n) { map[$&] || $& }
69
- string.gsub!(/(
70
- (?:
71
- [\xc2-\xdf][\x80-\xbf] |
72
- [\xe0-\xef][\x80-\xbf]{2} |
73
- [\xf0-\xf4][\x80-\xbf]{3}
74
- )+ |
75
- [\x80-\xc1\xf5-\xff] # invalid
76
- )/nx) { |c|
77
- c.size == 1 and raise GeneratorError, "invalid utf8 byte: '#{c}'"
78
- s = JSON.iconv('utf-16be', 'utf-8', c).unpack('H*')[0]
79
- s.force_encoding(::Encoding::ASCII_8BIT)
80
- s.gsub!(/.{4}/n, '\\\\u\&')
81
- s.force_encoding(::Encoding::UTF_8)
82
- }
83
- string.force_encoding(::Encoding::UTF_8)
84
- string
85
- rescue => e
86
- raise GeneratorError.wrap(e)
87
- end
65
+ def utf8_to_json_ascii(original_string, script_safe = false) # :nodoc:
66
+ string = original_string.b
67
+ map = script_safe ? SCRIPT_SAFE_MAP : MAP
68
+ string.gsub!(/[\/"\\\x0-\x1f]/n) { map[$&] || $& }
69
+ string.gsub!(/(
70
+ (?:
71
+ [\xc2-\xdf][\x80-\xbf] |
72
+ [\xe0-\xef][\x80-\xbf]{2} |
73
+ [\xf0-\xf4][\x80-\xbf]{3}
74
+ )+ |
75
+ [\x80-\xc1\xf5-\xff] # invalid
76
+ )/nx) { |c|
77
+ c.size == 1 and raise GeneratorError.new("invalid utf8 byte: '#{c}'", original_string)
78
+ s = c.encode(::Encoding::UTF_16BE, ::Encoding::UTF_8).unpack('H*')[0]
79
+ s.force_encoding(::Encoding::BINARY)
80
+ s.gsub!(/.{4}/n, '\\\\u\&')
81
+ s.force_encoding(::Encoding::UTF_8)
82
+ }
83
+ string.force_encoding(::Encoding::UTF_8)
84
+ string
85
+ rescue => e
86
+ raise GeneratorError.new(e.message, original_string)
87
+ end
88
88
 
89
- def valid_utf8?(string)
90
- encoding = string.encoding
91
- (encoding == Encoding::UTF_8 || encoding == Encoding::ASCII) &&
92
- string.valid_encoding?
93
- end
94
- module_function :utf8_to_json, :utf8_to_json_ascii, :valid_utf8?
89
+ def valid_utf8?(string)
90
+ encoding = string.encoding
91
+ (encoding == Encoding::UTF_8 || encoding == Encoding::ASCII) &&
92
+ string.valid_encoding?
93
+ end
94
+ module_function :utf8_to_json, :utf8_to_json_ascii, :valid_utf8?
95
95
 
96
- module Pure
97
- module Generator
98
96
  # This class is used to create State instances, that are use to hold data
99
97
  # while generating a JSON text from a Ruby data structure.
100
98
  class State
99
+ def self.generate(obj, opts = nil, io = nil)
100
+ string = new(opts).generate(obj)
101
+ if io
102
+ io.write(string)
103
+ io
104
+ else
105
+ string
106
+ end
107
+ end
108
+
101
109
  # Creates a State object from _opts_, which ought to be Hash to create
102
110
  # a new State instance configured by _opts_, something else to create
103
111
  # an unconfigured instance. If _opts_ is a State object, it is just
@@ -132,7 +140,7 @@ module JSON
132
140
  # * *allow_nan*: true if NaN, Infinity, and -Infinity should be
133
141
  # generated, otherwise an exception is thrown, if these values are
134
142
  # encountered. This options defaults to false.
135
- def initialize(opts = {})
143
+ def initialize(opts = nil)
136
144
  @indent = ''
137
145
  @space = ''
138
146
  @space_before = ''
@@ -140,10 +148,12 @@ module JSON
140
148
  @array_nl = ''
141
149
  @allow_nan = false
142
150
  @ascii_only = false
143
- @script_safe = false
144
- @strict = false
151
+ @depth = 0
145
152
  @buffer_initial_length = 1024
146
- configure opts
153
+ @script_safe = false
154
+ @strict = false
155
+ @max_nesting = 100
156
+ configure(opts) if opts
147
157
  end
148
158
 
149
159
  # This string is used to indent levels in the JSON text.
@@ -219,7 +229,9 @@ module JSON
219
229
  @script_safe
220
230
  end
221
231
 
222
- # Returns true, if forward slashes are escaped. Otherwise returns false.
232
+ # Returns true, if strict mode is enabled. Otherwise returns false.
233
+ # Strict mode only allow serializing JSON native types: Hash, Array,
234
+ # String, Integer, Float, true, false and nil.
223
235
  def strict?
224
236
  @strict
225
237
  end
@@ -237,13 +249,15 @@ module JSON
237
249
  opts.each do |key, value|
238
250
  instance_variable_set "@#{key}", value
239
251
  end
240
- @indent = opts[:indent] if opts.key?(:indent)
241
- @space = opts[:space] if opts.key?(:space)
242
- @space_before = opts[:space_before] if opts.key?(:space_before)
243
- @object_nl = opts[:object_nl] if opts.key?(:object_nl)
244
- @array_nl = opts[:array_nl] if opts.key?(:array_nl)
245
- @allow_nan = !!opts[:allow_nan] if opts.key?(:allow_nan)
246
- @ascii_only = opts[:ascii_only] if opts.key?(:ascii_only)
252
+
253
+ # NOTE: If adding new instance variables here, check whether #generate should check them for #generate_json
254
+ @indent = opts[:indent] || '' if opts.key?(:indent)
255
+ @space = opts[:space] || '' if opts.key?(:space)
256
+ @space_before = opts[:space_before] || '' if opts.key?(:space_before)
257
+ @object_nl = opts[:object_nl] || '' if opts.key?(:object_nl)
258
+ @array_nl = opts[:array_nl] || '' if opts.key?(:array_nl)
259
+ @allow_nan = !!opts[:allow_nan] if opts.key?(:allow_nan)
260
+ @ascii_only = opts[:ascii_only] if opts.key?(:ascii_only)
247
261
  @depth = opts[:depth] || 0
248
262
  @buffer_initial_length ||= opts[:buffer_initial_length]
249
263
 
@@ -286,12 +300,85 @@ module JSON
286
300
  # created this method raises a
287
301
  # GeneratorError exception.
288
302
  def generate(obj)
289
- result = obj.to_json(self)
290
- JSON.valid_utf8?(result) or raise GeneratorError,
291
- "source sequence #{result.inspect} is illegal/malformed utf-8"
303
+ if @indent.empty? and @space.empty? and @space_before.empty? and @object_nl.empty? and @array_nl.empty? and
304
+ !@ascii_only and !@script_safe and @max_nesting == 0 and !@strict
305
+ result = generate_json(obj, ''.dup)
306
+ else
307
+ result = obj.to_json(self)
308
+ end
309
+ JSON::TruffleRuby::Generator.valid_utf8?(result) or raise GeneratorError.new(
310
+ "source sequence #{result.inspect} is illegal/malformed utf-8",
311
+ obj
312
+ )
292
313
  result
293
314
  end
294
315
 
316
+ # Handles @allow_nan, @buffer_initial_length, other ivars must be the default value (see above)
317
+ private def generate_json(obj, buf)
318
+ case obj
319
+ when Hash
320
+ buf << '{'
321
+ first = true
322
+ obj.each_pair do |k,v|
323
+ buf << ',' unless first
324
+
325
+ key_str = k.to_s
326
+ if key_str.class == String
327
+ fast_serialize_string(key_str, buf)
328
+ elsif key_str.is_a?(String)
329
+ generate_json(key_str, buf)
330
+ else
331
+ raise TypeError, "#{k.class}#to_s returns an instance of #{key_str.class}, expected a String"
332
+ end
333
+
334
+ buf << ':'
335
+ generate_json(v, buf)
336
+ first = false
337
+ end
338
+ buf << '}'
339
+ when Array
340
+ buf << '['
341
+ first = true
342
+ obj.each do |e|
343
+ buf << ',' unless first
344
+ generate_json(e, buf)
345
+ first = false
346
+ end
347
+ buf << ']'
348
+ when String
349
+ if obj.class == String
350
+ fast_serialize_string(obj, buf)
351
+ else
352
+ buf << obj.to_json(self)
353
+ end
354
+ when Integer
355
+ buf << obj.to_s
356
+ else
357
+ # Note: Float is handled this way since Float#to_s is slow anyway
358
+ buf << obj.to_json(self)
359
+ end
360
+ end
361
+
362
+ # Assumes !@ascii_only, !@script_safe
363
+ private def fast_serialize_string(string, buf) # :nodoc:
364
+ buf << '"'
365
+ unless string.encoding == ::Encoding::UTF_8
366
+ begin
367
+ string = string.encode(::Encoding::UTF_8)
368
+ rescue Encoding::UndefinedConversionError => error
369
+ raise GeneratorError.new(error.message, string)
370
+ end
371
+ end
372
+ raise GeneratorError.new("source sequence is illegal/malformed utf-8", string) unless string.valid_encoding?
373
+
374
+ if /["\\\x0-\x1f]/n.match?(string)
375
+ buf << string.gsub(/["\\\x0-\x1f]/n, MAP)
376
+ else
377
+ buf << string
378
+ end
379
+ buf << '"'
380
+ end
381
+
295
382
  # Return the value returned by method +name+.
296
383
  def [](name)
297
384
  if respond_to?(name)
@@ -316,9 +403,9 @@ module JSON
316
403
  # Converts this object to a string (calling #to_s), converts
317
404
  # it to a JSON string, and returns the result. This is a fallback, if no
318
405
  # special method #to_json was defined for some object.
319
- def to_json(generator_state)
320
- if generator_state.strict?
321
- raise GeneratorError, "#{self.class} not allowed in JSON"
406
+ def to_json(state = nil, *)
407
+ if state && State.from_state(state).strict?
408
+ raise GeneratorError.new("#{self.class} not allowed in JSON", self)
322
409
  else
323
410
  to_s.to_json
324
411
  end
@@ -345,17 +432,31 @@ module JSON
345
432
  end
346
433
 
347
434
  def json_transform(state)
348
- delim = ",#{state.object_nl}"
349
- result = "{#{state.object_nl}"
350
435
  depth = state.depth += 1
436
+
437
+ if empty?
438
+ state.depth -= 1
439
+ return '{}'
440
+ end
441
+
442
+ delim = ",#{state.object_nl}"
443
+ result = +"{#{state.object_nl}"
351
444
  first = true
352
445
  indent = !state.object_nl.empty?
353
446
  each { |key, value|
354
447
  result << delim unless first
355
448
  result << state.indent * depth if indent
356
- result = "#{result}#{key.to_s.to_json(state)}#{state.space_before}:#{state.space}"
357
- if state.strict?
358
- raise GeneratorError, "#{value.class} not allowed in JSON"
449
+
450
+ key_str = key.to_s
451
+ if key_str.is_a?(String)
452
+ key_json = key_str.to_json(state)
453
+ else
454
+ raise TypeError, "#{key.class}#to_s returns an instance of #{key_str.class}, expected a String"
455
+ end
456
+
457
+ result = +"#{result}#{key_json}#{state.space_before}:#{state.space}"
458
+ if state.strict? && !(false == value || true == value || nil == value || String === value || Array === value || Hash === value || Integer === value || Float === value)
459
+ raise GeneratorError.new("#{value.class} not allowed in JSON", value)
359
460
  elsif value.respond_to?(:to_json)
360
461
  result << value.to_json(state)
361
462
  else
@@ -387,18 +488,28 @@ module JSON
387
488
  private
388
489
 
389
490
  def json_transform(state)
390
- delim = ','
391
- delim << state.array_nl
392
- result = '['
393
- result << state.array_nl
394
491
  depth = state.depth += 1
492
+
493
+ if empty?
494
+ state.depth -= 1
495
+ return '[]'
496
+ end
497
+
498
+ result = '['.dup
499
+ if state.array_nl.empty?
500
+ delim = ","
501
+ else
502
+ result << state.array_nl
503
+ delim = ",#{state.array_nl}"
504
+ end
505
+
395
506
  first = true
396
507
  indent = !state.array_nl.empty?
397
508
  each { |value|
398
509
  result << delim unless first
399
510
  result << state.indent * depth if indent
400
- if state.strict?
401
- raise GeneratorError, "#{value.class} not allowed in JSON"
511
+ if state.strict? && !(false == value || true == value || nil == value || String === value || Array === value || Hash === value || Integer === value || Float === value)
512
+ raise GeneratorError.new("#{value.class} not allowed in JSON", value)
402
513
  elsif value.respond_to?(:to_json)
403
514
  result << value.to_json(state)
404
515
  else
@@ -427,13 +538,13 @@ module JSON
427
538
  if state.allow_nan?
428
539
  to_s
429
540
  else
430
- raise GeneratorError, "#{self} not allowed in JSON"
541
+ raise GeneratorError.new("#{self} not allowed in JSON", self)
431
542
  end
432
543
  when nan?
433
544
  if state.allow_nan?
434
545
  to_s
435
546
  else
436
- raise GeneratorError, "#{self} not allowed in JSON"
547
+ raise GeneratorError.new("#{self} not allowed in JSON", self)
437
548
  end
438
549
  else
439
550
  to_s
@@ -448,15 +559,20 @@ module JSON
448
559
  def to_json(state = nil, *args)
449
560
  state = State.from_state(state)
450
561
  if encoding == ::Encoding::UTF_8
562
+ unless valid_encoding?
563
+ raise GeneratorError.new("source sequence is illegal/malformed utf-8", self)
564
+ end
451
565
  string = self
452
566
  else
453
567
  string = encode(::Encoding::UTF_8)
454
568
  end
455
569
  if state.ascii_only?
456
- '"' << JSON.utf8_to_json_ascii(string, state.script_safe) << '"'
570
+ %("#{JSON::TruffleRuby::Generator.utf8_to_json_ascii(string, state.script_safe)}")
457
571
  else
458
- '"' << JSON.utf8_to_json(string, state.script_safe) << '"'
572
+ %("#{JSON::TruffleRuby::Generator.utf8_to_json(string, state.script_safe)}")
459
573
  end
574
+ rescue Encoding::UndefinedConversionError => error
575
+ raise ::JSON::GeneratorError.new(error.message, self)
460
576
  end
461
577
 
462
578
  # Module that holds the extending methods if, the String module is
data/lib/json/version.rb CHANGED
@@ -1,9 +1,5 @@
1
- # frozen_string_literal: false
1
+ # frozen_string_literal: true
2
+
2
3
  module JSON
3
- # JSON version
4
- VERSION = '2.7.2'
5
- VERSION_ARRAY = VERSION.split(/\./).map { |x| x.to_i } # :nodoc:
6
- VERSION_MAJOR = VERSION_ARRAY[0] # :nodoc:
7
- VERSION_MINOR = VERSION_ARRAY[1] # :nodoc:
8
- VERSION_BUILD = VERSION_ARRAY[2] # :nodoc:
4
+ VERSION = '2.9.0'
9
5
  end
data/lib/json.rb CHANGED
@@ -1,4 +1,4 @@
1
- #frozen_string_literal: false
1
+ # frozen_string_literal: true
2
2
  require 'json/common'
3
3
 
4
4
  ##
@@ -378,13 +378,13 @@ require 'json/common'
378
378
  # json1 = JSON.generate(ruby)
379
379
  # ruby1 = JSON.parse(json1, create_additions: true)
380
380
  # # Make a nice display.
381
- # display = <<EOT
382
- # Generated JSON:
383
- # Without addition: #{json0} (#{json0.class})
384
- # With addition: #{json1} (#{json1.class})
385
- # Parsed JSON:
386
- # Without addition: #{ruby0.inspect} (#{ruby0.class})
387
- # With addition: #{ruby1.inspect} (#{ruby1.class})
381
+ # display = <<~EOT
382
+ # Generated JSON:
383
+ # Without addition: #{json0} (#{json0.class})
384
+ # With addition: #{json1} (#{json1.class})
385
+ # Parsed JSON:
386
+ # Without addition: #{ruby0.inspect} (#{ruby0.class})
387
+ # With addition: #{ruby1.inspect} (#{ruby1.class})
388
388
  # EOT
389
389
  # puts display
390
390
  #
@@ -562,13 +562,13 @@ require 'json/common'
562
562
  # json1 = JSON.generate(foo1)
563
563
  # obj1 = JSON.parse(json1, create_additions: true)
564
564
  # # Make a nice display.
565
- # display = <<EOT
566
- # Generated JSON:
567
- # Without custom addition: #{json0} (#{json0.class})
568
- # With custom addition: #{json1} (#{json1.class})
569
- # Parsed JSON:
570
- # Without custom addition: #{obj0.inspect} (#{obj0.class})
571
- # With custom addition: #{obj1.inspect} (#{obj1.class})
565
+ # display = <<~EOT
566
+ # Generated JSON:
567
+ # Without custom addition: #{json0} (#{json0.class})
568
+ # With custom addition: #{json1} (#{json1.class})
569
+ # Parsed JSON:
570
+ # Without custom addition: #{obj0.inspect} (#{obj0.class})
571
+ # With custom addition: #{obj1.inspect} (#{obj1.class})
572
572
  # EOT
573
573
  # puts display
574
574
  #
@@ -583,10 +583,5 @@ require 'json/common'
583
583
  #
584
584
  module JSON
585
585
  require 'json/version'
586
-
587
- begin
588
- require 'json/ext'
589
- rescue LoadError
590
- require 'json/pure'
591
- end
586
+ require 'json/ext'
592
587
  end