encoding_test.rb 16.4 KB
Newer Older
1
require 'securerandom'
2
require 'abstract_unit'
3
require 'active_support/core_ext/string/inflections'
J
Jeremy Kemper 已提交
4
require 'active_support/json'
5
require 'active_support/time'
6
require 'time_zone_test_helpers'
7

8
class TestJSONEncoding < ActiveSupport::TestCase
9 10
  include TimeZoneTestHelpers

11 12 13 14
  class Foo
    def initialize(a, b)
      @a, @b = a, b
    end
15 16
  end

17 18
  class Hashlike
    def to_hash
19
      { :foo => "hello", :bar => "world" }
20 21 22
    end
  end

23
  class Custom
24 25 26 27
    def initialize(serialized)
      @serialized = serialized
    end

G
Godfrey Chan 已提交
28
    def as_json(options = nil)
29
      @serialized
30 31 32
    end
  end

33 34 35 36 37 38 39 40 41
  class CustomWithOptions
    attr_accessor :foo, :bar

    def as_json(options={})
      options[:only] = %w(foo bar)
      super(options)
    end
  end

42 43 44 45 46 47
  class OptionsTest
    def as_json(options = :default)
      options
    end
  end

48 49 50 51 52 53 54 55 56 57 58 59 60
  class HashWithAsJson < Hash
    attr_accessor :as_json_called

    def initialize(*)
      super
    end

    def as_json(options={})
      @as_json_called = true
      super
    end
  end

61 62 63 64
  TrueTests     = [[ true,  %(true)  ]]
  FalseTests    = [[ false, %(false) ]]
  NilTests      = [[ nil,   %(null)  ]]
  NumericTests  = [[ 1,     %(1)     ],
65
                   [ 2.5,   %(2.5)   ],
66 67 68
                   [ 0.0/0.0,   %(null) ],
                   [ 1.0/0.0,   %(null) ],
                   [ -1.0/0.0,  %(null) ],
69
                   [ BigDecimal('0.0')/BigDecimal('0.0'),  %(null) ],
70
                   [ BigDecimal('2.5'), %("#{BigDecimal('2.5')}") ]]
71

72
  StringTests   = [[ 'this is the <string>',     %("this is the \\u003cstring\\u003e")],
73
                   [ 'a "string" with quotes & an ampersand', %("a \\"string\\" with quotes \\u0026 an ampersand") ],
74
                   [ 'http://test.host/posts/1', %("http://test.host/posts/1")],
75
                   [ "Control characters: \x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\u2028\u2029",
76
                     %("Control characters: \\u0000\\u0001\\u0002\\u0003\\u0004\\u0005\\u0006\\u0007\\b\\t\\n\\u000b\\f\\r\\u000e\\u000f\\u0010\\u0011\\u0012\\u0013\\u0014\\u0015\\u0016\\u0017\\u0018\\u0019\\u001a\\u001b\\u001c\\u001d\\u001e\\u001f\\u2028\\u2029") ]]
77

78 79
  ArrayTests    = [[ ['a', 'b', 'c'],          %([\"a\",\"b\",\"c\"])          ],
                   [ [1, 'a', :b, nil, false], %([1,\"a\",\"b\",null,false]) ]]
80

81 82 83 84
  RangeTests    = [[ 1..2,     %("1..2")],
                   [ 1...2,    %("1...2")],
                   [ 1.5..2.5, %("1.5..2.5")]]

85 86 87 88
  SymbolTests   = [[ :a,     %("a")    ],
                   [ :this,  %("this") ],
                   [ :"a b", %("a b")  ]]

89
  ObjectTests   = [[ Foo.new(1, 2), %({\"a\":1,\"b\":2}) ]]
90
  HashlikeTests = [[ Hashlike.new, %({\"bar\":\"world\",\"foo\":\"hello\"}) ]]
91 92 93 94 95 96 97
  CustomTests   = [[ Custom.new("custom"), '"custom"' ],
                   [ Custom.new(nil), 'null' ],
                   [ Custom.new(:a), '"a"' ],
                   [ Custom.new([ :foo, "bar" ]), '["foo","bar"]' ],
                   [ Custom.new({ :foo => "hello", :bar => "world" }), '{"bar":"world","foo":"hello"}' ],
                   [ Custom.new(Hashlike.new), '{"bar":"world","foo":"hello"}' ],
                   [ Custom.new(Custom.new(Custom.new(:a))), '"a"' ]]
98

99
  RegexpTests   = [[ /^a/, '"(?-mix:^a)"' ], [/^\w{1,2}[a-z]+/ix, '"(?ix-m:^\\\\w{1,2}[a-z]+)"']]
100

101 102 103
  DateTests     = [[ Date.new(2005,2,1), %("2005/02/01") ]]
  TimeTests     = [[ Time.utc(2005,2,1,15,15,10), %("2005/02/01 15:15:10 +0000") ]]
  DateTimeTests = [[ DateTime.civil(2005,2,1,15,15,10), %("2005/02/01 15:15:10 +0000") ]]
104

105
  StandardDateTests     = [[ Date.new(2005,2,1), %("2005-02-01") ]]
106 107
  StandardTimeTests     = [[ Time.utc(2005,2,1,15,15,10), %("2005-02-01T15:15:10.000Z") ]]
  StandardDateTimeTests = [[ DateTime.civil(2005,2,1,15,15,10), %("2005-02-01T15:15:10.000+00:00") ]]
108
  StandardStringTests   = [[ 'this is the <string>', %("this is the <string>")]]
109

Y
Yehuda Katz 已提交
110 111 112 113 114
  def sorted_json(json)
    return json unless json =~ /^\{.*\}$/
    '{' + json[1..-2].split(',').sort.join(',') + '}'
  end

115
  constants.grep(/Tests$/).each do |class_tests|
116 117
    define_method("test_#{class_tests[0..-6].underscore}") do
      begin
118 119
        prev = ActiveSupport.use_standard_json_time_format

120
        ActiveSupport.escape_html_entities_in_json  = class_tests !~ /^Standard/
121 122
        ActiveSupport.use_standard_json_time_format = class_tests =~ /^Standard/
        self.class.const_get(class_tests).each do |pair|
Y
Yehuda Katz 已提交
123
          assert_equal pair.last, sorted_json(ActiveSupport::JSON.encode(pair.first))
124 125
        end
      ensure
126
        ActiveSupport.escape_html_entities_in_json  = false
127
        ActiveSupport.use_standard_json_time_format = prev
128 129 130
      end
    end
  end
131

132
  def test_process_status
133 134
    rubinius_skip "https://github.com/rubinius/rubinius/issues/3334"

135 136 137 138 139 140
    # There doesn't seem to be a good way to get a handle on a Process::Status object without actually
    # creating a child process, hence this to populate $?
    system("not_a_real_program_#{SecureRandom.hex}")
    assert_equal %({"exitstatus":#{$?.exitstatus},"pid":#{$?.pid}}), ActiveSupport::JSON.encode($?)
  end

T
Thomas Fuchs 已提交
141
  def test_hash_encoding
142 143 144 145
    assert_equal %({\"a\":\"b\"}), ActiveSupport::JSON.encode(:a => :b)
    assert_equal %({\"a\":1}), ActiveSupport::JSON.encode('a' => 1)
    assert_equal %({\"a\":[1,2]}), ActiveSupport::JSON.encode('a' => [1,2])
    assert_equal %({"1":2}), ActiveSupport::JSON.encode(1 => 2)
146

Y
Yehuda Katz 已提交
147
    assert_equal %({\"a\":\"b\",\"c\":\"d\"}), sorted_json(ActiveSupport::JSON.encode(:a => :b, :c => :d))
T
Thomas Fuchs 已提交
148
  end
149

150 151
  def test_utf8_string_encoded_properly
    result = ActiveSupport::JSON.encode('€2.99')
152
    assert_equal '"€2.99"', result
153
    assert_equal(Encoding::UTF_8, result.encoding)
154 155

    result = ActiveSupport::JSON.encode('✎☺')
156
    assert_equal '"✎☺"', result
157
    assert_equal(Encoding::UTF_8, result.encoding)
158
  end
159

160 161 162
  def test_non_utf8_string_transcodes
    s = '二'.encode('Shift_JIS')
    result = ActiveSupport::JSON.encode(s)
163
    assert_equal '"二"', result
164
    assert_equal Encoding::UTF_8, result.encoding
165 166
  end

167 168 169 170 171 172 173 174 175 176 177 178 179
  def test_wide_utf8_chars
    w = '𠜎'
    result = ActiveSupport::JSON.encode(w)
    assert_equal '"𠜎"', result
  end

  def test_wide_utf8_roundtrip
    hash = { string: "𐒑" }
    json = ActiveSupport::JSON.encode(hash)
    decoded_hash = ActiveSupport::JSON.decode(json)
    assert_equal "𐒑", decoded_hash['string']
  end

180
  def test_hash_key_identifiers_are_always_quoted
181
    values = {0 => 0, 1 => 1, :_ => :_, "$" => "$", "a" => "a", :A => :A, :A0 => :A0, "A0B" => "A0B"}
182
    assert_equal %w( "$" "A" "A0" "A0B" "_" "a" "0" "1" ).sort, object_keys(ActiveSupport::JSON.encode(values))
183
  end
184

185
  def test_hash_should_allow_key_filtering_with_only
186
    assert_equal %({"a":1}), ActiveSupport::JSON.encode({'a' => 1, :b => 2, :c => 3}, :only => 'a')
187 188 189
  end

  def test_hash_should_allow_key_filtering_with_except
190
    assert_equal %({"b":2}), ActiveSupport::JSON.encode({'foo' => 'bar', :b => 2, :c => 3}, :except => ['foo', :c])
191
  end
192

193
  def test_time_to_json_includes_local_offset
194 195 196 197
    with_standard_json_time_format(true) do
      with_env_tz 'US/Eastern' do
        assert_equal %("2005-02-01T15:15:10.000-05:00"), ActiveSupport::JSON.encode(Time.local(2005,2,1,15,15,10))
      end
198 199
    end
  end
200

201
  def test_hash_with_time_to_json
202 203 204
    with_standard_json_time_format(false) do
      assert_equal '{"time":"2009/01/01 00:00:00 +0000"}', { :time => Time.utc(2009) }.to_json
    end
205 206
  end

D
Dan Barry 已提交
207 208 209 210
  def test_nested_hash_with_float
    assert_nothing_raised do
      hash = {
        "CHI" => {
211
          :display_name => "chicago",
D
Dan Barry 已提交
212 213 214
          :latitude => 123.234
        }
      }
S
Santiago Pastorino 已提交
215
      ActiveSupport::JSON.encode(hash)
D
Dan Barry 已提交
216 217 218
    end
  end

219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243
  def test_hash_like_with_options
    h = Hashlike.new
    json = h.to_json :only => [:foo]

    assert_equal({"foo"=>"hello"}, JSON.parse(json))
  end

  def test_object_to_json_with_options
    obj = Object.new
    obj.instance_variable_set :@foo, "hello"
    obj.instance_variable_set :@bar, "world"
    json = obj.to_json :only => ["foo"]

    assert_equal({"foo"=>"hello"}, JSON.parse(json))
  end

  def test_struct_to_json_with_options
    struct = Struct.new(:foo, :bar).new
    struct.foo = "hello"
    struct.bar = "world"
    json = struct.to_json :only => [:foo]

    assert_equal({"foo"=>"hello"}, JSON.parse(json))
  end

244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293
  def test_hash_should_pass_encoding_options_to_children_in_as_json
    person = {
      :name => 'John',
      :address => {
        :city => 'London',
        :country => 'UK'
      }
    }
    json = person.as_json :only => [:address, :city]

    assert_equal({ 'address' => { 'city' => 'London' }}, json)
  end

  def test_hash_should_pass_encoding_options_to_children_in_to_json
    person = {
      :name => 'John',
      :address => {
        :city => 'London',
        :country => 'UK'
      }
    }
    json = person.to_json :only => [:address, :city]

    assert_equal(%({"address":{"city":"London"}}), json)
  end

  def test_array_should_pass_encoding_options_to_children_in_as_json
    people = [
      { :name => 'John', :address => { :city => 'London', :country => 'UK' }},
      { :name => 'Jean', :address => { :city => 'Paris' , :country => 'France' }}
    ]
    json = people.as_json :only => [:address, :city]
    expected = [
      { 'address' => { 'city' => 'London' }},
      { 'address' => { 'city' => 'Paris' }}
    ]

    assert_equal(expected, json)
  end

  def test_array_should_pass_encoding_options_to_children_in_to_json
    people = [
      { :name => 'John', :address => { :city => 'London', :country => 'UK' }},
      { :name => 'Jean', :address => { :city => 'Paris' , :country => 'France' }}
    ]
    json = people.to_json :only => [:address, :city]

    assert_equal(%([{"address":{"city":"London"}},{"address":{"city":"Paris"}}]), json)
  end

294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314
  People = Class.new(BasicObject) do
    include Enumerable
    def initialize()
      @people = [
        { :name => 'John', :address => { :city => 'London', :country => 'UK' }},
        { :name => 'Jean', :address => { :city => 'Paris' , :country => 'France' }}
      ]
    end
    def each(*, &blk)
      @people.each do |p|
        yield p if blk
        p
      end.each
    end
  end

  def test_enumerable_should_generate_json_with_as_json
    json = People.new.as_json :only => [:address, :city]
    expected = [
      { 'address' => { 'city' => 'London' }},
      { 'address' => { 'city' => 'Paris' }}
315
    ]
316 317 318 319 320 321 322 323 324 325 326

    assert_equal(expected, json)
  end

  def test_enumerable_should_generate_json_with_to_json
    json = People.new.to_json :only => [:address, :city]
    assert_equal(%([{"address":{"city":"London"}},{"address":{"city":"Paris"}}]), json)
  end

  def test_enumerable_should_pass_encoding_options_to_children_in_as_json
    json = People.new.each.as_json :only => [:address, :city]
327 328 329 330 331 332 333 334 335
    expected = [
      { 'address' => { 'city' => 'London' }},
      { 'address' => { 'city' => 'Paris' }}
    ]

    assert_equal(expected, json)
  end

  def test_enumerable_should_pass_encoding_options_to_children_in_to_json
336
    json = People.new.each.to_json :only => [:address, :city]
337 338 339 340

    assert_equal(%([{"address":{"city":"London"}},{"address":{"city":"Paris"}}]), json)
  end

G
Godfrey Chan 已提交
341
  def test_hash_to_json_should_not_keep_options_around
342 343 344 345 346
    f = CustomWithOptions.new
    f.foo = "hello"
    f.bar = "world"

    hash = {"foo" => f, "other_hash" => {"foo" => "other_foo", "test" => "other_test"}}
347
    assert_equal({"foo"=>{"foo"=>"hello","bar"=>"world"},
348
                  "other_hash" => {"foo"=>"other_foo","test"=>"other_test"}}, ActiveSupport::JSON.decode(hash.to_json))
349 350
  end

G
Godfrey Chan 已提交
351 352 353 354 355 356 357 358 359 360
  def test_array_to_json_should_not_keep_options_around
    f = CustomWithOptions.new
    f.foo = "hello"
    f.bar = "world"

    array = [f, {"foo" => "other_foo", "test" => "other_test"}]
    assert_equal([{"foo"=>"hello","bar"=>"world"},
                  {"foo"=>"other_foo","test"=>"other_test"}], ActiveSupport::JSON.decode(array.to_json))
  end

361 362 363 364 365 366 367 368 369 370
  def test_hash_as_json_without_options
    json = { foo: OptionsTest.new }.as_json
    assert_equal({"foo" => :default}, json)
  end

  def test_array_as_json_without_options
    json = [ OptionsTest.new ].as_json
    assert_equal([:default], json)
  end

A
Alexey Nayden 已提交
371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389
  def test_struct_encoding
    Struct.new('UserNameAndEmail', :name, :email)
    Struct.new('UserNameAndDate', :name, :date)
    Struct.new('Custom', :name, :sub)
    user_email = Struct::UserNameAndEmail.new 'David', 'sample@example.com'
    user_birthday = Struct::UserNameAndDate.new 'David', Date.new(2010, 01, 01)
    custom = Struct::Custom.new 'David', user_birthday


    json_strings = ""
    json_string_and_date = ""
    json_custom = ""

    assert_nothing_raised do
      json_strings = user_email.to_json
      json_string_and_date = user_birthday.to_json
      json_custom = custom.to_json
    end

390 391 392
    assert_equal({"name" => "David",
                  "sub" => {
                    "name" => "David",
393
                    "date" => "2010-01-01" }}, ActiveSupport::JSON.decode(json_custom))
394 395

    assert_equal({"name" => "David", "email" => "sample@example.com"},
396
                 ActiveSupport::JSON.decode(json_strings))
397

398
    assert_equal({"name" => "David", "date" => "2010-01-01"},
399
                 ActiveSupport::JSON.decode(json_string_and_date))
A
Alexey Nayden 已提交
400
  end
401

402 403 404 405 406 407
  def test_nil_true_and_false_represented_as_themselves
    assert_equal nil,   nil.as_json
    assert_equal true,  true.as_json
    assert_equal false, false.as_json
  end

408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439
  def test_json_gem_dump_by_passing_active_support_encoder
    h = HashWithAsJson.new
    h[:foo] = "hello"
    h[:bar] = "world"

    assert_equal %({"foo":"hello","bar":"world"}), JSON.dump(h)
    assert_nil h.as_json_called
  end

  def test_json_gem_generate_by_passing_active_support_encoder
    h = HashWithAsJson.new
    h[:foo] = "hello"
    h[:bar] = "world"

    assert_equal %({"foo":"hello","bar":"world"}), JSON.generate(h)
    assert_nil h.as_json_called
  end

  def test_json_gem_pretty_generate_by_passing_active_support_encoder
    h = HashWithAsJson.new
    h[:foo] = "hello"
    h[:bar] = "world"

    assert_equal <<EXPECTED.chomp, JSON.pretty_generate(h)
{
  "foo": "hello",
  "bar": "world"
}
EXPECTED
    assert_nil h.as_json_called
  end

440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455
  def test_twz_to_json_with_use_standard_json_time_format_config_set_to_false
    with_standard_json_time_format(false) do
      zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)']
      time = ActiveSupport::TimeWithZone.new(Time.utc(2000), zone)
      assert_equal "\"1999/12/31 19:00:00 -0500\"", ActiveSupport::JSON.encode(time)
    end
  end

  def test_twz_to_json_with_use_standard_json_time_format_config_set_to_true
    with_standard_json_time_format(true) do
      zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)']
      time = ActiveSupport::TimeWithZone.new(Time.utc(2000), zone)
      assert_equal "\"1999-12-31T19:00:00.000-05:00\"", ActiveSupport::JSON.encode(time)
    end
  end

456
  def test_twz_to_json_with_custom_time_precision
457
    with_standard_json_time_format(true) do
458 459 460 461 462
      with_time_precision(0) do
        zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)']
        time = ActiveSupport::TimeWithZone.new(Time.utc(2000), zone)
        assert_equal "\"1999-12-31T19:00:00-05:00\"", ActiveSupport::JSON.encode(time)
      end
463
    end
464 465 466 467
  end

  def test_time_to_json_with_custom_time_precision
    with_standard_json_time_format(true) do
468 469 470
      with_time_precision(0) do
        assert_equal "\"2000-01-01T00:00:00Z\"", ActiveSupport::JSON.encode(Time.utc(2000))
      end
471 472 473 474 475
    end
  end

  def test_datetime_to_json_with_custom_time_precision
    with_standard_json_time_format(true) do
476 477 478
      with_time_precision(0) do
        assert_equal "\"2000-01-01T00:00:00+00:00\"", ActiveSupport::JSON.encode(DateTime.new(2000))
      end
479
    end
480 481 482 483 484 485 486 487
  end

  def test_twz_to_json_when_wrapping_a_date_time
    zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)']
    time = ActiveSupport::TimeWithZone.new(DateTime.new(2000), zone)
    assert_equal '"1999-12-31T19:00:00.000-05:00"', ActiveSupport::JSON.encode(time)
  end

488
  protected
489

490 491 492
    def object_keys(json_object)
      json_object[1..-2].scan(/([^{}:,\s]+):/).flatten.sort
    end
493

494 495 496 497 498 499
    def with_standard_json_time_format(boolean = true)
      old, ActiveSupport.use_standard_json_time_format = ActiveSupport.use_standard_json_time_format, boolean
      yield
    ensure
      ActiveSupport.use_standard_json_time_format = old
    end
500 501 502 503 504 505 506 507

    def with_time_precision(value)
      old_value = ActiveSupport::JSON::Encoding.time_precision
      ActiveSupport::JSON::Encoding.time_precision = value
      yield
    ensure
      ActiveSupport::JSON::Encoding.time_precision = old_value
    end
508
end