rails-patch-json-encode 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 7de82dfcf0621f732cbb61b7973a2a8be0af6576
4
- data.tar.gz: 52320bd295199b7797f6715a425ae5594c20652f
3
+ metadata.gz: fc0249750b2f5bb056856a8f3174ec5d7c96ab61
4
+ data.tar.gz: d9c341cb2f47d207cccc25f208c43e9929b2d7b3
5
5
  SHA512:
6
- metadata.gz: 0f6a698e80c1f7279f58f421e3227045b5d81335ae8f148b2c9c53774c1e3e94880d465ca0a9f6d6e672ae5696eca351d272f1be1e54834e76a3110b8d229e76
7
- data.tar.gz: 01eb45b91075c2e77b9d985de3e1a0bbe46eed10b8c0b7fa9b41b85f0d536785e4a7a4e39bc56ffe4c838e9eaa9facdc372774dd347b774d008d4fe591d93468
6
+ metadata.gz: 3a435841a65c874ef8735a29f751ff19e406fa52579057b800ae15f8d0d1de05f7813fb9be4159dee01248b41a7cd48b0b15d968e5d9960f34fdc785896e792d
7
+ data.tar.gz: f9a2fa556af989e56efc38ce7b5cf5ed4534145e777b2e7e6ba7634e6fa3039280941653f1c21f4a21a737db30578ee3fd7df11576b6d881fd9038be4baa377f
data/README.md CHANGED
@@ -8,38 +8,44 @@ All credits goes to [Jason Hutchens](https://github.com/jasonhutchens) for disco
8
8
 
9
9
  ## Installation
10
10
 
11
- First, go to your Rails console and type:
11
+ First, let's measure the time before the patch.
12
+ Go to your Rails console and type:
12
13
 
13
- data = Hash.new
14
+ require 'benchmark'
15
+ DATA = Hash.new
14
16
  key = 'aaa'
15
- 1000.times { data[key.succ!] = data.keys }
16
- 1000 * Benchmark.realtime { data.to_json }
17
+ 1000.times { DATA[key.succ!] = DATA.keys }
18
+ Benchmark.realtime { 5.times { DATA.to_json } }
17
19
 
18
- See how Rails performs before the patch.
19
-
20
- Second, bundle install this gem with a fast JSON encoding gem in your Rails' Gemfile.
20
+ Then bundle install this gem with a fast JSON encoding gem in your Rails' Gemfile.
21
21
 
22
22
  gem 'rails-patch-json-encode'
23
- gem 'oj'
23
+ gem 'yajl-ruby', require: 'yajl'
24
24
 
25
- In this case I choose the oj gem, but you can [choose a json-encoder gem that multi_json supports](https://github.com/intridea/multi_json#supported-json-engines).
25
+ In this case I choose the yajl-ruby gem, but you can [choose a json-encoder gem that multi_json supports](https://github.com/intridea/multi_json#supported-json-engines).
26
26
 
27
- Last, there are two levels of patch available. You have to choose one and call it explictly:
27
+ The final step is to choose a patch. Two types of patches are available, and you have to choose one and invoke it explictly:
28
28
 
29
29
  * `Rails::Patch::Json::Encode.patch_base_classes` patches all Ruby base classes.
30
30
  * `Rails::Patch::Json::Encode.patch_renderers` patches Rails' ActionController::Renderers only. This is for those who had issue with the JSON gem, as patching base classes cause infinite recursive loop.
31
31
 
32
- Place one of them in Rails' initializers like config/initializers/rails_patch_json_encode.rb, and Rails should now use the faster encoder.
32
+ Place one of them in a Rails initializer (e.g. `config/initializers/rails_patch_json_encode.rb`), and Rails should now use the faster encoder.
33
33
 
34
- ## Benchmark
34
+ Now it's done. Reopen your Rails console and rerun the benchmark to see the difference.
35
35
 
36
- For console benchmark comparison, restart console after the above installation. Call `Rails::Patch::Json::Encode.patch_base_classes` in console, then re-run the test to see how the performance changes.
36
+ ## Warning
37
37
 
38
- The actual performance boost on real-world applications will probably be less than that. For one of my page I see the rendering time dropped by 25%.
38
+ If you are using Oj gem, there is no need to install this gem. Call `Oj.optimize_rails` instead.
39
39
 
40
- ## Warning
40
+ Rails in recent years added safety nets to handle nested NaN, infinity and IO objects. This gem does not handle these cases.
41
41
 
42
- This gem may break your app. **Test your app**. I am not sure if this is production ready.
42
+ This gem may break your app. **Test your app**.
43
+
44
+ ## Benchmark
45
+
46
+ `rake benchmark` is provided to show the difference before and after the patch. From my machine the time is dropped to 14% when using yajl on Rails 5.2.
47
+
48
+ The actual performance boost on real-world applications will probably be less than that. For one of my page I see the rendering time dropped by 25%.
43
49
 
44
50
  ## What's with the name
45
51
 
data/Rakefile CHANGED
@@ -1 +1,19 @@
1
1
  require "bundler/gem_tasks"
2
+
3
+ task default: :test
4
+
5
+ desc "Benchmark"
6
+ task :benchmark do
7
+ require_relative 'benchmark/base_classes'
8
+ end
9
+
10
+ ### Test
11
+ require "rake/testtask"
12
+ dir = File.dirname(__FILE__)
13
+ Rake::TestTask.new do |t|
14
+ t.libs << "test"
15
+ t.test_files = Dir.glob("#{dir}/test/**/*_test.rb")
16
+ t.warning = true
17
+ t.verbose = true
18
+ t.ruby_opts = ["--dev"] if defined?(JRUBY_VERSION)
19
+ end
@@ -0,0 +1,38 @@
1
+ require 'benchmark'
2
+ require 'json'
3
+ require 'yajl'
4
+ require 'active_support'
5
+ require 'active_support/json'
6
+ require 'active_support/core_ext/object/json'
7
+ require 'rails/patch/json/encode'
8
+
9
+ puts <<MESSAGE
10
+ Benchmark: base classes patch mode
11
+
12
+ Gem versions:
13
+ ActiveSupport: #{ActiveSupport.version}
14
+ MultiJson: #{MultiJson::VERSION}
15
+
16
+ MESSAGE
17
+
18
+ # Prepare data
19
+
20
+ DATA = Hash.new
21
+ key = 'aaa'
22
+ 1000.times { DATA[key.succ!] = DATA.keys }
23
+ DATA.freeze
24
+
25
+ LOOP = 5
26
+ def loop_encoding
27
+ LOOP.times { DATA.to_json }
28
+ end
29
+
30
+ # Benchmark
31
+
32
+ print 'before patch: '
33
+ puts(Benchmark.measure{ loop_encoding })
34
+
35
+ Rails::Patch::Json::Encode.patch_base_classes
36
+
37
+ print 'after patch: '
38
+ puts(Benchmark.measure{ loop_encoding })
@@ -1,4 +1,5 @@
1
1
  require "rails/patch/json/encode/version"
2
+ require 'multi_json'
2
3
 
3
4
  module Rails::Patch::Json::Encode
4
5
  # Use multi_json instead of Rails' to_json method (which calls ActiveSupport::JSON)
@@ -20,15 +21,24 @@ module Rails::Patch::Json::Encode
20
21
  end
21
22
  end
22
23
 
23
- # Code from http://devblog.agworld.com.au/post/42586025923/the-performance-of-to-json-in-rails-sucks-and-theres
24
+
25
+
26
+ # Combine http://devblog.agworld.com.au/post/42586025923/the-performance-of-to-json-in-rails-sucks-and-theres
27
+ # and Rails' ToJsonWithActiveSupportEncoder together,
24
28
  # essentially reversing Rails' hard-coded call to ActiveSupport::JSON.encode
25
- def self.patch_base_classes
26
- [Object, Array, FalseClass, Float, Hash, Integer, NilClass, String, TrueClass].each do |klass|
27
- klass.class_eval do
28
- def to_json(opts = {})
29
- MultiJson::dump(self.as_json(opts), opts)
30
- end
29
+ module ToJsonWithMultiJson
30
+ def to_json(options = {})
31
+ if options.is_a?(::JSON::State)
32
+ super(options)
33
+ else
34
+ ::MultiJson::dump(self.as_json(options), options)
31
35
  end
32
36
  end
33
37
  end
38
+
39
+ def self.patch_base_classes
40
+ [Object, Array, FalseClass, Float, Hash, Integer, NilClass, String, TrueClass, Enumerable].reverse_each do |klass|
41
+ klass.prepend(ToJsonWithMultiJson)
42
+ end
43
+ end
34
44
  end
@@ -2,7 +2,7 @@ module Rails
2
2
  module Patch
3
3
  module Json
4
4
  module Encode
5
- VERSION = "0.1.1"
5
+ VERSION = "0.2.0"
6
6
  end
7
7
  end
8
8
  end
@@ -18,8 +18,11 @@ Gem::Specification.new do |spec|
18
18
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
19
  spec.require_paths = ["lib"]
20
20
 
21
- spec.add_development_dependency "bundler", "~> 1.3"
22
- spec.add_development_dependency "rake"
21
+ spec.add_development_dependency "bundler", "~> 1.16.0"
22
+ spec.add_development_dependency "rake", "~> 12.3.0"
23
+ spec.add_development_dependency "activesupport", "~> 5.1.4"
24
+ spec.add_development_dependency "yajl-ruby"
25
+ spec.add_development_dependency "byebug"
23
26
 
24
27
  spec.add_dependency 'multi_json', '>= 1.9.3', '~> 1.0'
25
28
  end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ ORIG_ARGV = ARGV.dup
4
+
5
+ require "active_support/core_ext/kernel/reporting"
6
+
7
+ silence_warnings do
8
+ Encoding.default_internal = Encoding::UTF_8
9
+ Encoding.default_external = Encoding::UTF_8
10
+ end
11
+
12
+ require "active_support/testing/autorun"
13
+ require "active_support/testing/method_call_assertions"
14
+
15
+ ENV["NO_RELOAD"] = "1"
16
+ require "active_support"
17
+
18
+ Thread.abort_on_exception = true
19
+
20
+ # Show backtraces for deprecated behavior for quicker cleanup.
21
+ ActiveSupport::Deprecation.debug = true
22
+
23
+ # Default to old to_time behavior but allow running tests with new behavior
24
+ ActiveSupport.to_time_preserves_timezone = ENV["PRESERVE_TIMEZONES"] == "1"
25
+
26
+ # Disable available locale checks to avoid warnings running the test suite.
27
+ I18n.enforce_available_locales = false
28
+
29
+ class ActiveSupport::TestCase
30
+ include ActiveSupport::Testing::MethodCallAssertions
31
+
32
+ # Skips the current run on Rubinius using Minitest::Assertions#skip
33
+ private def rubinius_skip(message = "")
34
+ skip message if RUBY_ENGINE == "rbx"
35
+ end
36
+
37
+ # Skips the current run on JRuby using Minitest::Assertions#skip
38
+ private def jruby_skip(message = "")
39
+ skip message if defined?(JRUBY_VERSION)
40
+ end
41
+
42
+ def frozen_error_class
43
+ Object.const_defined?(:FrozenError) ? FrozenError : RuntimeError
44
+ end
45
+ end
@@ -0,0 +1,485 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'yajl'
4
+ require "securerandom"
5
+ require "abstract_unit"
6
+ require "active_support/core_ext/string/inflections"
7
+ require "active_support/core_ext/regexp"
8
+ require "active_support/json"
9
+ require "active_support/time"
10
+ require "time_zone_test_helpers"
11
+ require "encoding_test_cases"
12
+ require 'rails/patch/json/encode'
13
+
14
+ Rails::Patch::Json::Encode.patch_base_classes
15
+
16
+ class TestJSONEncoding < ActiveSupport::TestCase
17
+ include TimeZoneTestHelpers
18
+
19
+ def sorted_json(json)
20
+ if json.start_with?("{") && json.end_with?("}")
21
+ "{" + json[1..-2].split(",").sort.join(",") + "}"
22
+ else
23
+ json
24
+ end
25
+ end
26
+
27
+ JSONTest::EncodingTestCases.constants.each do |class_tests|
28
+ define_method("test_#{class_tests[0..-6].underscore}") do
29
+ begin
30
+ prev = ActiveSupport.use_standard_json_time_format
31
+
32
+ standard_class_tests = /Standard/.match?(class_tests)
33
+
34
+ ActiveSupport.escape_html_entities_in_json = !standard_class_tests
35
+ ActiveSupport.use_standard_json_time_format = standard_class_tests
36
+ JSONTest::EncodingTestCases.const_get(class_tests).each do |pair|
37
+ assert_equal pair.last, sorted_json(ActiveSupport::JSON.encode(pair.first))
38
+ end
39
+ ensure
40
+ ActiveSupport.escape_html_entities_in_json = false
41
+ ActiveSupport.use_standard_json_time_format = prev
42
+ end
43
+ end
44
+ end
45
+
46
+ def test_process_status
47
+ rubinius_skip "https://github.com/rubinius/rubinius/issues/3334"
48
+
49
+ # There doesn't seem to be a good way to get a handle on a Process::Status object without actually
50
+ # creating a child process, hence this to populate $?
51
+ system("not_a_real_program_#{SecureRandom.hex}")
52
+ assert_equal %({"exitstatus":#{$?.exitstatus},"pid":#{$?.pid}}), ActiveSupport::JSON.encode($?)
53
+ end
54
+
55
+ def test_hash_encoding
56
+ assert_equal %({\"a\":\"b\"}), ActiveSupport::JSON.encode(a: :b)
57
+ assert_equal %({\"a\":1}), ActiveSupport::JSON.encode("a" => 1)
58
+ assert_equal %({\"a\":[1,2]}), ActiveSupport::JSON.encode("a" => [1, 2])
59
+ assert_equal %({"1":2}), ActiveSupport::JSON.encode(1 => 2)
60
+
61
+ assert_equal %({\"a\":\"b\",\"c\":\"d\"}), sorted_json(ActiveSupport::JSON.encode(a: :b, c: :d))
62
+ end
63
+
64
+ def test_hash_keys_encoding
65
+ ActiveSupport.escape_html_entities_in_json = true
66
+ assert_equal "{\"\\u003c\\u003e\":\"\\u003c\\u003e\"}", ActiveSupport::JSON.encode("<>" => "<>")
67
+ ensure
68
+ ActiveSupport.escape_html_entities_in_json = false
69
+ end
70
+
71
+ def test_utf8_string_encoded_properly
72
+ result = ActiveSupport::JSON.encode("€2.99")
73
+ assert_equal '"€2.99"', result
74
+ assert_equal(Encoding::UTF_8, result.encoding)
75
+
76
+ result = ActiveSupport::JSON.encode("✎☺")
77
+ assert_equal '"✎☺"', result
78
+ assert_equal(Encoding::UTF_8, result.encoding)
79
+ end
80
+
81
+ def test_non_utf8_string_transcodes
82
+ s = "二".encode("Shift_JIS")
83
+ result = ActiveSupport::JSON.encode(s)
84
+ assert_equal '"二"', result
85
+ assert_equal Encoding::UTF_8, result.encoding
86
+ end
87
+
88
+ def test_wide_utf8_chars
89
+ w = "𠜎"
90
+ result = ActiveSupport::JSON.encode(w)
91
+ assert_equal '"𠜎"', result
92
+ end
93
+
94
+ def test_wide_utf8_roundtrip
95
+ hash = { string: "𐒑" }
96
+ json = ActiveSupport::JSON.encode(hash)
97
+ decoded_hash = ActiveSupport::JSON.decode(json)
98
+ assert_equal "𐒑", decoded_hash["string"]
99
+ end
100
+
101
+ def test_hash_key_identifiers_are_always_quoted
102
+ values = { 0 => 0, 1 => 1, :_ => :_, "$" => "$", "a" => "a", :A => :A, :A0 => :A0, "A0B" => "A0B" }
103
+ assert_equal %w( "$" "A" "A0" "A0B" "_" "a" "0" "1" ).sort, object_keys(ActiveSupport::JSON.encode(values))
104
+ end
105
+
106
+ def test_hash_should_allow_key_filtering_with_only
107
+ assert_equal %({"a":1}), ActiveSupport::JSON.encode({ "a" => 1, :b => 2, :c => 3 }, { only: "a" })
108
+ end
109
+
110
+ def test_hash_should_allow_key_filtering_with_except
111
+ assert_equal %({"b":2}), ActiveSupport::JSON.encode({ "foo" => "bar", :b => 2, :c => 3 }, { except: ["foo", :c] })
112
+ end
113
+
114
+ def test_time_to_json_includes_local_offset
115
+ with_standard_json_time_format(true) do
116
+ with_env_tz "US/Eastern" do
117
+ assert_equal %("2005-02-01T15:15:10.000-05:00"), ActiveSupport::JSON.encode(Time.local(2005, 2, 1, 15, 15, 10))
118
+ end
119
+ end
120
+ end
121
+
122
+ def test_hash_with_time_to_json
123
+ with_standard_json_time_format(false) do
124
+ assert_equal '{"time":"2009/01/01 00:00:00 +0000"}', { time: Time.utc(2009) }.to_json
125
+ end
126
+ end
127
+
128
+ def test_nested_hash_with_float
129
+ assert_nothing_raised do
130
+ hash = {
131
+ "CHI" => {
132
+ display_name: "chicago",
133
+ latitude: 123.234
134
+ }
135
+ }
136
+ ActiveSupport::JSON.encode(hash)
137
+ end
138
+ end
139
+
140
+ def test_hash_like_with_options
141
+ h = JSONTest::Hashlike.new
142
+ json = h.to_json only: [:foo]
143
+
144
+ assert_equal({ "foo" => "hello" }, JSON.parse(json))
145
+ end
146
+
147
+ def test_object_to_json_with_options
148
+ obj = Object.new
149
+ obj.instance_variable_set :@foo, "hello"
150
+ obj.instance_variable_set :@bar, "world"
151
+ json = obj.to_json only: ["foo"]
152
+
153
+ assert_equal({ "foo" => "hello" }, JSON.parse(json))
154
+ end
155
+
156
+ def test_struct_to_json_with_options
157
+ struct = Struct.new(:foo, :bar).new
158
+ struct.foo = "hello"
159
+ struct.bar = "world"
160
+ json = struct.to_json only: [:foo]
161
+
162
+ assert_equal({ "foo" => "hello" }, JSON.parse(json))
163
+ end
164
+
165
+ def test_hash_should_pass_encoding_options_to_children_in_as_json
166
+ person = {
167
+ name: "John",
168
+ address: {
169
+ city: "London",
170
+ country: "UK"
171
+ }
172
+ }
173
+ json = person.as_json only: [:address, :city]
174
+
175
+ assert_equal({ "address" => { "city" => "London" } }, json)
176
+ end
177
+
178
+ def test_hash_should_pass_encoding_options_to_children_in_to_json
179
+ person = {
180
+ name: "John",
181
+ address: {
182
+ city: "London",
183
+ country: "UK"
184
+ }
185
+ }
186
+ json = person.to_json only: [:address, :city]
187
+
188
+ assert_equal(%({"address":{"city":"London"}}), json)
189
+ end
190
+
191
+ def test_array_should_pass_encoding_options_to_children_in_as_json
192
+ people = [
193
+ { name: "John", address: { city: "London", country: "UK" } },
194
+ { name: "Jean", address: { city: "Paris", country: "France" } }
195
+ ]
196
+ json = people.as_json only: [:address, :city]
197
+ expected = [
198
+ { "address" => { "city" => "London" } },
199
+ { "address" => { "city" => "Paris" } }
200
+ ]
201
+
202
+ assert_equal(expected, json)
203
+ end
204
+
205
+ def test_array_should_pass_encoding_options_to_children_in_to_json
206
+ people = [
207
+ { name: "John", address: { city: "London", country: "UK" } },
208
+ { name: "Jean", address: { city: "Paris", country: "France" } }
209
+ ]
210
+ json = people.to_json only: [:address, :city]
211
+
212
+ assert_equal(%([{"address":{"city":"London"}},{"address":{"city":"Paris"}}]), json)
213
+ end
214
+
215
+ People = Class.new(BasicObject) do
216
+ include Enumerable
217
+ def initialize
218
+ @people = [
219
+ { name: "John", address: { city: "London", country: "UK" } },
220
+ { name: "Jean", address: { city: "Paris", country: "France" } }
221
+ ]
222
+ end
223
+ def each(*, &blk)
224
+ @people.each do |p|
225
+ yield p if blk
226
+ p
227
+ end.each
228
+ end
229
+ end
230
+
231
+ def test_enumerable_should_generate_json_with_as_json
232
+ json = People.new.as_json only: [:address, :city]
233
+ expected = [
234
+ { "address" => { "city" => "London" } },
235
+ { "address" => { "city" => "Paris" } }
236
+ ]
237
+
238
+ assert_equal(expected, json)
239
+ end
240
+
241
+ def test_enumerable_should_generate_json_with_to_json
242
+ json = People.new.to_json only: [:address, :city]
243
+ assert_equal(%([{"address":{"city":"London"}},{"address":{"city":"Paris"}}]), json)
244
+ end
245
+
246
+ def test_enumerable_should_pass_encoding_options_to_children_in_as_json
247
+ json = People.new.each.as_json only: [:address, :city]
248
+ expected = [
249
+ { "address" => { "city" => "London" } },
250
+ { "address" => { "city" => "Paris" } }
251
+ ]
252
+
253
+ assert_equal(expected, json)
254
+ end
255
+
256
+ def test_enumerable_should_pass_encoding_options_to_children_in_to_json
257
+ json = People.new.each.to_json only: [:address, :city]
258
+
259
+ assert_equal(%([{"address":{"city":"London"}},{"address":{"city":"Paris"}}]), json)
260
+ end
261
+
262
+ class CustomWithOptions
263
+ attr_accessor :foo, :bar
264
+
265
+ def as_json(options = {})
266
+ options[:only] = %w(foo bar)
267
+ super(options)
268
+ end
269
+ end
270
+
271
+ def test_hash_to_json_should_not_keep_options_around
272
+ f = CustomWithOptions.new
273
+ f.foo = "hello"
274
+ f.bar = "world"
275
+
276
+ hash = { "foo" => f, "other_hash" => { "foo" => "other_foo", "test" => "other_test" } }
277
+ assert_equal({ "foo" => { "foo" => "hello", "bar" => "world" },
278
+ "other_hash" => { "foo" => "other_foo", "test" => "other_test" } }, ActiveSupport::JSON.decode(hash.to_json))
279
+ end
280
+
281
+ def test_array_to_json_should_not_keep_options_around
282
+ f = CustomWithOptions.new
283
+ f.foo = "hello"
284
+ f.bar = "world"
285
+
286
+ array = [f, { "foo" => "other_foo", "test" => "other_test" }]
287
+ assert_equal([{ "foo" => "hello", "bar" => "world" },
288
+ { "foo" => "other_foo", "test" => "other_test" }], ActiveSupport::JSON.decode(array.to_json))
289
+ end
290
+
291
+ class OptionsTest
292
+ def as_json(options = :default)
293
+ options
294
+ end
295
+ end
296
+
297
+ def test_hash_as_json_without_options
298
+ json = { foo: OptionsTest.new }.as_json
299
+ assert_equal({ "foo" => :default }, json)
300
+ end
301
+
302
+ def test_array_as_json_without_options
303
+ json = [ OptionsTest.new ].as_json
304
+ assert_equal([:default], json)
305
+ end
306
+
307
+ def test_struct_encoding
308
+ Struct.new("UserNameAndEmail", :name, :email)
309
+ Struct.new("UserNameAndDate", :name, :date)
310
+ Struct.new("Custom", :name, :sub)
311
+ user_email = Struct::UserNameAndEmail.new "David", "sample@example.com"
312
+ user_birthday = Struct::UserNameAndDate.new "David", Date.new(2010, 01, 01)
313
+ custom = Struct::Custom.new "David", user_birthday
314
+
315
+ json_strings = ""
316
+ json_string_and_date = ""
317
+ json_custom = ""
318
+
319
+ assert_nothing_raised do
320
+ json_strings = user_email.to_json
321
+ json_string_and_date = user_birthday.to_json
322
+ json_custom = custom.to_json
323
+ end
324
+
325
+ assert_equal({ "name" => "David",
326
+ "sub" => {
327
+ "name" => "David",
328
+ "date" => "2010-01-01" } }, ActiveSupport::JSON.decode(json_custom))
329
+
330
+ assert_equal({ "name" => "David", "email" => "sample@example.com" },
331
+ ActiveSupport::JSON.decode(json_strings))
332
+
333
+ assert_equal({ "name" => "David", "date" => "2010-01-01" },
334
+ ActiveSupport::JSON.decode(json_string_and_date))
335
+ end
336
+
337
+ def test_nil_true_and_false_represented_as_themselves
338
+ assert_nil nil.as_json
339
+ assert_equal true, true.as_json
340
+ assert_equal false, false.as_json
341
+ end
342
+
343
+ class HashWithAsJson < Hash
344
+ attr_accessor :as_json_called
345
+
346
+ def initialize(*)
347
+ super
348
+ end
349
+
350
+ def as_json(options = {})
351
+ @as_json_called = true
352
+ super
353
+ end
354
+ end
355
+
356
+ def test_json_gem_dump_by_passing_active_support_encoder
357
+ h = HashWithAsJson.new
358
+ h[:foo] = "hello"
359
+ h[:bar] = "world"
360
+
361
+ assert_equal %({"foo":"hello","bar":"world"}), JSON.dump(h)
362
+ assert_nil h.as_json_called
363
+ end
364
+
365
+ def test_json_gem_generate_by_passing_active_support_encoder
366
+ h = HashWithAsJson.new
367
+ h[:foo] = "hello"
368
+ h[:bar] = "world"
369
+
370
+ assert_equal %({"foo":"hello","bar":"world"}), JSON.generate(h)
371
+ assert_nil h.as_json_called
372
+ end
373
+
374
+ def test_json_gem_pretty_generate_by_passing_active_support_encoder
375
+ h = HashWithAsJson.new
376
+ h[:foo] = "hello"
377
+ h[:bar] = "world"
378
+
379
+ assert_equal <<EXPECTED.chomp, JSON.pretty_generate(h)
380
+ {
381
+ "foo": "hello",
382
+ "bar": "world"
383
+ }
384
+ EXPECTED
385
+ assert_nil h.as_json_called
386
+ end
387
+
388
+ def test_twz_to_json_with_use_standard_json_time_format_config_set_to_false
389
+ with_standard_json_time_format(false) do
390
+ zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"]
391
+ time = ActiveSupport::TimeWithZone.new(Time.utc(2000), zone)
392
+ assert_equal "\"1999/12/31 19:00:00 -0500\"", ActiveSupport::JSON.encode(time)
393
+ end
394
+ end
395
+
396
+ def test_twz_to_json_with_use_standard_json_time_format_config_set_to_true
397
+ with_standard_json_time_format(true) do
398
+ zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"]
399
+ time = ActiveSupport::TimeWithZone.new(Time.utc(2000), zone)
400
+ assert_equal "\"1999-12-31T19:00:00.000-05:00\"", ActiveSupport::JSON.encode(time)
401
+ end
402
+ end
403
+
404
+ def test_twz_to_json_with_custom_time_precision
405
+ with_standard_json_time_format(true) do
406
+ with_time_precision(0) do
407
+ zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"]
408
+ time = ActiveSupport::TimeWithZone.new(Time.utc(2000), zone)
409
+ assert_equal "\"1999-12-31T19:00:00-05:00\"", ActiveSupport::JSON.encode(time)
410
+ end
411
+ end
412
+ end
413
+
414
+ def test_time_to_json_with_custom_time_precision
415
+ with_standard_json_time_format(true) do
416
+ with_time_precision(0) do
417
+ assert_equal "\"2000-01-01T00:00:00Z\"", ActiveSupport::JSON.encode(Time.utc(2000))
418
+ end
419
+ end
420
+ end
421
+
422
+ def test_datetime_to_json_with_custom_time_precision
423
+ with_standard_json_time_format(true) do
424
+ with_time_precision(0) do
425
+ assert_equal "\"2000-01-01T00:00:00+00:00\"", ActiveSupport::JSON.encode(DateTime.new(2000))
426
+ end
427
+ end
428
+ end
429
+
430
+ def test_twz_to_json_when_wrapping_a_date_time
431
+ zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"]
432
+ time = ActiveSupport::TimeWithZone.new(DateTime.new(2000), zone)
433
+ assert_equal '"1999-12-31T19:00:00.000-05:00"', ActiveSupport::JSON.encode(time)
434
+ end
435
+
436
+ def test_exception_to_json
437
+ exception = Exception.new("foo")
438
+ assert_equal '"foo"', ActiveSupport::JSON.encode(exception)
439
+ end
440
+
441
+ class InfiniteNumber
442
+ def as_json(options = nil)
443
+ { "number" => Float::INFINITY }
444
+ end
445
+ end
446
+
447
+ def test_to_json_works_when_as_json_returns_infinite_number
448
+ assert_equal '{"number":null}', InfiniteNumber.new.to_json
449
+ end
450
+
451
+ class NaNNumber
452
+ def as_json(options = nil)
453
+ { "number" => Float::NAN }
454
+ end
455
+ end
456
+
457
+ def test_to_json_works_when_as_json_returns_NaN_number
458
+ assert_equal '{"number":null}', NaNNumber.new.to_json
459
+ end
460
+
461
+ def test_to_json_works_on_io_objects
462
+ assert_equal STDOUT.to_s.to_json, STDOUT.to_json
463
+ end
464
+
465
+ private
466
+
467
+ def object_keys(json_object)
468
+ json_object[1..-2].scan(/([^{}:,\s]+):/).flatten.sort
469
+ end
470
+
471
+ def with_standard_json_time_format(boolean = true)
472
+ old, ActiveSupport.use_standard_json_time_format = ActiveSupport.use_standard_json_time_format, boolean
473
+ yield
474
+ ensure
475
+ ActiveSupport.use_standard_json_time_format = old
476
+ end
477
+
478
+ def with_time_precision(value)
479
+ old_value = ActiveSupport::JSON::Encoding.time_precision
480
+ ActiveSupport::JSON::Encoding.time_precision = value
481
+ yield
482
+ ensure
483
+ ActiveSupport::JSON::Encoding.time_precision = old_value
484
+ end
485
+ end
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bigdecimal"
4
+ require "date"
5
+ require "time"
6
+ require "pathname"
7
+ require "uri"
8
+
9
+ module JSONTest
10
+ class Foo
11
+ def initialize(a, b)
12
+ @a, @b = a, b
13
+ end
14
+ end
15
+
16
+ class Hashlike
17
+ def to_hash
18
+ { foo: "hello", bar: "world" }
19
+ end
20
+ end
21
+
22
+ class Custom
23
+ def initialize(serialized)
24
+ @serialized = serialized
25
+ end
26
+
27
+ def as_json(options = nil)
28
+ @serialized
29
+ end
30
+ end
31
+
32
+ MyStruct = Struct.new(:name, :value) do
33
+ def initialize(*)
34
+ @unused = "unused instance variable"
35
+ super
36
+ end
37
+ end
38
+
39
+ module EncodingTestCases
40
+ TrueTests = [[ true, %(true) ]]
41
+ FalseTests = [[ false, %(false) ]]
42
+ NilTests = [[ nil, %(null) ]]
43
+ NumericTests = [[ 1, %(1) ],
44
+ [ 2.5, %(2.5) ],
45
+ [ 0.0 / 0.0, %(null) ],
46
+ [ 1.0 / 0.0, %(null) ],
47
+ [ -1.0 / 0.0, %(null) ],
48
+ [ BigDecimal("0.0") / BigDecimal("0.0"), %(null) ],
49
+ [ BigDecimal("2.5"), %("#{BigDecimal('2.5')}") ]]
50
+
51
+ StringTests = [[ "this is the <string>", %("this is the \\u003cstring\\u003e")],
52
+ [ 'a "string" with quotes & an ampersand', %("a \\"string\\" with quotes \\u0026 an ampersand") ],
53
+ [ "http://test.host/posts/1", %("http://test.host/posts/1")],
54
+ [ "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",
55
+ %("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") ]]
56
+
57
+ ArrayTests = [[ ["a", "b", "c"], %([\"a\",\"b\",\"c\"]) ],
58
+ [ [1, "a", :b, nil, false], %([1,\"a\",\"b\",null,false]) ]]
59
+
60
+ HashTests = [[ { foo: "bar" }, %({\"foo\":\"bar\"}) ],
61
+ [ { 1 => 1, 2 => "a", 3 => :b, 4 => nil, 5 => false }, %({\"1\":1,\"2\":\"a\",\"3\":\"b\",\"4\":null,\"5\":false}) ]]
62
+
63
+ RangeTests = [[ 1..2, %("1..2")],
64
+ [ 1...2, %("1...2")],
65
+ [ 1.5..2.5, %("1.5..2.5")]]
66
+
67
+ SymbolTests = [[ :a, %("a") ],
68
+ [ :this, %("this") ],
69
+ [ :"a b", %("a b") ]]
70
+
71
+ ObjectTests = [[ Foo.new(1, 2), %({\"a\":1,\"b\":2}) ]]
72
+ HashlikeTests = [[ Hashlike.new, %({\"bar\":\"world\",\"foo\":\"hello\"}) ]]
73
+ StructTests = [[ MyStruct.new(:foo, "bar"), %({\"name\":\"foo\",\"value\":\"bar\"}) ],
74
+ [ MyStruct.new(nil, nil), %({\"name\":null,\"value\":null}) ]]
75
+ CustomTests = [[ Custom.new("custom"), '"custom"' ],
76
+ [ Custom.new(nil), "null" ],
77
+ [ Custom.new(:a), '"a"' ],
78
+ [ Custom.new([ :foo, "bar" ]), '["foo","bar"]' ],
79
+ [ Custom.new(foo: "hello", bar: "world"), '{"bar":"world","foo":"hello"}' ],
80
+ [ Custom.new(Hashlike.new), '{"bar":"world","foo":"hello"}' ],
81
+ [ Custom.new(Custom.new(Custom.new(:a))), '"a"' ]]
82
+
83
+ RegexpTests = [[ /^a/, '"(?-mix:^a)"' ], [/^\w{1,2}[a-z]+/ix, '"(?ix-m:^\\\\w{1,2}[a-z]+)"']]
84
+
85
+ URITests = [[ URI.parse("http://example.com"), %("http://example.com") ]]
86
+
87
+ PathnameTests = [[ Pathname.new("lib/index.rb"), %("lib/index.rb") ]]
88
+
89
+ DateTests = [[ Date.new(2005, 2, 1), %("2005/02/01") ]]
90
+ TimeTests = [[ Time.utc(2005, 2, 1, 15, 15, 10), %("2005/02/01 15:15:10 +0000") ]]
91
+ DateTimeTests = [[ DateTime.civil(2005, 2, 1, 15, 15, 10), %("2005/02/01 15:15:10 +0000") ]]
92
+
93
+ StandardDateTests = [[ Date.new(2005, 2, 1), %("2005-02-01") ]]
94
+ StandardTimeTests = [[ Time.utc(2005, 2, 1, 15, 15, 10), %("2005-02-01T15:15:10.000Z") ]]
95
+ StandardDateTimeTests = [[ DateTime.civil(2005, 2, 1, 15, 15, 10), %("2005-02-01T15:15:10.000+00:00") ]]
96
+ StandardStringTests = [[ "this is the <string>", %("this is the <string>")]]
97
+ end
98
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TimeZoneTestHelpers
4
+ def with_tz_default(tz = nil)
5
+ old_tz = Time.zone
6
+ Time.zone = tz
7
+ yield
8
+ ensure
9
+ Time.zone = old_tz
10
+ end
11
+
12
+ def with_env_tz(new_tz = "US/Eastern")
13
+ old_tz, ENV["TZ"] = ENV["TZ"], new_tz
14
+ yield
15
+ ensure
16
+ old_tz ? ENV["TZ"] = old_tz : ENV.delete("TZ")
17
+ end
18
+
19
+ def with_preserve_timezone(value)
20
+ old_preserve_tz = ActiveSupport.to_time_preserves_timezone
21
+ ActiveSupport.to_time_preserves_timezone = value
22
+ yield
23
+ ensure
24
+ ActiveSupport.to_time_preserves_timezone = old_preserve_tz
25
+ end
26
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rails-patch-json-encode
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - lulalala
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2014-08-22 00:00:00.000000000 Z
12
+ date: 2018-01-05 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bundler
@@ -17,16 +17,58 @@ dependencies:
17
17
  requirements:
18
18
  - - "~>"
19
19
  - !ruby/object:Gem::Version
20
- version: '1.3'
20
+ version: 1.16.0
21
21
  type: :development
22
22
  prerelease: false
23
23
  version_requirements: !ruby/object:Gem::Requirement
24
24
  requirements:
25
25
  - - "~>"
26
26
  - !ruby/object:Gem::Version
27
- version: '1.3'
27
+ version: 1.16.0
28
28
  - !ruby/object:Gem::Dependency
29
29
  name: rake
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - "~>"
33
+ - !ruby/object:Gem::Version
34
+ version: 12.3.0
35
+ type: :development
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - "~>"
40
+ - !ruby/object:Gem::Version
41
+ version: 12.3.0
42
+ - !ruby/object:Gem::Dependency
43
+ name: activesupport
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - "~>"
47
+ - !ruby/object:Gem::Version
48
+ version: 5.1.4
49
+ type: :development
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - "~>"
54
+ - !ruby/object:Gem::Version
55
+ version: 5.1.4
56
+ - !ruby/object:Gem::Dependency
57
+ name: yajl-ruby
58
+ requirement: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
63
+ type: :development
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ - !ruby/object:Gem::Dependency
71
+ name: byebug
30
72
  requirement: !ruby/object:Gem::Requirement
31
73
  requirements:
32
74
  - - ">="
@@ -71,9 +113,14 @@ files:
71
113
  - LICENSE.txt
72
114
  - README.md
73
115
  - Rakefile
116
+ - benchmark/base_classes.rb
74
117
  - lib/rails/patch/json/encode.rb
75
118
  - lib/rails/patch/json/encode/version.rb
76
119
  - rails-patch-json-encode.gemspec
120
+ - test/abstract_unit.rb
121
+ - test/encoding_test.rb
122
+ - test/encoding_test_cases.rb
123
+ - test/time_zone_test_helpers.rb
77
124
  homepage: https://github.com/GoodLife/rails-patch-json-encode
78
125
  licenses:
79
126
  - MIT
@@ -94,8 +141,12 @@ required_rubygems_version: !ruby/object:Gem::Requirement
94
141
  version: '0'
95
142
  requirements: []
96
143
  rubyforge_project:
97
- rubygems_version: 2.2.2
144
+ rubygems_version: 2.6.13
98
145
  signing_key:
99
146
  specification_version: 4
100
147
  summary: A monkey patch to speed up Rails' JSON generation time.
101
- test_files: []
148
+ test_files:
149
+ - test/abstract_unit.rb
150
+ - test/encoding_test.rb
151
+ - test/encoding_test_cases.rb
152
+ - test/time_zone_test_helpers.rb