json 2.7.2 → 2.9.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.
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.1'
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