oj 2.18.5 → 3.0.0

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 (111) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +33 -226
  3. data/ext/oj/circarray.c +0 -25
  4. data/ext/oj/circarray.h +0 -25
  5. data/ext/oj/code.c +227 -0
  6. data/ext/oj/code.h +40 -0
  7. data/ext/oj/compat.c +126 -38
  8. data/ext/oj/custom.c +1097 -0
  9. data/ext/oj/dump.c +658 -2376
  10. data/ext/oj/dump.h +92 -0
  11. data/ext/oj/dump_compat.c +937 -0
  12. data/ext/oj/dump_leaf.c +254 -0
  13. data/ext/oj/dump_object.c +810 -0
  14. data/ext/oj/dump_rails.c +329 -0
  15. data/ext/oj/dump_strict.c +416 -0
  16. data/ext/oj/err.c +0 -25
  17. data/ext/oj/err.h +8 -2
  18. data/ext/oj/fast.c +24 -24
  19. data/ext/oj/mimic_json.c +817 -0
  20. data/ext/oj/mimic_rails.c +806 -0
  21. data/ext/oj/mimic_rails.h +17 -0
  22. data/ext/oj/object.c +18 -72
  23. data/ext/oj/odd.c +0 -25
  24. data/ext/oj/odd.h +2 -27
  25. data/ext/oj/oj.c +655 -1503
  26. data/ext/oj/oj.h +93 -40
  27. data/ext/oj/parse.c +99 -46
  28. data/ext/oj/parse.h +12 -26
  29. data/ext/oj/reader.c +1 -25
  30. data/ext/oj/reader.h +3 -25
  31. data/ext/oj/resolve.c +9 -11
  32. data/ext/oj/resolve.h +2 -2
  33. data/ext/oj/rxclass.c +133 -0
  34. data/ext/oj/rxclass.h +27 -0
  35. data/ext/oj/saj.c +4 -25
  36. data/ext/oj/scp.c +3 -25
  37. data/ext/oj/sparse.c +89 -13
  38. data/ext/oj/stream_writer.c +301 -0
  39. data/ext/oj/strict.c +4 -27
  40. data/ext/oj/string_writer.c +480 -0
  41. data/ext/oj/val_stack.h +6 -2
  42. data/lib/oj.rb +1 -23
  43. data/lib/oj/easy_hash.rb +12 -4
  44. data/lib/oj/json.rb +172 -0
  45. data/lib/oj/mimic.rb +123 -18
  46. data/lib/oj/state.rb +131 -0
  47. data/lib/oj/version.rb +1 -1
  48. data/pages/Advanced.md +22 -0
  49. data/pages/Compatibility.md +25 -0
  50. data/pages/Custom.md +23 -0
  51. data/pages/Encoding.md +65 -0
  52. data/pages/JsonGem.md +79 -0
  53. data/pages/Modes.md +140 -0
  54. data/pages/Options.md +250 -0
  55. data/pages/Rails.md +60 -0
  56. data/pages/Security.md +20 -0
  57. data/test/activesupport4/decoding_test.rb +105 -0
  58. data/test/activesupport4/encoding_test.rb +531 -0
  59. data/test/activesupport4/test_helper.rb +41 -0
  60. data/test/activesupport5/decoding_test.rb +125 -0
  61. data/test/activesupport5/encoding_test.rb +483 -0
  62. data/test/activesupport5/encoding_test_cases.rb +90 -0
  63. data/test/activesupport5/test_helper.rb +50 -0
  64. data/test/activesupport5/time_zone_test_helpers.rb +24 -0
  65. data/test/json_gem/json_addition_test.rb +216 -0
  66. data/test/json_gem/json_common_interface_test.rb +143 -0
  67. data/test/json_gem/json_encoding_test.rb +109 -0
  68. data/test/json_gem/json_ext_parser_test.rb +20 -0
  69. data/test/json_gem/json_fixtures_test.rb +35 -0
  70. data/test/json_gem/json_generator_test.rb +383 -0
  71. data/test/json_gem/json_generic_object_test.rb +90 -0
  72. data/test/json_gem/json_parser_test.rb +470 -0
  73. data/test/json_gem/json_string_matching_test.rb +42 -0
  74. data/test/json_gem/test_helper.rb +18 -0
  75. data/test/perf_compat.rb +30 -28
  76. data/test/perf_object.rb +1 -1
  77. data/test/perf_strict.rb +18 -1
  78. data/test/sample.rb +0 -1
  79. data/test/test_compat.rb +169 -93
  80. data/test/test_custom.rb +355 -0
  81. data/test/test_file.rb +0 -8
  82. data/test/test_null.rb +376 -0
  83. data/test/test_object.rb +268 -3
  84. data/test/test_scp.rb +22 -1
  85. data/test/test_strict.rb +160 -4
  86. data/test/test_various.rb +52 -620
  87. data/test/tests.rb +14 -0
  88. data/test/tests_mimic.rb +14 -0
  89. data/test/tests_mimic_addition.rb +7 -0
  90. metadata +89 -47
  91. data/test/activesupport_datetime_test.rb +0 -23
  92. data/test/bug.rb +0 -51
  93. data/test/bug2.rb +0 -10
  94. data/test/bug3.rb +0 -46
  95. data/test/bug_fast.rb +0 -32
  96. data/test/bug_load.rb +0 -24
  97. data/test/crash.rb +0 -111
  98. data/test/curl/curl_oj.rb +0 -46
  99. data/test/curl/get_oj.rb +0 -24
  100. data/test/curl/just_curl.rb +0 -31
  101. data/test/curl/just_oj.rb +0 -51
  102. data/test/example.rb +0 -11
  103. data/test/foo.rb +0 -24
  104. data/test/io.rb +0 -48
  105. data/test/isolated/test_mimic_rails_datetime.rb +0 -27
  106. data/test/mod.rb +0 -16
  107. data/test/rails.rb +0 -50
  108. data/test/russian.rb +0 -18
  109. data/test/struct.rb +0 -29
  110. data/test/test_serializer.rb +0 -59
  111. data/test/write_timebars.rb +0 -31
@@ -0,0 +1,41 @@
1
+ require 'active_support/testing/assertions'
2
+ require 'active_support/testing/deprecation'
3
+ require 'active_support/testing/declarative'
4
+ require 'minitest/autorun'
5
+
6
+ module ActiveSupport
7
+ class TestCase < ::Minitest::Test
8
+ Assertion = Minitest::Assertion
9
+
10
+ alias_method :method_name, :name
11
+
12
+ include ActiveSupport::Testing::Assertions
13
+ include ActiveSupport::Testing::Deprecation
14
+ extend ActiveSupport::Testing::Declarative
15
+
16
+ # test/unit backwards compatibility methods
17
+ alias :assert_raise :assert_raises
18
+ alias :assert_not_empty :refute_empty
19
+ alias :assert_not_equal :refute_equal
20
+ alias :assert_not_in_delta :refute_in_delta
21
+ alias :assert_not_in_epsilon :refute_in_epsilon
22
+ alias :assert_not_includes :refute_includes
23
+ alias :assert_not_instance_of :refute_instance_of
24
+ alias :assert_not_kind_of :refute_kind_of
25
+ alias :assert_no_match :refute_match
26
+ alias :assert_not_nil :refute_nil
27
+ alias :assert_not_operator :refute_operator
28
+ alias :assert_not_predicate :refute_predicate
29
+ alias :assert_not_respond_to :refute_respond_to
30
+ alias :assert_not_same :refute_same
31
+
32
+ # Fails if the block raises an exception.
33
+ #
34
+ # assert_nothing_raised do
35
+ # ...
36
+ # end
37
+ def assert_nothing_raised(*args)
38
+ yield
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,125 @@
1
+ require 'activesupport5/test_helper'
2
+ require 'active_support/json'
3
+ require 'active_support/time'
4
+ require 'activesupport5/time_zone_test_helpers'
5
+
6
+ require 'oj'
7
+
8
+ Oj::Rails.set_decoder()
9
+
10
+ class TestJSONDecoding < ActiveSupport::TestCase
11
+ include TimeZoneTestHelpers
12
+
13
+ class Foo
14
+ def self.json_create(object)
15
+ "Foo"
16
+ end
17
+ end
18
+
19
+ TESTS = {
20
+ %q({"returnTo":{"\/categories":"\/"}}) => {"returnTo" => {"/categories" => "/"}},
21
+ %q({"return\\"To\\":":{"\/categories":"\/"}}) => {"return\"To\":" => {"/categories" => "/"}},
22
+ %q({"returnTo":{"\/categories":1}}) => {"returnTo" => {"/categories" => 1}},
23
+ %({"returnTo":[1,"a"]}) => {"returnTo" => [1, "a"]},
24
+ %({"returnTo":[1,"\\"a\\",", "b"]}) => {"returnTo" => [1, "\"a\",", "b"]},
25
+ %({"a": "'", "b": "5,000"}) => {"a" => "'", "b" => "5,000"},
26
+ %({"a": "a's, b's and c's", "b": "5,000"}) => {"a" => "a's, b's and c's", "b" => "5,000"},
27
+ # multibyte
28
+ %({"matzue": "松江", "asakusa": "浅草"}) => {"matzue" => "松江", "asakusa" => "浅草"},
29
+ %({"a": "2007-01-01"}) => {'a' => Date.new(2007, 1, 1)},
30
+ %({"a": "2007-01-01 01:12:34 Z"}) => {'a' => Time.utc(2007, 1, 1, 1, 12, 34)},
31
+ %(["2007-01-01 01:12:34 Z"]) => [Time.utc(2007, 1, 1, 1, 12, 34)],
32
+ %(["2007-01-01 01:12:34 Z", "2007-01-01 01:12:35 Z"]) => [Time.utc(2007, 1, 1, 1, 12, 34), Time.utc(2007, 1, 1, 1, 12, 35)],
33
+ # no time zone
34
+ %({"a": "2007-01-01 01:12:34"}) => {'a' => Time.new(2007, 1, 1, 1, 12, 34, "-05:00")},
35
+ # invalid date
36
+ %({"a": "1089-10-40"}) => {'a' => "1089-10-40"},
37
+ # xmlschema date notation
38
+ %({"a": "2009-08-10T19:01:02"}) => {'a' => Time.new(2009, 8, 10, 19, 1, 2, "-04:00")},
39
+ %({"a": "2009-08-10T19:01:02Z"}) => {'a' => Time.utc(2009, 8, 10, 19, 1, 2)},
40
+ %({"a": "2009-08-10T19:01:02+02:00"}) => {'a' => Time.utc(2009, 8, 10, 17, 1, 2)},
41
+ %({"a": "2009-08-10T19:01:02-05:00"}) => {'a' => Time.utc(2009, 8, 11, 00, 1, 2)},
42
+ # needs to be *exact*
43
+ %({"a": " 2007-01-01 01:12:34 Z "}) => {'a' => " 2007-01-01 01:12:34 Z "},
44
+ %({"a": "2007-01-01 : it's your birthday"}) => {'a' => "2007-01-01 : it's your birthday"},
45
+ %([]) => [],
46
+ %({}) => {},
47
+ %({"a":1}) => {"a" => 1},
48
+ %({"a": ""}) => {"a" => ""},
49
+ %({"a":"\\""}) => {"a" => "\""},
50
+ %({"a": null}) => {"a" => nil},
51
+ %({"a": true}) => {"a" => true},
52
+ %({"a": false}) => {"a" => false},
53
+ %q({"bad":"\\\\","trailing":""}) => {"bad" => "\\", "trailing" => ""},
54
+ %q({"a": "http:\/\/test.host\/posts\/1"}) => {"a" => "http://test.host/posts/1"},
55
+ %q({"a": "\u003cunicode\u0020escape\u003e"}) => {"a" => "<unicode escape>"},
56
+ %q({"a": "\\\\u0020skip double backslashes"}) => {"a" => "\\u0020skip double backslashes"},
57
+ %q({"a": "\u003cbr /\u003e"}) => {'a' => "<br />"},
58
+ %q({"b":["\u003ci\u003e","\u003cb\u003e","\u003cu\u003e"]}) => {'b' => ["<i>","<b>","<u>"]},
59
+ # test combination of dates and escaped or unicode encoded data in arrays
60
+ %q([{"d":"1970-01-01", "s":"\u0020escape"},{"d":"1970-01-01", "s":"\u0020escape"}]) =>
61
+ [{'d' => Date.new(1970, 1, 1), 's' => ' escape'},{'d' => Date.new(1970, 1, 1), 's' => ' escape'}],
62
+ %q([{"d":"1970-01-01","s":"http:\/\/example.com"},{"d":"1970-01-01","s":"http:\/\/example.com"}]) =>
63
+ [{'d' => Date.new(1970, 1, 1), 's' => 'http://example.com'},
64
+ {'d' => Date.new(1970, 1, 1), 's' => 'http://example.com'}],
65
+ # tests escaping of "\n" char with Yaml backend
66
+ %q({"a":"\n"}) => {"a"=>"\n"},
67
+ %q({"a":"\u000a"}) => {"a"=>"\n"},
68
+ %q({"a":"Line1\u000aLine2"}) => {"a"=>"Line1\nLine2"},
69
+ # prevent json unmarshalling
70
+ %q({"json_class":"TestJSONDecoding::Foo"}) => {"json_class"=>"TestJSONDecoding::Foo"},
71
+ # json "fragments" - these are invalid JSON, but ActionPack relies on this
72
+ %q("a string") => "a string",
73
+ %q(1.1) => 1.1,
74
+ %q(1) => 1,
75
+ %q(-1) => -1,
76
+ %q(true) => true,
77
+ %q(false) => false,
78
+ %q(null) => nil
79
+ }
80
+
81
+ TESTS.each_with_index do |(json, expected), index|
82
+ test "json decodes #{index}" do
83
+ with_tz_default 'Eastern Time (US & Canada)' do
84
+ with_parse_json_times(true) do
85
+ silence_warnings do
86
+ if expected.nil?
87
+ assert_nil(ActiveSupport::JSON.decode(json), "JSON failed for #{json}")
88
+ else
89
+ assert_equal(expected, ActiveSupport::JSON.decode(json), "JSON failed for #{json}")
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
96
+
97
+ test "json decodes time json with time parsing disabled" do
98
+ with_parse_json_times(false) do
99
+ expected = {"a" => "2007-01-01 01:12:34 Z"}
100
+ assert_equal expected, ActiveSupport::JSON.decode(%({"a": "2007-01-01 01:12:34 Z"}))
101
+ end
102
+ end
103
+
104
+ def test_failed_json_decoding
105
+ assert_raise(ActiveSupport::JSON.parse_error) { ActiveSupport::JSON.decode(%(undefined)) }
106
+ assert_raise(ActiveSupport::JSON.parse_error) { ActiveSupport::JSON.decode(%({a: 1})) }
107
+ assert_raise(ActiveSupport::JSON.parse_error) { ActiveSupport::JSON.decode(%({: 1})) }
108
+ assert_raise(ActiveSupport::JSON.parse_error) { ActiveSupport::JSON.decode(%()) }
109
+ end
110
+
111
+ def test_cannot_pass_unsupported_options
112
+ assert_raise(ArgumentError) { ActiveSupport::JSON.decode("", create_additions: true) }
113
+ end
114
+
115
+ private
116
+
117
+ def with_parse_json_times(value)
118
+ old_value = ActiveSupport.parse_json_times
119
+ ActiveSupport.parse_json_times = value
120
+ yield
121
+ ensure
122
+ ActiveSupport.parse_json_times = old_value
123
+ end
124
+ end
125
+
@@ -0,0 +1,483 @@
1
+ require 'activesupport5/test_helper'
2
+ require 'securerandom'
3
+ require 'active_support/core_ext/string/inflections'
4
+ require 'active_support/json'
5
+ require 'active_support/time'
6
+ require 'activesupport5/time_zone_test_helpers'
7
+ require 'activesupport5/encoding_test_cases'
8
+
9
+ require 'oj'
10
+
11
+ # Sets the ActiveSupport emcoder to be Oj and also wraps the setting of
12
+ # globals.
13
+ Oj::Rails.set_encoder()
14
+ Oj::Rails.optimize(BigDecimal, Time, Range, Regexp)
15
+
16
+ class TestJSONEncoding < ActiveSupport::TestCase
17
+ include TimeZoneTestHelpers
18
+
19
+ def sorted_json(json)
20
+ return json unless json =~ /^\{.*\}$/
21
+ '{' + json[1..-2].split(',').sort.join(',') + '}'
22
+ end
23
+
24
+ JSONTest::EncodingTestCases.constants.each do |class_tests|
25
+ define_method("test_#{class_tests[0..-6].underscore}") do
26
+ begin
27
+ prev = ActiveSupport.use_standard_json_time_format
28
+
29
+ ActiveSupport.escape_html_entities_in_json = class_tests !~ /^Standard/
30
+ ActiveSupport.use_standard_json_time_format = class_tests =~ /^Standard/
31
+ JSONTest::EncodingTestCases.const_get(class_tests).each do |pair|
32
+ assert_equal pair.last, sorted_json(ActiveSupport::JSON.encode(pair.first))
33
+ end
34
+ ensure
35
+ ActiveSupport.escape_html_entities_in_json = false
36
+ ActiveSupport.use_standard_json_time_format = prev
37
+ end
38
+ end
39
+ end
40
+
41
+ def test_process_status
42
+ rubinius_skip "https://github.com/rubinius/rubinius/issues/3334"
43
+
44
+ # There doesn't seem to be a good way to get a handle on a Process::Status object without actually
45
+ # creating a child process, hence this to populate $?
46
+ system("not_a_real_program_#{SecureRandom.hex}")
47
+ assert_equal %({"exitstatus":#{$?.exitstatus},"pid":#{$?.pid}}), ActiveSupport::JSON.encode($?)
48
+ end
49
+
50
+ def test_hash_encoding
51
+ assert_equal %({\"a\":\"b\"}), ActiveSupport::JSON.encode(:a => :b)
52
+ assert_equal %({\"a\":1}), ActiveSupport::JSON.encode('a' => 1)
53
+ assert_equal %({\"a\":[1,2]}), ActiveSupport::JSON.encode('a' => [1,2])
54
+ assert_equal %({"1":2}), ActiveSupport::JSON.encode(1 => 2)
55
+
56
+ assert_equal %({\"a\":\"b\",\"c\":\"d\"}), sorted_json(ActiveSupport::JSON.encode(:a => :b, :c => :d))
57
+ end
58
+
59
+ def test_hash_keys_encoding
60
+ ActiveSupport.escape_html_entities_in_json = true
61
+ assert_equal "{\"\\u003c\\u003e\":\"\\u003c\\u003e\"}", ActiveSupport::JSON.encode("<>" => "<>")
62
+ ensure
63
+ ActiveSupport.escape_html_entities_in_json = false
64
+ end
65
+
66
+ def test_utf8_string_encoded_properly
67
+ # The original test seems to expect that
68
+ # ActiveSupport.escape_html_entities_in_json reverts to true even after
69
+ # being set to false. I haven't been able to figure that out so the value
70
+ # is set to true, the default, before running the test. This might be
71
+ # wrong but for now it will have to do.
72
+ ActiveSupport.escape_html_entities_in_json = true
73
+ result = ActiveSupport::JSON.encode('€2.99')
74
+ assert_equal '"€2.99"', result
75
+ assert_equal(Encoding::UTF_8, result.encoding)
76
+
77
+ result = ActiveSupport::JSON.encode('✎☺')
78
+ assert_equal '"✎☺"', result
79
+ assert_equal(Encoding::UTF_8, result.encoding)
80
+ end
81
+
82
+ def test_non_utf8_string_transcodes
83
+ s = '二'.encode('Shift_JIS')
84
+ result = ActiveSupport::JSON.encode(s)
85
+ assert_equal '"二"', result
86
+ assert_equal Encoding::UTF_8, result.encoding
87
+ end
88
+
89
+ def test_wide_utf8_chars
90
+ w = '𠜎'
91
+ result = ActiveSupport::JSON.encode(w)
92
+ assert_equal '"𠜎"', result
93
+ end
94
+
95
+ def test_wide_utf8_roundtrip
96
+ hash = { string: "𐒑" }
97
+ json = ActiveSupport::JSON.encode(hash)
98
+ decoded_hash = ActiveSupport::JSON.decode(json)
99
+ assert_equal "𐒑", decoded_hash['string']
100
+ end
101
+
102
+ def test_hash_key_identifiers_are_always_quoted
103
+ values = {0 => 0, 1 => 1, :_ => :_, "$" => "$", "a" => "a", :A => :A, :A0 => :A0, "A0B" => "A0B"}
104
+ assert_equal %w( "$" "A" "A0" "A0B" "_" "a" "0" "1" ).sort, object_keys(ActiveSupport::JSON.encode(values))
105
+ end
106
+
107
+ def test_hash_should_allow_key_filtering_with_only
108
+ assert_equal %({"a":1}), ActiveSupport::JSON.encode({'a' => 1, :b => 2, :c => 3}, :only => 'a')
109
+ end
110
+
111
+ def test_hash_should_allow_key_filtering_with_except
112
+ assert_equal %({"b":2}), ActiveSupport::JSON.encode({'foo' => 'bar', :b => 2, :c => 3}, :except => ['foo', :c])
113
+ end
114
+
115
+ def test_time_to_json_includes_local_offset
116
+ with_standard_json_time_format(true) do
117
+ with_env_tz 'US/Eastern' do
118
+ assert_equal %("2005-02-01T15:15:10.000-05:00"), ActiveSupport::JSON.encode(Time.local(2005,2,1,15,15,10))
119
+ end
120
+ end
121
+ end
122
+
123
+ def test_hash_with_time_to_json
124
+ with_standard_json_time_format(false) do
125
+ assert_equal '{"time":"2009/01/01 00:00:00 +0000"}', { :time => Time.utc(2009) }.to_json
126
+ end
127
+ end
128
+
129
+ def test_nested_hash_with_float
130
+ assert_nothing_raised do
131
+ hash = {
132
+ "CHI" => {
133
+ :display_name => "chicago",
134
+ :latitude => 123.234
135
+ }
136
+ }
137
+ ActiveSupport::JSON.encode(hash)
138
+ end
139
+ end
140
+
141
+ def test_hash_like_with_options
142
+ h = JSONTest::Hashlike.new
143
+ json = h.to_json :only => [:foo]
144
+
145
+ assert_equal({"foo"=>"hello"}, JSON.parse(json))
146
+ end
147
+
148
+ def test_object_to_json_with_options
149
+ obj = Object.new
150
+ obj.instance_variable_set :@foo, "hello"
151
+ obj.instance_variable_set :@bar, "world"
152
+ json = obj.to_json :only => ["foo"]
153
+
154
+ assert_equal({"foo"=>"hello"}, JSON.parse(json))
155
+ end
156
+
157
+ def test_struct_to_json_with_options
158
+ struct = Struct.new(:foo, :bar).new
159
+ struct.foo = "hello"
160
+ struct.bar = "world"
161
+ json = struct.to_json :only => [:foo]
162
+
163
+ assert_equal({"foo"=>"hello"}, JSON.parse(json))
164
+ end
165
+
166
+ def test_hash_should_pass_encoding_options_to_children_in_as_json
167
+ person = {
168
+ :name => 'John',
169
+ :address => {
170
+ :city => 'London',
171
+ :country => 'UK'
172
+ }
173
+ }
174
+ json = person.as_json :only => [:address, :city]
175
+
176
+ assert_equal({ 'address' => { 'city' => 'London' }}, json)
177
+ end
178
+
179
+ def test_hash_should_pass_encoding_options_to_children_in_to_json
180
+ person = {
181
+ :name => 'John',
182
+ :address => {
183
+ :city => 'London',
184
+ :country => 'UK'
185
+ }
186
+ }
187
+ json = person.to_json :only => [:address, :city]
188
+
189
+ assert_equal(%({"address":{"city":"London"}}), json)
190
+ end
191
+
192
+ def test_array_should_pass_encoding_options_to_children_in_as_json
193
+ people = [
194
+ { :name => 'John', :address => { :city => 'London', :country => 'UK' }},
195
+ { :name => 'Jean', :address => { :city => 'Paris' , :country => 'France' }}
196
+ ]
197
+ json = people.as_json :only => [:address, :city]
198
+ expected = [
199
+ { 'address' => { 'city' => 'London' }},
200
+ { 'address' => { 'city' => 'Paris' }}
201
+ ]
202
+
203
+ assert_equal(expected, json)
204
+ end
205
+
206
+ def test_array_should_pass_encoding_options_to_children_in_to_json
207
+ people = [
208
+ { :name => 'John', :address => { :city => 'London', :country => 'UK' }},
209
+ { :name => 'Jean', :address => { :city => 'Paris' , :country => 'France' }}
210
+ ]
211
+ json = people.to_json :only => [:address, :city]
212
+
213
+ assert_equal(%([{"address":{"city":"London"}},{"address":{"city":"Paris"}}]), json)
214
+ end
215
+
216
+ People = Class.new(BasicObject) do
217
+ include Enumerable
218
+ def initialize()
219
+ @people = [
220
+ { :name => 'John', :address => { :city => 'London', :country => 'UK' }},
221
+ { :name => 'Jean', :address => { :city => 'Paris' , :country => 'France' }}
222
+ ]
223
+ end
224
+ def each(*, &blk)
225
+ @people.each do |p|
226
+ yield p if blk
227
+ p
228
+ end.each
229
+ end
230
+ end
231
+
232
+ def test_enumerable_should_generate_json_with_as_json
233
+ json = People.new.as_json :only => [:address, :city]
234
+ expected = [
235
+ { 'address' => { 'city' => 'London' }},
236
+ { 'address' => { 'city' => 'Paris' }}
237
+ ]
238
+
239
+ assert_equal(expected, json)
240
+ end
241
+
242
+ def test_enumerable_should_generate_json_with_to_json
243
+ json = People.new.to_json :only => [:address, :city]
244
+ assert_equal(%([{"address":{"city":"London"}},{"address":{"city":"Paris"}}]), json)
245
+ end
246
+
247
+ def test_enumerable_should_pass_encoding_options_to_children_in_as_json
248
+ json = People.new.each.as_json :only => [:address, :city]
249
+ expected = [
250
+ { 'address' => { 'city' => 'London' }},
251
+ { 'address' => { 'city' => 'Paris' }}
252
+ ]
253
+
254
+ assert_equal(expected, json)
255
+ end
256
+
257
+ def test_enumerable_should_pass_encoding_options_to_children_in_to_json
258
+ json = People.new.each.to_json :only => [:address, :city]
259
+
260
+ assert_equal(%([{"address":{"city":"London"}},{"address":{"city":"Paris"}}]), json)
261
+ end
262
+
263
+ class CustomWithOptions
264
+ attr_accessor :foo, :bar
265
+
266
+ def as_json(options={})
267
+ options[:only] = %w(foo bar)
268
+ super(options)
269
+ end
270
+ end
271
+
272
+ def test_hash_to_json_should_not_keep_options_around
273
+ f = CustomWithOptions.new
274
+ f.foo = "hello"
275
+ f.bar = "world"
276
+
277
+ hash = {"foo" => f, "other_hash" => {"foo" => "other_foo", "test" => "other_test"}}
278
+ assert_equal({"foo"=>{"foo"=>"hello","bar"=>"world"},
279
+ "other_hash" => {"foo"=>"other_foo","test"=>"other_test"}}, ActiveSupport::JSON.decode(hash.to_json))
280
+ end
281
+
282
+ def test_array_to_json_should_not_keep_options_around
283
+ f = CustomWithOptions.new
284
+ f.foo = "hello"
285
+ f.bar = "world"
286
+
287
+ array = [f, {"foo" => "other_foo", "test" => "other_test"}]
288
+ assert_equal([{"foo"=>"hello","bar"=>"world"},
289
+ {"foo"=>"other_foo","test"=>"other_test"}], ActiveSupport::JSON.decode(array.to_json))
290
+ end
291
+
292
+ class OptionsTest
293
+ def as_json(options = :default)
294
+ options
295
+ end
296
+ end
297
+
298
+ def test_hash_as_json_without_options
299
+ json = { foo: OptionsTest.new }.as_json
300
+ assert_equal({"foo" => :default}, json)
301
+ end
302
+
303
+ def test_array_as_json_without_options
304
+ json = [ OptionsTest.new ].as_json
305
+ assert_equal([:default], json)
306
+ end
307
+
308
+ def test_struct_encoding
309
+ Struct.new('UserNameAndEmail', :name, :email)
310
+ Struct.new('UserNameAndDate', :name, :date)
311
+ Struct.new('Custom', :name, :sub)
312
+ user_email = Struct::UserNameAndEmail.new 'David', 'sample@example.com'
313
+ user_birthday = Struct::UserNameAndDate.new 'David', Date.new(2010, 01, 01)
314
+ custom = Struct::Custom.new 'David', user_birthday
315
+
316
+
317
+ json_strings = ""
318
+ json_string_and_date = ""
319
+ json_custom = ""
320
+
321
+ assert_nothing_raised do
322
+ json_strings = user_email.to_json
323
+ json_string_and_date = user_birthday.to_json
324
+ json_custom = custom.to_json
325
+ end
326
+
327
+ assert_equal({"name" => "David",
328
+ "sub" => {
329
+ "name" => "David",
330
+ "date" => "2010-01-01" }}, ActiveSupport::JSON.decode(json_custom))
331
+
332
+ assert_equal({"name" => "David", "email" => "sample@example.com"},
333
+ ActiveSupport::JSON.decode(json_strings))
334
+
335
+ assert_equal({"name" => "David", "date" => "2010-01-01"},
336
+ ActiveSupport::JSON.decode(json_string_and_date))
337
+ end
338
+
339
+ def test_nil_true_and_false_represented_as_themselves
340
+ assert_nil nil.as_json
341
+ assert_equal true, true.as_json
342
+ assert_equal false, false.as_json
343
+ end
344
+
345
+ class HashWithAsJson < Hash
346
+ attr_accessor :as_json_called
347
+
348
+ def initialize(*)
349
+ super
350
+ end
351
+
352
+ def as_json(options={})
353
+ @as_json_called = true
354
+ super
355
+ end
356
+ end
357
+
358
+ def test_json_gem_dump_by_passing_active_support_encoder
359
+ h = HashWithAsJson.new
360
+ h[:foo] = "hello"
361
+ h[:bar] = "world"
362
+
363
+ assert_equal %({"foo":"hello","bar":"world"}), JSON.dump(h)
364
+ assert_nil h.as_json_called
365
+ end
366
+
367
+ def test_json_gem_generate_by_passing_active_support_encoder
368
+ h = HashWithAsJson.new
369
+ h[:foo] = "hello"
370
+ h[:bar] = "world"
371
+
372
+ assert_equal %({"foo":"hello","bar":"world"}), JSON.generate(h)
373
+ assert_nil h.as_json_called
374
+ end
375
+
376
+ def test_json_gem_pretty_generate_by_passing_active_support_encoder
377
+ h = HashWithAsJson.new
378
+ h[:foo] = "hello"
379
+ h[:bar] = "world"
380
+
381
+ assert_equal <<EXPECTED.chomp, JSON.pretty_generate(h)
382
+ {
383
+ "foo": "hello",
384
+ "bar": "world"
385
+ }
386
+ EXPECTED
387
+ assert_nil h.as_json_called
388
+ end
389
+
390
+ def test_twz_to_json_with_use_standard_json_time_format_config_set_to_false
391
+ with_standard_json_time_format(false) do
392
+ zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)']
393
+ time = ActiveSupport::TimeWithZone.new(Time.utc(2000), zone)
394
+ assert_equal "\"1999/12/31 19:00:00 -0500\"", ActiveSupport::JSON.encode(time)
395
+ end
396
+ end
397
+
398
+ def test_twz_to_json_with_use_standard_json_time_format_config_set_to_true
399
+ with_standard_json_time_format(true) do
400
+ zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)']
401
+ time = ActiveSupport::TimeWithZone.new(Time.utc(2000), zone)
402
+ assert_equal "\"1999-12-31T19:00:00.000-05:00\"", ActiveSupport::JSON.encode(time)
403
+ end
404
+ end
405
+
406
+ def test_twz_to_json_with_custom_time_precision
407
+ with_standard_json_time_format(true) do
408
+ with_time_precision(0) do
409
+ zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)']
410
+ time = ActiveSupport::TimeWithZone.new(Time.utc(2000), zone)
411
+ assert_equal "\"1999-12-31T19:00:00-05:00\"", ActiveSupport::JSON.encode(time)
412
+ end
413
+ end
414
+ end
415
+
416
+ def test_time_to_json_with_custom_time_precision
417
+ with_standard_json_time_format(true) do
418
+ with_time_precision(0) do
419
+ assert_equal "\"2000-01-01T00:00:00Z\"", ActiveSupport::JSON.encode(Time.utc(2000))
420
+ end
421
+ end
422
+ end
423
+
424
+ def test_datetime_to_json_with_custom_time_precision
425
+ with_standard_json_time_format(true) do
426
+ with_time_precision(0) do
427
+ assert_equal "\"2000-01-01T00:00:00+00:00\"", ActiveSupport::JSON.encode(DateTime.new(2000))
428
+ end
429
+ end
430
+ end
431
+
432
+ def test_twz_to_json_when_wrapping_a_date_time
433
+ zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)']
434
+ time = ActiveSupport::TimeWithZone.new(DateTime.new(2000), zone)
435
+ assert_equal '"1999-12-31T19:00:00.000-05:00"', ActiveSupport::JSON.encode(time)
436
+ end
437
+
438
+ def test_exception_to_json
439
+ exception = Exception.new("foo")
440
+ assert_equal '"foo"', ActiveSupport::JSON.encode(exception)
441
+ end
442
+
443
+ class InfiniteNumber
444
+ def as_json(options = nil)
445
+ { "number" => Float::INFINITY }
446
+ end
447
+ end
448
+
449
+ def test_to_json_works_when_as_json_returns_infinite_number
450
+ assert_equal '{"number":null}', InfiniteNumber.new.to_json
451
+ end
452
+
453
+ class NaNNumber
454
+ def as_json(options = nil)
455
+ { "number" => Float::INFINITY }
456
+ end
457
+ end
458
+
459
+ def test_to_json_works_when_as_json_returns_NaN_number
460
+ assert_equal '{"number":null}', NaNNumber.new.to_json
461
+ end
462
+
463
+ protected
464
+
465
+ def object_keys(json_object)
466
+ json_object[1..-2].scan(/([^{}:,\s]+):/).flatten.sort
467
+ end
468
+
469
+ def with_standard_json_time_format(boolean = true)
470
+ old, ActiveSupport.use_standard_json_time_format = ActiveSupport.use_standard_json_time_format, boolean
471
+ yield
472
+ ensure
473
+ ActiveSupport.use_standard_json_time_format = old
474
+ end
475
+
476
+ def with_time_precision(value)
477
+ old_value = ActiveSupport::JSON::Encoding.time_precision
478
+ ActiveSupport::JSON::Encoding.time_precision = value
479
+ yield
480
+ ensure
481
+ ActiveSupport::JSON::Encoding.time_precision = old_value
482
+ end
483
+ end