summaryrefslogtreecommitdiff
path: root/test/json/json_generator_test.rb
diff options
context:
space:
mode:
Diffstat (limited to 'test/json/json_generator_test.rb')
-rwxr-xr-xtest/json/json_generator_test.rb975
1 files changed, 810 insertions, 165 deletions
diff --git a/test/json/json_generator_test.rb b/test/json/json_generator_test.rb
index 19b4c7434f..753ee0fbdf 100755
--- a/test/json/json_generator_test.rb
+++ b/test/json/json_generator_test.rb
@@ -1,5 +1,5 @@
#!/usr/bin/env ruby
-# frozen_string_literal: false
+# frozen_string_literal: true
require_relative 'test_helper'
@@ -19,32 +19,24 @@ class JSONGeneratorTest < Test::Unit::TestCase
}
@json2 = '{"a":2,"b":3.141,"c":"c","d":[1,"b",3.14],"e":{"foo":"bar"},' +
'"g":"\\"\\u0000\\u001f","h":1000.0,"i":0.001}'
- @json3 = <<'EOT'.chomp
-{
- "a": 2,
- "b": 3.141,
- "c": "c",
- "d": [
- 1,
- "b",
- 3.14
- ],
- "e": {
- "foo": "bar"
- },
- "g": "\"\u0000\u001f",
- "h": 1000.0,
- "i": 0.001
-}
-EOT
- end
-
- def silence
- v = $VERBOSE
- $VERBOSE = nil
- yield
- ensure
- $VERBOSE = v
+ @json3 = <<~'JSON'.chomp
+ {
+ "a": 2,
+ "b": 3.141,
+ "c": "c",
+ "d": [
+ 1,
+ "b",
+ 3.14
+ ],
+ "e": {
+ "foo": "bar"
+ },
+ "g": "\"\u0000\u001f",
+ "h": 1000.0,
+ "i": 0.001
+ }
+ JSON
end
def test_generate
@@ -67,14 +59,104 @@ EOT
def test_dump_strict
assert_equal '{}', dump({}, strict: true)
+
+ assert_equal '{"array":[42,4.2,"forty-two",true,false,null]}', dump({
+ "array" => [42, 4.2, "forty-two", true, false, nil]
+ }, strict: true)
+
+ assert_equal '{"int":42,"float":4.2,"string":"forty-two","true":true,"false":false,"nil":null,"hash":{}}', dump({
+ "int" => 42,
+ "float" => 4.2,
+ "string" => "forty-two",
+ "true" => true,
+ "false" => false,
+ "nil" => nil,
+ "hash" => {},
+ }, strict: true)
+
+ assert_equal '[]', dump([], strict: true)
+
+ assert_equal '42', dump(42, strict: true)
+ assert_equal 'true', dump(true, strict: true)
+
+ assert_equal '"hello"', dump(:hello, strict: true)
+ assert_equal '"hello"', :hello.to_json(strict: true)
+ assert_equal '"World"', "World".to_json(strict: true)
+ assert_equal '["hello"]', dump([:hello], strict: true)
+ assert_equal '{"hello":"world"}', dump({ hello: :world }, strict: true)
+ end
+
+ def test_not_frozen
+ [
+ [[], '[]'],
+ [{}, '{}'],
+ ["string", '"string"'],
+ [:sym, '"sym"'],
+ [1, '1'],
+ [1.0, '1.0'],
+ [true, 'true'],
+ [false, 'false'],
+ [nil, 'null'],
+ ].each do |(obj, exp)|
+ dumped = dump(obj, strict: true)
+ assert_equal exp, dumped
+ refute_predicate dumped, :frozen?
+ end
+ end
+
+ def test_state_depth_to_json
+ depth = Object.new
+ def depth.to_json(state)
+ JSON::State.from_state(state).depth.to_s
+ end
+
+ assert_equal "0", JSON.generate(depth)
+ assert_equal "[1]", JSON.generate([depth])
+ assert_equal %({"depth":1}), JSON.generate(depth: depth)
+ assert_equal "[[2]]", JSON.generate([[depth]])
+ assert_equal %([{"depth":2}]), JSON.generate([{depth: depth}])
+
+ state = JSON::State.new
+ assert_equal "0", state.generate(depth)
+ assert_equal "[1]", state.generate([depth])
+ assert_equal %({"depth":1}), state.generate(depth: depth)
+ assert_equal "[[2]]", state.generate([[depth]])
+ assert_equal %([{"depth":2}]), state.generate([{depth: depth}])
+ end
+
+ def test_state_depth_to_json_recursive
+ recur = Object.new
+ def recur.to_json(state = nil, *)
+ state = JSON::State.from_state(state)
+ if state.depth < 3
+ state.generate([state.depth, self])
+ else
+ state.generate([state.depth])
+ end
+ end
+
+ assert_raise(NestingError) { JSON.generate(recur, max_nesting: 3) }
+ assert_equal "[0,[1,[2,[3]]]]", JSON.generate(recur, max_nesting: 4)
+
+ state = JSON::State.new(max_nesting: 3)
+ assert_raise(NestingError) { state.generate(recur) }
+ state.max_nesting = 4
+ assert_equal "[0,[1,[2,[3]]]]", JSON.generate(recur, max_nesting: 4)
end
def test_generate_pretty
json = pretty_generate({})
- assert_equal(<<'EOT'.chomp, json)
-{
-}
-EOT
+ assert_equal('{}', json)
+
+ json = pretty_generate({1=>{}, 2=>[], 3=>4})
+ assert_equal(<<~'JSON'.chomp, json)
+ {
+ "1": {},
+ "2": [],
+ "3": 4
+ }
+ JSON
+
json = pretty_generate(@hash)
# hashes aren't (insertion) ordered on every ruby implementation
# assert_equal(@json3, json)
@@ -82,39 +164,57 @@ EOT
parsed_json = parse(json)
assert_equal(@hash, parsed_json)
json = pretty_generate({1=>2})
- assert_equal(<<'EOT'.chomp, json)
-{
- "1": 2
-}
-EOT
+ assert_equal(<<~'JSON'.chomp, json)
+ {
+ "1": 2
+ }
+ JSON
parsed_json = parse(json)
assert_equal({"1"=>2}, parsed_json)
assert_equal '666', pretty_generate(666)
end
+ def test_generate_pretty_custom
+ state = State.new(:space_before => "<psb>", :space => "<ps>", :indent => "<pi>", :object_nl => "\n<po_nl>\n", :array_nl => "<pa_nl>")
+ json = pretty_generate({1=>{}, 2=>['a','b'], 3=>4}, state)
+ assert_equal(<<~'JSON'.chomp, json)
+ {
+ <po_nl>
+ <pi>"1"<psb>:<ps>{},
+ <po_nl>
+ <pi>"2"<psb>:<ps>[<pa_nl><pi><pi>"a",<pa_nl><pi><pi>"b"<pa_nl><pi>],
+ <po_nl>
+ <pi>"3"<psb>:<ps>4
+ <po_nl>
+ }
+ JSON
+ end
+
def test_generate_custom
state = State.new(:space_before => " ", :space => " ", :indent => "<i>", :object_nl => "\n", :array_nl => "<a_nl>")
json = generate({1=>{2=>3,4=>[5,6]}}, state)
- assert_equal(<<'EOT'.chomp, json)
-{
-<i>"1" : {
-<i><i>"2" : 3,
-<i><i>"4" : [<a_nl><i><i><i>5,<a_nl><i><i><i>6<a_nl><i><i>]
-<i>}
-}
-EOT
+ assert_equal(<<~'JSON'.chomp, json)
+ {
+ <i>"1" : {
+ <i><i>"2" : 3,
+ <i><i>"4" : [<a_nl><i><i><i>5,<a_nl><i><i><i>6<a_nl><i><i>]
+ <i>}
+ }
+ JSON
end
def test_fast_generate
- json = fast_generate(@hash)
- assert_equal(parse(@json2), parse(json))
- parsed_json = parse(json)
- assert_equal(@hash, parsed_json)
- json = fast_generate({1=>2})
- assert_equal('{"1":2}', json)
- parsed_json = parse(json)
- assert_equal({"1"=>2}, parsed_json)
- assert_equal '666', fast_generate(666)
+ assert_deprecated_warning(/fast_generate/) do
+ json = fast_generate(@hash)
+ assert_equal(parse(@json2), parse(json))
+ parsed_json = parse(json)
+ assert_equal(@hash, parsed_json)
+ json = fast_generate({1=>2})
+ assert_equal('{"1":2}', json)
+ parsed_json = parse(json)
+ assert_equal({"1"=>2}, parsed_json)
+ assert_equal '666', fast_generate(666)
+ end
end
def test_own_state
@@ -135,7 +235,9 @@ EOT
assert_equal('{"1":2}', json)
s = JSON.state.new
assert s.check_circular?
- assert s[:check_circular?]
+ assert_deprecated_warning(/JSON::State/) do
+ assert s[:check_circular?]
+ end
h = { 1=>2 }
h[3] = h
assert_raise(JSON::NestingError) { generate(h) }
@@ -145,32 +247,38 @@ EOT
a << a
assert_raise(JSON::NestingError) { generate(a, s) }
assert s.check_circular?
- assert s[:check_circular?]
+ assert_deprecated_warning(/JSON::State/) do
+ assert s[:check_circular?]
+ end
end
- def test_pretty_state
- state = JSON.create_pretty_state
- assert_equal({
- :allow_nan => false,
- :array_nl => "\n",
- :ascii_only => false,
- :buffer_initial_length => 1024,
- :depth => 0,
- :script_safe => false,
- :strict => false,
- :indent => " ",
- :max_nesting => 100,
- :object_nl => "\n",
- :space => " ",
- :space_before => "",
- }.sort_by { |n,| n.to_s }, state.to_h.sort_by { |n,| n.to_s })
+ def test_falsy_state
+ object = { foo: [1, 2], bar: { egg: :spam }}
+ expected_json = JSON.generate(
+ object,
+ array_nl: "",
+ indent: "",
+ object_nl: "",
+ space: "",
+ space_before: "",
+ )
+
+ assert_equal expected_json, JSON.generate(
+ object,
+ array_nl: nil,
+ indent: nil,
+ object_nl: nil,
+ space: nil,
+ space_before: nil,
+ )
end
- def test_safe_state
+ def test_state_defaults
state = JSON::State.new
assert_equal({
:allow_nan => false,
:array_nl => "",
+ :as_json => false,
:ascii_only => false,
:buffer_initial_length => 1024,
:depth => 0,
@@ -182,20 +290,20 @@ EOT
:space => "",
:space_before => "",
}.sort_by { |n,| n.to_s }, state.to_h.sort_by { |n,| n.to_s })
- end
- def test_fast_state
- state = JSON.create_fast_state
+ state = JSON::State.new(allow_duplicate_key: true)
assert_equal({
+ :allow_duplicate_key => true,
:allow_nan => false,
:array_nl => "",
+ :as_json => false,
:ascii_only => false,
:buffer_initial_length => 1024,
:depth => 0,
:script_safe => false,
:strict => false,
:indent => "",
- :max_nesting => 0,
+ :max_nesting => 100,
:object_nl => "",
:space => "",
:space_before => "",
@@ -203,31 +311,122 @@ EOT
end
def test_allow_nan
- assert_raise(GeneratorError) { generate([JSON::NaN]) }
- assert_equal '[NaN]', generate([JSON::NaN], :allow_nan => true)
- assert_raise(GeneratorError) { fast_generate([JSON::NaN]) }
- assert_raise(GeneratorError) { pretty_generate([JSON::NaN]) }
- assert_equal "[\n NaN\n]", pretty_generate([JSON::NaN], :allow_nan => true)
- assert_raise(GeneratorError) { generate([JSON::Infinity]) }
- assert_equal '[Infinity]', generate([JSON::Infinity], :allow_nan => true)
- assert_raise(GeneratorError) { fast_generate([JSON::Infinity]) }
- assert_raise(GeneratorError) { pretty_generate([JSON::Infinity]) }
- assert_equal "[\n Infinity\n]", pretty_generate([JSON::Infinity], :allow_nan => true)
- assert_raise(GeneratorError) { generate([JSON::MinusInfinity]) }
- assert_equal '[-Infinity]', generate([JSON::MinusInfinity], :allow_nan => true)
- assert_raise(GeneratorError) { fast_generate([JSON::MinusInfinity]) }
- assert_raise(GeneratorError) { pretty_generate([JSON::MinusInfinity]) }
- assert_equal "[\n -Infinity\n]", pretty_generate([JSON::MinusInfinity], :allow_nan => true)
+ assert_deprecated_warning(/fast_generate/) do
+ error = assert_raise(GeneratorError) { generate([JSON::NaN]) }
+ assert_same JSON::NaN, error.invalid_object
+ assert_equal '[NaN]', generate([JSON::NaN], :allow_nan => true)
+ assert_raise(GeneratorError) { fast_generate([JSON::NaN]) }
+ assert_raise(GeneratorError) { pretty_generate([JSON::NaN]) }
+ assert_equal "[\n NaN\n]", pretty_generate([JSON::NaN], :allow_nan => true)
+ error = assert_raise(GeneratorError) { generate([JSON::Infinity]) }
+ assert_same JSON::Infinity, error.invalid_object
+ assert_equal '[Infinity]', generate([JSON::Infinity], :allow_nan => true)
+ assert_raise(GeneratorError) { fast_generate([JSON::Infinity]) }
+ assert_raise(GeneratorError) { pretty_generate([JSON::Infinity]) }
+ assert_equal "[\n Infinity\n]", pretty_generate([JSON::Infinity], :allow_nan => true)
+ error = assert_raise(GeneratorError) { generate([JSON::MinusInfinity]) }
+ assert_same JSON::MinusInfinity, error.invalid_object
+ assert_equal '[-Infinity]', generate([JSON::MinusInfinity], :allow_nan => true)
+ assert_raise(GeneratorError) { fast_generate([JSON::MinusInfinity]) }
+ assert_raise(GeneratorError) { pretty_generate([JSON::MinusInfinity]) }
+ assert_equal "[\n -Infinity\n]", pretty_generate([JSON::MinusInfinity], :allow_nan => true)
+ end
+ end
+
+ # An object that changes state.depth when it receives to_json(state)
+ def bad_to_json
+ obj = Object.new
+ def obj.to_json(state)
+ state.depth += 1
+ "{#{state.object_nl}"\
+ "#{state.indent * state.depth}\"foo\":#{state.space}1#{state.object_nl}"\
+ "#{state.indent * (state.depth - 1)}}"
+ end
+ obj
+ end
+
+ def test_depth_restored_bad_to_json
+ state = JSON::State.new
+ state.generate(bad_to_json)
+ assert_equal 0, state.depth
+ end
+
+ def test_depth_restored_bad_to_json_in_Array
+ assert_equal <<~JSON.chomp, JSON.pretty_generate([bad_to_json] * 2)
+ [
+ {
+ "foo": 1
+ },
+ {
+ "foo": 1
+ }
+ ]
+ JSON
+ state = JSON::State.new
+ state.generate([bad_to_json])
+ assert_equal 0, state.depth
+ end
+
+ def test_depth_restored_bad_to_json_in_Hash
+ assert_equal <<~JSON.chomp, JSON.pretty_generate(a: bad_to_json, b: bad_to_json)
+ {
+ "a": {
+ "foo": 1
+ },
+ "b": {
+ "foo": 1
+ }
+ }
+ JSON
+ state = JSON::State.new
+ state.generate(a: bad_to_json)
+ assert_equal 0, state.depth
end
def test_depth
+ pretty = { object_nl: "\n", array_nl: "\n", space: " ", indent: " " }
+ state = JSON.state.new(**pretty)
+ assert_equal %({\n "foo": 42\n}), JSON.generate({ foo: 42 }, pretty)
+ assert_equal %({\n "foo": 42\n}), state.generate(foo: 42)
+ state.depth = 1
+ assert_equal %({\n "foo": 42\n }), JSON.generate({ foo: 42 }, pretty.merge(depth: 1))
+ assert_equal %({\n "foo": 42\n }), state.generate(foo: 42)
+ end
+
+ def test_depth_nesting_error
ary = []; ary << ary
assert_raise(JSON::NestingError) { generate(ary) }
assert_raise(JSON::NestingError) { JSON.pretty_generate(ary) }
- s = JSON.state.new
- assert_equal 0, s.depth
+ end
+
+ def test_depth_nesting_error_to_json
+ ary = []; ary << ary
+ s = JSON.state.new(depth: 1)
assert_raise(JSON::NestingError) { ary.to_json(s) }
- assert_equal 100, s.depth
+ assert_equal 1, s.depth
+ end
+
+ def test_depth_nesting_error_Hash_to_json
+ hash = {}; hash[:a] = hash
+ s = JSON.state.new(depth: 1)
+ assert_raise(JSON::NestingError) { hash.to_json(s) }
+ assert_equal 1, s.depth
+ end
+
+ def test_depth_nesting_error_generate
+ ary = []; ary << ary
+ s = JSON.state.new(depth: 1)
+ assert_raise(JSON::NestingError) { s.generate(ary) }
+ assert_equal 1, s.depth
+ end
+
+ def test_depth_exception_calling_to_json
+ def (obj = Object.new).to_json(*)
+ raise
+ end
+ s = JSON.state.new(depth: 1).freeze
+ assert_raise(RuntimeError) { s.generate([{ hash: obj }]) }
+ assert_equal 1, s.depth
end
def test_buffer_initial_length
@@ -242,19 +441,19 @@ EOT
end
def test_gc
- if respond_to?(:assert_in_out_err) && !(RUBY_PLATFORM =~ /java/)
- assert_in_out_err(%w[-rjson -Ilib -Iext], <<-EOS, [], [])
- bignum_too_long_to_embed_as_string = 1234567890123456789012345
- expect = bignum_too_long_to_embed_as_string.to_s
- GC.stress = true
+ pid = fork do
+ bignum_too_long_to_embed_as_string = 1234567890123456789012345
+ expect = bignum_too_long_to_embed_as_string.to_s
+ GC.stress = true
- 10.times do |i|
- tmp = bignum_too_long_to_embed_as_string.to_json
- raise "'\#{expect}' is expected, but '\#{tmp}'" unless tmp == expect
- end
- EOS
+ 10.times do |i|
+ tmp = bignum_too_long_to_embed_as_string.to_json
+ raise "#{expect}' is expected, but '#{tmp}'" unless tmp == expect
+ end
end
- end if GC.respond_to?(:stress=)
+ _, status = Process.waitpid2(pid)
+ assert_predicate status, :success?
+ end if GC.respond_to?(:stress=) && Process.respond_to?(:fork)
def test_configure_using_configure_and_merge
numbered_state = {
@@ -285,63 +484,76 @@ EOT
state.configure(:indent => '1')
assert_equal '1', state.indent
state = JSON.state.new
- foo = 'foo'
+ foo = 'foo'.dup
assert_raise(TypeError) do
state.configure(foo)
end
def foo.to_h
- { :indent => '2' }
+ { indent: '2' }
end
state.configure(foo)
assert_equal '2', state.indent
end
- if defined?(JSON::Ext::Generator)
- def test_broken_bignum # [ruby-core:38867]
- pid = fork do
- x = 1 << 64
- x.class.class_eval do
- def to_s
- end
- end
- begin
- JSON::Ext::Generator::State.new.generate(x)
- exit 1
- rescue TypeError
- exit 0
- end
+ def test_broken_bignum # [Bug #5173]
+ bignum = 1 << 64
+ bignum_to_s = bignum.to_s
+
+ original_to_s = bignum.class.instance_method(:to_s)
+ bignum.class.class_eval do
+ def to_s
+ nil
+ end
+ alias_method :to_s, :to_s
+ end
+ case RUBY_ENGINE
+ when "jruby"
+ assert_equal bignum_to_s, JSON.generate(bignum)
+ when "truffleruby"
+ assert_raise(NoMethodError) do
+ JSON.generate(bignum)
+ end
+ when "ruby"
+ assert_raise(TypeError) do
+ JSON.generate(bignum)
end
- _, status = Process.waitpid2(pid)
- assert status.success?
- rescue NotImplementedError
- # forking to avoid modifying core class of a parent process and
- # introducing race conditions of tests are run in parallel
end
+ ensure
+ bignum.class.define_method(:to_s, original_to_s) if original_to_s
end
def test_hash_likeness_set_symbol
- state = JSON.state.new
- assert_equal nil, state[:foo]
- assert_equal nil.class, state[:foo].class
- assert_equal nil, state['foo']
- state[:foo] = :bar
- assert_equal :bar, state[:foo]
- assert_equal :bar, state['foo']
- state_hash = state.to_hash
- assert_kind_of Hash, state_hash
- assert_equal :bar, state_hash[:foo]
+ assert_deprecated_warning(/JSON::State/) do
+ state = JSON.state.new
+ assert_equal nil, state[:foo]
+ assert_equal nil.class, state[:foo].class
+ assert_equal nil, state['foo']
+ state[:foo] = :bar
+ assert_equal :bar, state[:foo]
+ assert_equal :bar, state['foo']
+ state_hash = state.to_hash
+ assert_kind_of Hash, state_hash
+ assert_equal :bar, state_hash[:foo]
+ end
end
def test_hash_likeness_set_string
+ assert_deprecated_warning(/JSON::State/) do
+ state = JSON.state.new
+ assert_equal nil, state[:foo]
+ assert_equal nil, state['foo']
+ state['foo'] = :bar
+ assert_equal :bar, state[:foo]
+ assert_equal :bar, state['foo']
+ state_hash = state.to_hash
+ assert_kind_of Hash, state_hash
+ assert_equal :bar, state_hash[:foo]
+ end
+ end
+
+ def test_json_state_to_h_roundtrip
state = JSON.state.new
- assert_equal nil, state[:foo]
- assert_equal nil, state['foo']
- state['foo'] = :bar
- assert_equal :bar, state[:foo]
- assert_equal :bar, state['foo']
- state_hash = state.to_hash
- assert_kind_of Hash, state_hash
- assert_equal :bar, state_hash[:foo]
+ assert_equal state.to_h, JSON.state.new(state.to_h).to_h
end
def test_json_generate
@@ -350,10 +562,30 @@ EOT
end
end
+ def test_json_generate_error_detailed_message
+ error = assert_raise JSON::GeneratorError do
+ generate(["\xea"])
+ end
+
+ assert_not_nil(error.detailed_message)
+ end
+
def test_json_generate_unsupported_types
assert_raise JSON::GeneratorError do
generate(Object.new, strict: true)
end
+
+ assert_raise JSON::GeneratorError do
+ generate([Object.new], strict: true)
+ end
+
+ assert_raise JSON::GeneratorError do
+ generate({ "key" => Object.new }, strict: true)
+ end
+
+ assert_raise JSON::GeneratorError do
+ generate({ Object.new => "value" }, strict: true)
+ end
end
def test_nesting
@@ -369,6 +601,8 @@ EOT
assert_equal too_deep, ok
ok = generate too_deep_ary, :max_nesting => 0
assert_equal too_deep, ok
+
+ assert_raise(TypeError) { generate too_deep_ary, max_nesting: "garbage" }
end
def test_backslash
@@ -376,18 +610,34 @@ EOT
json = '["\\\\.(?i:gif|jpe?g|png)$"]'
assert_equal json, generate(data)
#
- data = [ '\\"' ]
- json = '["\\\\\""]'
+ data = [ '\\.(?i:gif|jpe?g|png)$\\.(?i:gif|jpe?g|png)$\\.(?i:gif|jpe?g|png)$\\.(?i:gif|jpe?g|png)$\\.(?i:gif|jpe?g|png)$\\.(?i:gif|jpe?g|png)$\\.(?i:gif|jpe?g|png)$\\.(?i:gif|jpe?g|png)$\\.(?i:gif|jpe?g|png)$\\.(?i:gif|jpe?g|png)$\\.(?i:gif|jpe?g|png)$' ]
+ json = '["\\\\.(?i:gif|jpe?g|png)$\\\\.(?i:gif|jpe?g|png)$\\\\.(?i:gif|jpe?g|png)$\\\\.(?i:gif|jpe?g|png)$\\\\.(?i:gif|jpe?g|png)$\\\\.(?i:gif|jpe?g|png)$\\\\.(?i:gif|jpe?g|png)$\\\\.(?i:gif|jpe?g|png)$\\\\.(?i:gif|jpe?g|png)$\\\\.(?i:gif|jpe?g|png)$\\\\.(?i:gif|jpe?g|png)$"]'
+ assert_equal json, generate(data)
+ #
+ data = [ '\\"\\"\\"\\"\\"\\"\\"\\"\\"\\"\\"' ]
+ json = '["\\\\\"\\\\\"\\\\\"\\\\\"\\\\\"\\\\\"\\\\\"\\\\\"\\\\\"\\\\\"\\\\\""]'
assert_equal json, generate(data)
#
data = [ '/' ]
json = '["/"]'
assert_equal json, generate(data)
#
+ data = [ '////////////////////////////////////////////////////////////////////////////////////' ]
+ json = '["////////////////////////////////////////////////////////////////////////////////////"]'
+ assert_equal json, generate(data)
+ #
data = [ '/' ]
json = '["\/"]'
assert_equal json, generate(data, :script_safe => true)
#
+ data = [ '///////////' ]
+ json = '["\/\/\/\/\/\/\/\/\/\/\/"]'
+ assert_equal json, generate(data, :script_safe => true)
+ #
+ data = [ '///////////////////////////////////////////////////////' ]
+ json = '["\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/"]'
+ assert_equal json, generate(data, :script_safe => true)
+ #
data = [ "\u2028\u2029" ]
json = '["\u2028\u2029"]'
assert_equal json, generate(data, :script_safe => true)
@@ -404,9 +654,111 @@ EOT
json = '["\""]'
assert_equal json, generate(data)
#
+ data = ['"""""""""""""""""""""""""']
+ json = '["\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\""]'
+ assert_equal json, generate(data)
+ #
+ data = '"""""'
+ json = '"\"\"\"\"\""'
+ assert_equal json, generate(data)
+ #
+ data = "abc\n"
+ json = '"abc\\n"'
+ assert_equal json, generate(data)
+ #
+ data = "\nabc"
+ json = '"\\nabc"'
+ assert_equal json, generate(data)
+ #
+ data = "\n"
+ json = '"\\n"'
+ assert_equal json, generate(data)
+ #
+ (0..16).each do |i|
+ data = ('a' * i) + "\n"
+ json = '"' + ('a' * i) + '\\n"'
+ assert_equal json, generate(data)
+ end
+ #
+ (0..16).each do |i|
+ data = "\n" + ('a' * i)
+ json = '"' + '\\n' + ('a' * i) + '"'
+ assert_equal json, generate(data)
+ end
+ #
data = ["'"]
json = '["\\\'"]'
assert_equal '["\'"]', generate(data)
+ #
+ data = ["倩", "瀨"]
+ json = '["倩","瀨"]'
+ assert_equal json, generate(data, script_safe: true)
+ #
+ data = '["This is a "test" of the emergency broadcast system."]'
+ json = "\"[\\\"This is a \\\"test\\\" of the emergency broadcast system.\\\"]\""
+ assert_equal json, generate(data)
+ #
+ data = '\tThis is a test of the emergency broadcast system.'
+ json = "\"\\\\tThis is a test of the emergency broadcast system.\""
+ assert_equal json, generate(data)
+ #
+ data = 'This\tis a test of the emergency broadcast system.'
+ json = "\"This\\\\tis a test of the emergency broadcast system.\""
+ assert_equal json, generate(data)
+ #
+ data = 'This is\ta test of the emergency broadcast system.'
+ json = "\"This is\\\\ta test of the emergency broadcast system.\""
+ assert_equal json, generate(data)
+ #
+ data = 'This is a test of the emergency broadcast\tsystem.'
+ json = "\"This is a test of the emergency broadcast\\\\tsystem.\""
+ assert_equal json, generate(data)
+ #
+ data = 'This is a test of the emergency broadcast\tsystem.\n'
+ json = "\"This is a test of the emergency broadcast\\\\tsystem.\\\\n\""
+ assert_equal json, generate(data)
+ data = '"' * 15
+ json = "\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\""
+ assert_equal json, generate(data)
+ data = "\"\"\"\"\"\"\"\"\"\"\"\"\"\"a"
+ json = "\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"a\""
+ assert_equal json, generate(data)
+ data = "\u0001\u0001\u0001\u0001"
+ json = "\"\\u0001\\u0001\\u0001\\u0001\""
+ assert_equal json, generate(data)
+ data = "\u0001a\u0001a\u0001a\u0001a"
+ json = "\"\\u0001a\\u0001a\\u0001a\\u0001a\""
+ assert_equal json, generate(data)
+ data = "\u0001aa\u0001aa"
+ json = "\"\\u0001aa\\u0001aa\""
+ assert_equal json, generate(data)
+ data = "\u0001aa\u0001aa\u0001aa"
+ json = "\"\\u0001aa\\u0001aa\\u0001aa\""
+ assert_equal json, generate(data)
+ data = "\u0001aa\u0001aa\u0001aa\u0001aa\u0001aa\u0001aa"
+ json = "\"\\u0001aa\\u0001aa\\u0001aa\\u0001aa\\u0001aa\\u0001aa\""
+ assert_equal json, generate(data)
+ data = "\u0001a\u0002\u0001a\u0002\u0001a\u0002\u0001a\u0002\u0001a\u0002\u0001a\u0002\u0001a\u0002\u0001a\u0002"
+ json = "\"\\u0001a\\u0002\\u0001a\\u0002\\u0001a\\u0002\\u0001a\\u0002\\u0001a\\u0002\\u0001a\\u0002\\u0001a\\u0002\\u0001a\\u0002\""
+ assert_equal json, generate(data)
+ data = "ab\u0002c"
+ json = "\"ab\\u0002c\""
+ assert_equal json, generate(data)
+ data = "ab\u0002cab\u0002cab\u0002cab\u0002c"
+ json = "\"ab\\u0002cab\\u0002cab\\u0002cab\\u0002c\""
+ assert_equal json, generate(data)
+ data = "ab\u0002cab\u0002cab\u0002cab\u0002cab\u0002cab\u0002c"
+ json = "\"ab\\u0002cab\\u0002cab\\u0002cab\\u0002cab\\u0002cab\\u0002c\""
+ assert_equal json, generate(data)
+ data = "\n\t\f\b\n\t\f\b\n\t\f\b\n\t\f"
+ json = "\"\\n\\t\\f\\b\\n\\t\\f\\b\\n\\t\\f\\b\\n\\t\\f\""
+ assert_equal json, generate(data)
+ data = "\n\t\f\b\n\t\f\b\n\t\f\b\n\t\f\b"
+ json = "\"\\n\\t\\f\\b\\n\\t\\f\\b\\n\\t\\f\\b\\n\\t\\f\\b\""
+ assert_equal json, generate(data)
+ data = "a\n\t\f\b\n\t\f\b\n\t\f\b\n\t"
+ json = "\"a\\n\\t\\f\\b\\n\\t\\f\\b\\n\\t\\f\\b\\n\\t\""
+ assert_equal json, generate(data)
end
def test_string_subclass
@@ -419,34 +771,327 @@ EOT
end
end
+ def test_invalid_encoding_string
+ error = assert_raise(JSON::GeneratorError) do
+ "\x82\xAC\xEF".to_json
+ end
+ assert_includes error.message, "source sequence is illegal/malformed utf-8"
+
+ error = assert_raise(JSON::GeneratorError) do
+ JSON.dump("\x82\xAC\xEF")
+ end
+ assert_includes error.message, "source sequence is illegal/malformed utf-8"
+
+ assert_raise(JSON::GeneratorError) do
+ JSON.dump("\x82\xAC\xEF".b)
+ end
+
+ assert_raise(JSON::GeneratorError) do
+ "\x82\xAC\xEF".b.to_json
+ end
+
+ assert_raise(JSON::GeneratorError) do
+ ["\x82\xAC\xEF".b].to_json
+ end
+
+ badly_encoded = "\x82\xAC\xEF".b
+ exception = assert_raise(JSON::GeneratorError) do
+ { foo: badly_encoded }.to_json
+ end
+
+ assert_kind_of EncodingError, exception.cause
+ assert_same badly_encoded, exception.invalid_object
+ end
+
+ class MyCustomString < String
+ def to_json(_state = nil)
+ '"my_custom_key"'
+ end
+
+ def to_s
+ self
+ end
+ end
+
+ def test_string_subclass_as_keys
+ # Ref: https://github.com/ruby/json/issues/667
+ # if key.to_s doesn't return a bare string, we call `to_json` on it.
+ key = MyCustomString.new("won't be used")
+ assert_equal '{"my_custom_key":1}', JSON.generate(key => 1)
+ end
+
+ class FakeString
+ def to_json(_state = nil)
+ raise "Shouldn't be called"
+ end
+
+ def to_s
+ self
+ end
+ end
+
+ def test_custom_object_as_keys
+ key = FakeString.new
+ error = assert_raise(TypeError) do
+ JSON.generate(key => 1)
+ end
+ assert_match "FakeString", error.message
+ end
+
+ def test_to_json_called_with_state_object
+ object = Object.new
+ called = false
+ argument = nil
+ object.singleton_class.define_method(:to_json) do |state|
+ called = true
+ argument = state
+ "<hello>"
+ end
+
+ assert_equal "<hello>", JSON.dump(object)
+ assert called, "#to_json wasn't called"
+ assert_instance_of JSON::State, argument
+ end
+
+ module CustomToJSON
+ def to_json(*)
+ %{"#{self.class.name}#to_json"}
+ end
+ end
+
+ module CustomToS
+ def to_s
+ "#{self.class.name}#to_s"
+ end
+ end
+
+ class ArrayWithToJSON < Array
+ include CustomToJSON
+ end
+
+ def test_array_subclass_with_to_json
+ assert_equal '["JSONGeneratorTest::ArrayWithToJSON#to_json"]', JSON.generate([ArrayWithToJSON.new])
+ assert_equal '{"[]":1}', JSON.generate(ArrayWithToJSON.new => 1)
+ end
+
+ class ArrayWithToS < Array
+ include CustomToS
+ end
+
+ def test_array_subclass_with_to_s
+ assert_equal '[[]]', JSON.generate([ArrayWithToS.new])
+ assert_equal '{"JSONGeneratorTest::ArrayWithToS#to_s":1}', JSON.generate(ArrayWithToS.new => 1)
+ end
+
+ class HashWithToJSON < Hash
+ include CustomToJSON
+ end
+
+ def test_hash_subclass_with_to_json
+ assert_equal '["JSONGeneratorTest::HashWithToJSON#to_json"]', JSON.generate([HashWithToJSON.new])
+ assert_equal '{"{}":1}', JSON.generate(HashWithToJSON.new => 1)
+ end
+
+ class HashWithToS < Hash
+ include CustomToS
+ end
+
+ def test_hash_subclass_with_to_s
+ assert_equal '[{}]', JSON.generate([HashWithToS.new])
+ assert_equal '{"JSONGeneratorTest::HashWithToS#to_s":1}', JSON.generate(HashWithToS.new => 1)
+ end
+
+ class StringWithToJSON < String
+ include CustomToJSON
+ end
+
+ def test_string_subclass_with_to_json
+ assert_equal '["JSONGeneratorTest::StringWithToJSON#to_json"]', JSON.generate([StringWithToJSON.new])
+ assert_equal '{"":1}', JSON.generate(StringWithToJSON.new => 1)
+ end
+
+ class StringWithToS < String
+ include CustomToS
+ end
+
+ def test_string_subclass_with_to_s
+ assert_equal '[""]', JSON.generate([StringWithToS.new])
+ assert_equal '{"JSONGeneratorTest::StringWithToS#to_s":1}', JSON.generate(StringWithToS.new => 1)
+ end
+
+ def test_string_subclass_with_broken_to_s
+ klass = Class.new(String) do
+ def to_s
+ false
+ end
+ end
+ s = klass.new("test")
+ assert_equal '["test"]', JSON.generate([s])
+
+ omit("Can't figure out how to match behavior in java code") if RUBY_PLATFORM == "java"
+
+ assert_raise TypeError do
+ JSON.generate(s => 1)
+ end
+ end
+
if defined?(JSON::Ext::Generator) and RUBY_PLATFORM != "java"
- def test_string_ext_included_calls_super
- included = false
-
- Module.send(:alias_method, :included_orig, :included)
- Module.send(:remove_method, :included)
- Module.send(:define_method, :included) do |base|
- included_orig(base)
- included = true
+ def test_valid_utf8_in_different_encoding
+ utf8_string = "€™"
+ wrong_encoding_string = utf8_string.b
+ # This behavior is historical. Not necessary desirable. We should deprecated it.
+ # The pure and java version of the gem already don't behave this way.
+ assert_warning(/UTF-8 string passed as BINARY, this will raise an encoding error in json 3.0/) do
+ assert_equal utf8_string.to_json, wrong_encoding_string.to_json
end
- Class.new(String) do
- include JSON::Ext::Generator::GeneratorMethods::String
+ assert_warning(/UTF-8 string passed as BINARY, this will raise an encoding error in json 3.0/) do
+ assert_equal JSON.dump(utf8_string), JSON.dump(wrong_encoding_string)
end
+ end
+ end
+
+ def test_nonutf8_encoding
+ assert_equal("\"5\u{b0}\"", "5\xb0".dup.force_encoding(Encoding::ISO_8859_1).to_json)
+ end
- assert included
- ensure
- if Module.private_method_defined?(:included_orig)
- Module.send(:remove_method, :included) if Module.method_defined?(:included)
- Module.send(:alias_method, :included, :included_orig)
- Module.send(:remove_method, :included_orig)
+ def test_utf8_multibyte
+ assert_equal('["foßbar"]', JSON.generate(["foßbar"]))
+ assert_equal('"n€ßt€ð2"', JSON.generate("n€ßt€ð2"))
+ assert_equal('"\"\u0000\u001f"', JSON.generate("\"\u0000\u001f"))
+ end
+
+ def test_fragment
+ fragment = JSON::Fragment.new(" 42")
+ assert_equal '{"number": 42}', JSON.generate({ number: fragment })
+ assert_equal '{"number": 42}', JSON.generate({ number: fragment }, strict: true)
+ end
+
+ def test_json_generate_as_json_convert_to_proc
+ object = Object.new
+ assert_equal object.object_id.to_json, JSON.generate(object, strict: true, as_json: -> (o, is_key) { o.object_id })
+ end
+
+ def test_as_json_nan_does_not_call_to_json
+ def (obj = Object.new).to_json(*)
+ "null"
+ end
+ assert_raise(JSON::GeneratorError) do
+ JSON.generate(Float::NAN, strict: true, as_json: proc { obj })
+ end
+ end
+
+ def assert_float_roundtrip(expected, actual)
+ assert_equal(expected, JSON.generate(actual))
+ assert_equal(actual, JSON.parse(JSON.generate(actual)), "JSON: #{JSON.generate(actual)}")
+ end
+
+ def test_json_generate_float
+ assert_float_roundtrip "-1.0", -1.0
+ assert_float_roundtrip "1.0", 1.0
+ assert_float_roundtrip "0.0", 0.0
+ assert_float_roundtrip "12.2", 12.2
+ assert_float_roundtrip "2.34375", 7.5 / 3.2
+ assert_float_roundtrip "12.0", 12.0
+ assert_float_roundtrip "100.0", 100.0
+ assert_float_roundtrip "1000.0", 1000.0
+
+ if RUBY_ENGINE == "jruby"
+ assert_float_roundtrip "1.7468619377842371E9", 1746861937.7842371
+ else
+ assert_float_roundtrip "1746861937.7842371", 1746861937.7842371
+ end
+
+ if RUBY_ENGINE == "ruby"
+ assert_float_roundtrip "100000000000000.0", 100000000000000.0
+ assert_float_roundtrip "1e+15", 1e+15
+ assert_float_roundtrip "-100000000000000.0", -100000000000000.0
+ assert_float_roundtrip "-1e+15", -1e+15
+ assert_float_roundtrip "1111111111111111.1", 1111111111111111.1
+ assert_float_roundtrip "1.1111111111111112e+16", 11111111111111111.1
+ assert_float_roundtrip "-1111111111111111.1", -1111111111111111.1
+ assert_float_roundtrip "-1.1111111111111112e+16", -11111111111111111.1
+
+ assert_float_roundtrip "-0.000000022471348024634545", -2.2471348024634545e-08
+ assert_float_roundtrip "-0.0000000022471348024634545", -2.2471348024634545e-09
+ assert_float_roundtrip "-2.2471348024634546e-10", -2.2471348024634545e-10
+ end
+ end
+
+ def test_numbers_of_various_sizes
+ numbers = [
+ 0, 1, -1, 9, -9, 13, -13, 91, -91, 513, -513, 7513, -7513,
+ 17591, -17591, -4611686018427387904, 4611686018427387903,
+ 2**62, 2**63, 2**64, -(2**62), -(2**63), -(2**64)
+ ]
+
+ numbers.each do |number|
+ assert_equal "[#{number}]", JSON.generate([number])
+ end
+ end
+
+ def test_generate_duplicate_keys_allowed
+ hash = { foo: 1, "foo" => 2 }
+ assert_equal %({"foo":1,"foo":2}), JSON.generate(hash, allow_duplicate_key: true)
+ end
+
+ def test_generate_duplicate_keys_deprecated
+ hash = { foo: 1, "foo" => 2 }
+ assert_deprecated_warning(/allow_duplicate_key/) do
+ assert_equal %({"foo":1,"foo":2}), JSON.generate(hash)
+ end
+ end
+
+ def test_generate_duplicate_keys_disallowed
+ hash = { foo: 1, "foo" => 2 }
+ error = assert_raise JSON::GeneratorError do
+ JSON.generate(hash, allow_duplicate_key: false)
+ end
+ assert_equal %(detected duplicate key "foo" in #{hash.inspect}), error.message
+ end
+
+ def test_frozen
+ state = JSON::State.new.freeze
+ assert_raise(FrozenError) do
+ state.configure(max_nesting: 1)
+ end
+ setters = state.methods.grep(/\w=$/)
+ assert_not_empty setters
+ setters.each do |setter|
+ assert_raise(FrozenError) do
+ state.send(setter, 1)
end
end
end
- if defined?(Encoding)
- def test_nonutf8_encoding
- assert_equal("\"5\u{b0}\"", "5\xb0".force_encoding("iso-8859-1").to_json)
+ # The case when the State is frozen is tested in JSONCoderTest#test_nesting_recovery
+ def test_nesting_recovery
+ state = JSON::State.new
+ ary = []
+ ary << ary
+ assert_raise(JSON::NestingError) { state.generate(ary) }
+ assert_equal 0, state.depth
+ assert_equal '{"a":1}', state.generate({ a: 1 })
+ end
+
+ def test_negative_depth_raises
+ assert_raise(ArgumentError) do
+ JSON.generate({"a" => 1}, depth: -1)
+ end
+ assert_raise(ArgumentError) do
+ JSON.state.new(depth: -1)
end
end
+
+ def test_large_depth_raises
+ assert_raise(RangeError, ArgumentError) do
+ JSON.generate([[1]],
+ indent: " " * 5,
+ array_nl: "\n",
+ depth: 3_689_348_814_741_910_324,
+ max_nesting: 0
+ )
+ end
+ end
+
end