yagni_json_encoder 0.0.2
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.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +66 -0
- data/lib/activesupport/json_encoder.rb +10 -0
- data/lib/yagni_json_encoder.rb +17 -0
- data/test/encoding_test.rb +453 -0
- data/test/encoding_test_cases.rb +84 -0
- data/test/test_helper.rb +17 -0
- data/test/time_zone_test_helpers.rb +16 -0
- metadata +99 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 3461fb18bd4f2d2a69eaaeb655af5ad290fe3e60
|
4
|
+
data.tar.gz: 7b79546f667857112a48fa2b88b41d985d2cc73b
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 713ac3358afa5e9bb1bf2b4bda4e4265efc0bda80a549459623d804076295493eed8cf424cd86fb6b0b3f2beb471914e7e56178692ac10c940db99ef067e57e1
|
7
|
+
data.tar.gz: 311736b35fdac8db60fab3db9dc3f22a425e8e395a3662dd8f1bf4716542a62d89f4c3da24c85d071f02e2a91bafec6a770956bab82ce3240fdc1894393d236b
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2016 Ian Clay Ker-Seymer
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
# YagniJsonEncoder
|
2
|
+
|
3
|
+
[](https://travis-ci.org/ianks/yagni_json_encoder)
|
4
|
+
|
5
|
+
This gem overrides the [default ActiveSupport JSON
|
6
|
+
encoder](https://github.com/rails/rails/blob/master/activesupport/lib/active_support/json/encoding.rb)
|
7
|
+
with a faster encoder which makes a few assumptions about your app.
|
8
|
+
|
9
|
+
1. You do not need the escape HTML entities in your JSON
|
10
|
+
2. You do not need any special escaping besides that provided by the JSON
|
11
|
+
standard.
|
12
|
+
|
13
|
+
Under the hood, this app removes Rails' special JSON escaping, and relies on
|
14
|
+
[Oj](https://github.com/ohler55/oj) to do the heavy lifting of JSON encoding.
|
15
|
+
|
16
|
+
|
17
|
+
## Why?
|
18
|
+
|
19
|
+
By default, Rails plays it safe and escapes `\u2028` and `\u2029`. In order to
|
20
|
+
do this, a `#gsub` call (O(n) time complexity) is required on every string it
|
21
|
+
encounters. I do not interface with old browsers, so escaping these characters
|
22
|
+
is useless to me. Instead, we dont escape these characters, and let Oj do all
|
23
|
+
the heavy JSON encoding work.
|
24
|
+
|
25
|
+
|
26
|
+
## Perfomance (show me the money!)
|
27
|
+
|
28
|
+
In a real Rails app, I benchmarked `Tips.all.to_json` with the different
|
29
|
+
encoders. On average, YagniJsonEncoder is ~2x as fast. However, you could
|
30
|
+
see bigger gains if you models are text-heavy.
|
31
|
+
|
32
|
+
```
|
33
|
+
Calculating -------------------------------------
|
34
|
+
YagniJsonEncoder 10.000 i/100ms
|
35
|
+
JSONGemEncoder 5.000 i/100ms
|
36
|
+
-------------------------------------------------
|
37
|
+
YagniJsonEncoder 105.536 (± 6.6%) i/s - 530.000
|
38
|
+
JSONGemEncoder 50.605 (± 4.0%) i/s - 255.000
|
39
|
+
|
40
|
+
Comparison:
|
41
|
+
YagniJsonEncoder: 105.5 i/s
|
42
|
+
JSONGemEncoder: 50.6 i/s - 2.09x slower
|
43
|
+
```
|
44
|
+
|
45
|
+
|
46
|
+
## Installation
|
47
|
+
|
48
|
+
Just add this line to your application's Gemfile:
|
49
|
+
|
50
|
+
```ruby
|
51
|
+
gem 'yagni_json_encoder'
|
52
|
+
```
|
53
|
+
|
54
|
+
And then execute:
|
55
|
+
|
56
|
+
$ bundle
|
57
|
+
|
58
|
+
Or install it yourself as:
|
59
|
+
|
60
|
+
$ gem install yagni_json_encoder
|
61
|
+
|
62
|
+
|
63
|
+
## Contributing
|
64
|
+
|
65
|
+
Bug reports and pull requests are welcome on GitHub at
|
66
|
+
https://github.com/ianks/yagni_json_encoder.
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'oj'
|
2
|
+
|
3
|
+
class YagniJsonEncoder
|
4
|
+
attr_reader :options
|
5
|
+
|
6
|
+
def initialize(options = nil)
|
7
|
+
@options = options || {}
|
8
|
+
end
|
9
|
+
|
10
|
+
def encode(value)
|
11
|
+
Oj.dump value.as_json(options.dup),
|
12
|
+
mode: :compat,
|
13
|
+
quirks_mode: true
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
require 'activesupport/json_encoder'
|
@@ -0,0 +1,453 @@
|
|
1
|
+
require 'active_support/json'
|
2
|
+
require 'active_support/time'
|
3
|
+
require 'encoding_test_cases'
|
4
|
+
require 'securerandom'
|
5
|
+
require 'test_helper'
|
6
|
+
require 'time_zone_test_helpers'
|
7
|
+
|
8
|
+
require 'yagni_json_encoder'
|
9
|
+
|
10
|
+
class TestJSONEncoding < ActiveSupport::TestCase
|
11
|
+
include TimeZoneTestHelpers
|
12
|
+
|
13
|
+
def sorted_json(json)
|
14
|
+
return json unless json =~ /^\{.*\}$/
|
15
|
+
'{' + json[1..-2].split(',').sort.join(',') + '}'
|
16
|
+
end
|
17
|
+
|
18
|
+
JSONTest::EncodingTestCases.constants.each do |class_tests|
|
19
|
+
define_method("test_#{class_tests[0..-6].underscore}") do
|
20
|
+
begin
|
21
|
+
prev = ActiveSupport.use_standard_json_time_format
|
22
|
+
|
23
|
+
ActiveSupport.use_standard_json_time_format = class_tests =~ /^Standard/
|
24
|
+
JSONTest::EncodingTestCases.const_get(class_tests).each do |pair|
|
25
|
+
assert_equal pair.last, sorted_json(ActiveSupport::JSON.encode(pair.first))
|
26
|
+
end
|
27
|
+
ensure
|
28
|
+
ActiveSupport.use_standard_json_time_format = prev
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def test_yagni_json_encoder_loaded
|
34
|
+
assert_equal ActiveSupport.json_encoder, YagniJsonEncoder
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_process_status
|
38
|
+
message = "https://github.com/rubinius/rubinius/issues/3334"
|
39
|
+
skip message if RUBY_ENGINE == 'rbx'
|
40
|
+
|
41
|
+
# There doesn't seem to be a good way to get a handle on a Process::Status object without actually
|
42
|
+
# creating a child process, hence this to populate $?
|
43
|
+
system("not_a_real_program_#{SecureRandom.hex}")
|
44
|
+
assert_equal %({"exitstatus":#{$?.exitstatus},"pid":#{$?.pid}}), ActiveSupport::JSON.encode($?)
|
45
|
+
end
|
46
|
+
|
47
|
+
def test_hash_encoding
|
48
|
+
assert_equal %({\"a\":\"b\"}), ActiveSupport::JSON.encode(:a => :b)
|
49
|
+
assert_equal %({\"a\":1}), ActiveSupport::JSON.encode('a' => 1)
|
50
|
+
assert_equal %({\"a\":[1,2]}), ActiveSupport::JSON.encode('a' => [1,2])
|
51
|
+
assert_equal %({"1":2}), ActiveSupport::JSON.encode(1 => 2)
|
52
|
+
|
53
|
+
assert_equal %({\"a\":\"b\",\"c\":\"d\"}), sorted_json(ActiveSupport::JSON.encode(:a => :b, :c => :d))
|
54
|
+
end
|
55
|
+
|
56
|
+
def test_escape_html_entities_in_json_fails
|
57
|
+
assert_raise do
|
58
|
+
ActiveSupport.escape_html_entities_in_json = true
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def test_hash_keys_encoding
|
63
|
+
assert_equal "{\"<>\":\"<>\"}", ActiveSupport::JSON.encode("<>" => "<>")
|
64
|
+
end
|
65
|
+
|
66
|
+
def test_utf8_string_encoded_properly
|
67
|
+
result = ActiveSupport::JSON.encode('€2.99')
|
68
|
+
assert_equal '"€2.99"', result
|
69
|
+
assert_equal(Encoding::UTF_8, result.encoding)
|
70
|
+
|
71
|
+
result = ActiveSupport::JSON.encode('✎☺')
|
72
|
+
assert_equal '"✎☺"', result
|
73
|
+
assert_equal(Encoding::UTF_8, result.encoding)
|
74
|
+
end
|
75
|
+
|
76
|
+
def test_non_utf8_string_transcodes
|
77
|
+
skip
|
78
|
+
s = '二'.encode('Shift_JIS')
|
79
|
+
result = ActiveSupport::JSON.encode(s)
|
80
|
+
assert_equal '"二"', result
|
81
|
+
assert_equal Encoding::UTF_8, result.encoding
|
82
|
+
end
|
83
|
+
|
84
|
+
def test_wide_utf8_chars
|
85
|
+
w = '𠜎'
|
86
|
+
result = ActiveSupport::JSON.encode(w)
|
87
|
+
assert_equal '"𠜎"', result
|
88
|
+
end
|
89
|
+
|
90
|
+
def test_wide_utf8_roundtrip
|
91
|
+
hash = { string: "𐒑" }
|
92
|
+
json = ActiveSupport::JSON.encode(hash)
|
93
|
+
decoded_hash = ActiveSupport::JSON.decode(json)
|
94
|
+
assert_equal "𐒑", decoded_hash['string']
|
95
|
+
end
|
96
|
+
|
97
|
+
def test_hash_key_identifiers_are_always_quoted
|
98
|
+
values = {0 => 0, 1 => 1, :_ => :_, "$" => "$", "a" => "a", :A => :A, :A0 => :A0, "A0B" => "A0B"}
|
99
|
+
assert_equal %w( "$" "A" "A0" "A0B" "_" "a" "0" "1" ).sort, object_keys(ActiveSupport::JSON.encode(values))
|
100
|
+
end
|
101
|
+
|
102
|
+
def test_hash_should_allow_key_filtering_with_only
|
103
|
+
assert_equal %({"a":1}), ActiveSupport::JSON.encode({'a' => 1, :b => 2, :c => 3}, :only => 'a')
|
104
|
+
end
|
105
|
+
|
106
|
+
def test_hash_should_allow_key_filtering_with_except
|
107
|
+
assert_equal %({"b":2}), ActiveSupport::JSON.encode({'foo' => 'bar', :b => 2, :c => 3}, :except => ['foo', :c])
|
108
|
+
end
|
109
|
+
|
110
|
+
def test_time_to_json_includes_local_offset
|
111
|
+
with_standard_json_time_format(true) do
|
112
|
+
with_env_tz 'US/Eastern' do
|
113
|
+
assert_equal %("2005-02-01T15:15:10.000-05:00"), ActiveSupport::JSON.encode(Time.local(2005,2,1,15,15,10))
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def test_hash_with_time_to_json
|
119
|
+
with_standard_json_time_format(false) do
|
120
|
+
assert_equal '{"time":"2009/01/01 00:00:00 +0000"}', { :time => Time.utc(2009) }.to_json
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def test_nested_hash_with_float
|
125
|
+
assert_nothing_raised do
|
126
|
+
hash = {
|
127
|
+
"CHI" => {
|
128
|
+
:display_name => "chicago",
|
129
|
+
:latitude => 123.234
|
130
|
+
}
|
131
|
+
}
|
132
|
+
ActiveSupport::JSON.encode(hash)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def test_hash_like_with_options
|
137
|
+
h = JSONTest::Hashlike.new
|
138
|
+
json = h.to_json :only => [:foo]
|
139
|
+
|
140
|
+
assert_equal({"foo"=>"hello"}, JSON.parse(json))
|
141
|
+
end
|
142
|
+
|
143
|
+
def test_object_to_json_with_options
|
144
|
+
obj = Object.new
|
145
|
+
obj.instance_variable_set :@foo, "hello"
|
146
|
+
obj.instance_variable_set :@bar, "world"
|
147
|
+
json = obj.to_json :only => ["foo"]
|
148
|
+
|
149
|
+
assert_equal({"foo"=>"hello"}, JSON.parse(json))
|
150
|
+
end
|
151
|
+
|
152
|
+
def test_struct_to_json_with_options
|
153
|
+
struct = Struct.new(:foo, :bar).new
|
154
|
+
struct.foo = "hello"
|
155
|
+
struct.bar = "world"
|
156
|
+
json = struct.to_json :only => [:foo]
|
157
|
+
|
158
|
+
assert_equal({"foo"=>"hello"}, JSON.parse(json))
|
159
|
+
end
|
160
|
+
|
161
|
+
def test_hash_should_pass_encoding_options_to_children_in_as_json
|
162
|
+
person = {
|
163
|
+
:name => 'John',
|
164
|
+
:address => {
|
165
|
+
:city => 'London',
|
166
|
+
:country => 'UK'
|
167
|
+
}
|
168
|
+
}
|
169
|
+
json = person.as_json :only => [:address, :city]
|
170
|
+
|
171
|
+
assert_equal({ 'address' => { 'city' => 'London' }}, json)
|
172
|
+
end
|
173
|
+
|
174
|
+
def test_hash_should_pass_encoding_options_to_children_in_to_json
|
175
|
+
person = {
|
176
|
+
:name => 'John',
|
177
|
+
:address => {
|
178
|
+
:city => 'London',
|
179
|
+
:country => 'UK'
|
180
|
+
}
|
181
|
+
}
|
182
|
+
json = person.to_json :only => [:address, :city]
|
183
|
+
|
184
|
+
assert_equal(%({"address":{"city":"London"}}), json)
|
185
|
+
end
|
186
|
+
|
187
|
+
def test_array_should_pass_encoding_options_to_children_in_as_json
|
188
|
+
people = [
|
189
|
+
{ :name => 'John', :address => { :city => 'London', :country => 'UK' }},
|
190
|
+
{ :name => 'Jean', :address => { :city => 'Paris' , :country => 'France' }}
|
191
|
+
]
|
192
|
+
json = people.as_json :only => [:address, :city]
|
193
|
+
expected = [
|
194
|
+
{ 'address' => { 'city' => 'London' }},
|
195
|
+
{ 'address' => { 'city' => 'Paris' }}
|
196
|
+
]
|
197
|
+
|
198
|
+
assert_equal(expected, json)
|
199
|
+
end
|
200
|
+
|
201
|
+
def test_array_should_pass_encoding_options_to_children_in_to_json
|
202
|
+
people = [
|
203
|
+
{ :name => 'John', :address => { :city => 'London', :country => 'UK' }},
|
204
|
+
{ :name => 'Jean', :address => { :city => 'Paris' , :country => 'France' }}
|
205
|
+
]
|
206
|
+
json = people.to_json :only => [:address, :city]
|
207
|
+
|
208
|
+
assert_equal(%([{"address":{"city":"London"}},{"address":{"city":"Paris"}}]), json)
|
209
|
+
end
|
210
|
+
|
211
|
+
People = Class.new(BasicObject) do
|
212
|
+
include Enumerable
|
213
|
+
def initialize()
|
214
|
+
@people = [
|
215
|
+
{ :name => 'John', :address => { :city => 'London', :country => 'UK' }},
|
216
|
+
{ :name => 'Jean', :address => { :city => 'Paris' , :country => 'France' }}
|
217
|
+
]
|
218
|
+
end
|
219
|
+
def each(*, &blk)
|
220
|
+
@people.each do |p|
|
221
|
+
yield p if blk
|
222
|
+
p
|
223
|
+
end.each
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
def test_enumerable_should_generate_json_with_as_json
|
228
|
+
json = People.new.as_json :only => [:address, :city]
|
229
|
+
expected = [
|
230
|
+
{ 'address' => { 'city' => 'London' }},
|
231
|
+
{ 'address' => { 'city' => 'Paris' }}
|
232
|
+
]
|
233
|
+
|
234
|
+
assert_equal(expected, json)
|
235
|
+
end
|
236
|
+
|
237
|
+
def test_enumerable_should_generate_json_with_to_json
|
238
|
+
json = People.new.to_json :only => [:address, :city]
|
239
|
+
assert_equal(%([{"address":{"city":"London"}},{"address":{"city":"Paris"}}]), json)
|
240
|
+
end
|
241
|
+
|
242
|
+
def test_enumerable_should_pass_encoding_options_to_children_in_as_json
|
243
|
+
json = People.new.each.as_json :only => [:address, :city]
|
244
|
+
expected = [
|
245
|
+
{ 'address' => { 'city' => 'London' }},
|
246
|
+
{ 'address' => { 'city' => 'Paris' }}
|
247
|
+
]
|
248
|
+
|
249
|
+
assert_equal(expected, json)
|
250
|
+
end
|
251
|
+
|
252
|
+
def test_enumerable_should_pass_encoding_options_to_children_in_to_json
|
253
|
+
json = People.new.each.to_json :only => [:address, :city]
|
254
|
+
|
255
|
+
assert_equal(%([{"address":{"city":"London"}},{"address":{"city":"Paris"}}]), json)
|
256
|
+
end
|
257
|
+
|
258
|
+
class CustomWithOptions
|
259
|
+
attr_accessor :foo, :bar
|
260
|
+
|
261
|
+
def as_json(options={})
|
262
|
+
options[:only] = %w(foo bar)
|
263
|
+
super(options)
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
def test_hash_to_json_should_not_keep_options_around
|
268
|
+
f = CustomWithOptions.new
|
269
|
+
f.foo = "hello"
|
270
|
+
f.bar = "world"
|
271
|
+
|
272
|
+
hash = {"foo" => f, "other_hash" => {"foo" => "other_foo", "test" => "other_test"}}
|
273
|
+
assert_equal({"foo"=>{"foo"=>"hello","bar"=>"world"},
|
274
|
+
"other_hash" => {"foo"=>"other_foo","test"=>"other_test"}}, ActiveSupport::JSON.decode(hash.to_json))
|
275
|
+
end
|
276
|
+
|
277
|
+
def test_array_to_json_should_not_keep_options_around
|
278
|
+
f = CustomWithOptions.new
|
279
|
+
f.foo = "hello"
|
280
|
+
f.bar = "world"
|
281
|
+
|
282
|
+
array = [f, {"foo" => "other_foo", "test" => "other_test"}]
|
283
|
+
assert_equal([{"foo"=>"hello","bar"=>"world"},
|
284
|
+
{"foo"=>"other_foo","test"=>"other_test"}], ActiveSupport::JSON.decode(array.to_json))
|
285
|
+
end
|
286
|
+
|
287
|
+
class OptionsTest
|
288
|
+
def as_json(options = :default)
|
289
|
+
options
|
290
|
+
end
|
291
|
+
end
|
292
|
+
|
293
|
+
def test_hash_as_json_without_options
|
294
|
+
json = { foo: OptionsTest.new }.as_json
|
295
|
+
assert_equal({"foo" => :default}, json)
|
296
|
+
end
|
297
|
+
|
298
|
+
def test_array_as_json_without_options
|
299
|
+
json = [ OptionsTest.new ].as_json
|
300
|
+
assert_equal([:default], json)
|
301
|
+
end
|
302
|
+
|
303
|
+
def test_struct_encoding
|
304
|
+
Struct.new('UserNameAndEmail', :name, :email)
|
305
|
+
Struct.new('UserNameAndDate', :name, :date)
|
306
|
+
Struct.new('Custom', :name, :sub)
|
307
|
+
user_email = Struct::UserNameAndEmail.new 'David', 'sample@example.com'
|
308
|
+
user_birthday = Struct::UserNameAndDate.new 'David', Date.new(2010, 01, 01)
|
309
|
+
custom = Struct::Custom.new 'David', user_birthday
|
310
|
+
|
311
|
+
|
312
|
+
json_strings = ""
|
313
|
+
json_string_and_date = ""
|
314
|
+
json_custom = ""
|
315
|
+
|
316
|
+
assert_nothing_raised do
|
317
|
+
json_strings = user_email.to_json
|
318
|
+
json_string_and_date = user_birthday.to_json
|
319
|
+
json_custom = custom.to_json
|
320
|
+
end
|
321
|
+
|
322
|
+
assert_equal({"name" => "David",
|
323
|
+
"sub" => {
|
324
|
+
"name" => "David",
|
325
|
+
"date" => "2010-01-01" }}, ActiveSupport::JSON.decode(json_custom))
|
326
|
+
|
327
|
+
assert_equal({"name" => "David", "email" => "sample@example.com"},
|
328
|
+
ActiveSupport::JSON.decode(json_strings))
|
329
|
+
|
330
|
+
assert_equal({"name" => "David", "date" => "2010-01-01"},
|
331
|
+
ActiveSupport::JSON.decode(json_string_and_date))
|
332
|
+
end
|
333
|
+
|
334
|
+
def test_nil_true_and_false_represented_as_themselves
|
335
|
+
assert_equal nil, nil.as_json
|
336
|
+
assert_equal true, true.as_json
|
337
|
+
assert_equal false, false.as_json
|
338
|
+
end
|
339
|
+
|
340
|
+
class HashWithAsJson < Hash
|
341
|
+
attr_accessor :as_json_called
|
342
|
+
|
343
|
+
def initialize(*)
|
344
|
+
super
|
345
|
+
end
|
346
|
+
|
347
|
+
def as_json(options={})
|
348
|
+
@as_json_called = true
|
349
|
+
super
|
350
|
+
end
|
351
|
+
end
|
352
|
+
|
353
|
+
def test_json_gem_dump_by_passing_active_support_encoder
|
354
|
+
h = HashWithAsJson.new
|
355
|
+
h[:foo] = "hello"
|
356
|
+
h[:bar] = "world"
|
357
|
+
|
358
|
+
assert_equal %({"foo":"hello","bar":"world"}), JSON.dump(h)
|
359
|
+
assert_nil h.as_json_called
|
360
|
+
end
|
361
|
+
|
362
|
+
def test_json_gem_generate_by_passing_active_support_encoder
|
363
|
+
h = HashWithAsJson.new
|
364
|
+
h[:foo] = "hello"
|
365
|
+
h[:bar] = "world"
|
366
|
+
|
367
|
+
assert_equal %({"foo":"hello","bar":"world"}), JSON.generate(h)
|
368
|
+
assert_nil h.as_json_called
|
369
|
+
end
|
370
|
+
|
371
|
+
def test_json_gem_pretty_generate_by_passing_active_support_encoder
|
372
|
+
h = HashWithAsJson.new
|
373
|
+
h[:foo] = "hello"
|
374
|
+
h[:bar] = "world"
|
375
|
+
|
376
|
+
assert_equal <<EXPECTED.chomp, JSON.pretty_generate(h)
|
377
|
+
{
|
378
|
+
"foo": "hello",
|
379
|
+
"bar": "world"
|
380
|
+
}
|
381
|
+
EXPECTED
|
382
|
+
assert_nil h.as_json_called
|
383
|
+
end
|
384
|
+
|
385
|
+
def test_twz_to_json_with_use_standard_json_time_format_config_set_to_false
|
386
|
+
with_standard_json_time_format(false) do
|
387
|
+
zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)']
|
388
|
+
time = ActiveSupport::TimeWithZone.new(Time.utc(2000), zone)
|
389
|
+
assert_equal "\"1999/12/31 19:00:00 -0500\"", ActiveSupport::JSON.encode(time)
|
390
|
+
end
|
391
|
+
end
|
392
|
+
|
393
|
+
def test_twz_to_json_with_use_standard_json_time_format_config_set_to_true
|
394
|
+
with_standard_json_time_format(true) do
|
395
|
+
zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)']
|
396
|
+
time = ActiveSupport::TimeWithZone.new(Time.utc(2000), zone)
|
397
|
+
assert_equal "\"1999-12-31T19:00:00.000-05:00\"", ActiveSupport::JSON.encode(time)
|
398
|
+
end
|
399
|
+
end
|
400
|
+
|
401
|
+
def test_twz_to_json_with_custom_time_precision
|
402
|
+
with_standard_json_time_format(true) do
|
403
|
+
with_time_precision(0) do
|
404
|
+
zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)']
|
405
|
+
time = ActiveSupport::TimeWithZone.new(Time.utc(2000), zone)
|
406
|
+
assert_equal "\"1999-12-31T19:00:00-05:00\"", ActiveSupport::JSON.encode(time)
|
407
|
+
end
|
408
|
+
end
|
409
|
+
end
|
410
|
+
|
411
|
+
def test_time_to_json_with_custom_time_precision
|
412
|
+
with_standard_json_time_format(true) do
|
413
|
+
with_time_precision(0) do
|
414
|
+
assert_equal "\"2000-01-01T00:00:00Z\"", ActiveSupport::JSON.encode(Time.utc(2000))
|
415
|
+
end
|
416
|
+
end
|
417
|
+
end
|
418
|
+
|
419
|
+
def test_datetime_to_json_with_custom_time_precision
|
420
|
+
with_standard_json_time_format(true) do
|
421
|
+
with_time_precision(0) do
|
422
|
+
assert_equal "\"2000-01-01T00:00:00+00:00\"", ActiveSupport::JSON.encode(DateTime.new(2000))
|
423
|
+
end
|
424
|
+
end
|
425
|
+
end
|
426
|
+
|
427
|
+
def test_twz_to_json_when_wrapping_a_date_time
|
428
|
+
zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)']
|
429
|
+
time = ActiveSupport::TimeWithZone.new(DateTime.new(2000), zone)
|
430
|
+
assert_equal '"1999-12-31T19:00:00.000-05:00"', ActiveSupport::JSON.encode(time)
|
431
|
+
end
|
432
|
+
|
433
|
+
protected
|
434
|
+
|
435
|
+
def object_keys(json_object)
|
436
|
+
json_object[1..-2].scan(/([^{}:,\s]+):/).flatten.sort
|
437
|
+
end
|
438
|
+
|
439
|
+
def with_standard_json_time_format(boolean = true)
|
440
|
+
old, ActiveSupport.use_standard_json_time_format = ActiveSupport.use_standard_json_time_format, boolean
|
441
|
+
yield
|
442
|
+
ensure
|
443
|
+
ActiveSupport.use_standard_json_time_format = old
|
444
|
+
end
|
445
|
+
|
446
|
+
def with_time_precision(value)
|
447
|
+
old_value = ActiveSupport::JSON::Encoding.time_precision
|
448
|
+
ActiveSupport::JSON::Encoding.time_precision = value
|
449
|
+
yield
|
450
|
+
ensure
|
451
|
+
ActiveSupport::JSON::Encoding.time_precision = old_value
|
452
|
+
end
|
453
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
require 'bigdecimal'
|
2
|
+
|
3
|
+
module JSONTest
|
4
|
+
class Foo
|
5
|
+
def initialize(a, b)
|
6
|
+
@a, @b = a, b
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
class Hashlike
|
11
|
+
def to_hash
|
12
|
+
{ :foo => "hello", :bar => "world" }
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class Custom
|
17
|
+
def initialize(serialized)
|
18
|
+
@serialized = serialized
|
19
|
+
end
|
20
|
+
|
21
|
+
def as_json(options = nil)
|
22
|
+
@serialized
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class MyStruct < Struct.new(:name, :value)
|
27
|
+
def initialize(*)
|
28
|
+
@unused = "unused instance variable"
|
29
|
+
super
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
module EncodingTestCases
|
34
|
+
TrueTests = [[ true, %(true) ]]
|
35
|
+
FalseTests = [[ false, %(false) ]]
|
36
|
+
NilTests = [[ nil, %(null) ]]
|
37
|
+
NumericTests = [[ 1, %(1) ],
|
38
|
+
[ 2.5, %(2.5) ],
|
39
|
+
[ 0.0/0.0, %(null) ],
|
40
|
+
[ 1.0/0.0, %(null) ],
|
41
|
+
[ -1.0/0.0, %(null) ],
|
42
|
+
[ BigDecimal('0.0')/BigDecimal('0.0'), %(null) ],
|
43
|
+
[ BigDecimal('2.5'), %("#{BigDecimal('2.5')}") ]]
|
44
|
+
|
45
|
+
StringTests = [[ 'http://test.host/posts/1', %("http://test.host/posts/1")]]
|
46
|
+
|
47
|
+
ArrayTests = [[ ['a', 'b', 'c'], %([\"a\",\"b\",\"c\"]) ],
|
48
|
+
[ [1, 'a', :b, nil, false], %([1,\"a\",\"b\",null,false]) ]]
|
49
|
+
|
50
|
+
HashTests = [[ {foo: "bar"}, %({\"foo\":\"bar\"}) ],
|
51
|
+
[ {1 => 1, 2 => 'a', 3 => :b, 4 => nil, 5 => false}, %({\"1\":1,\"2\":\"a\",\"3\":\"b\",\"4\":null,\"5\":false}) ]]
|
52
|
+
|
53
|
+
RangeTests = [[ 1..2, %("1..2")],
|
54
|
+
[ 1...2, %("1...2")],
|
55
|
+
[ 1.5..2.5, %("1.5..2.5")]]
|
56
|
+
|
57
|
+
SymbolTests = [[ :a, %("a") ],
|
58
|
+
[ :this, %("this") ],
|
59
|
+
[ :"a b", %("a b") ]]
|
60
|
+
|
61
|
+
ObjectTests = [[ Foo.new(1, 2), %({\"a\":1,\"b\":2}) ]]
|
62
|
+
HashlikeTests = [[ Hashlike.new, %({\"bar\":\"world\",\"foo\":\"hello\"}) ]]
|
63
|
+
StructTests = [[ MyStruct.new(:foo, "bar"), %({\"name\":\"foo\",\"value\":\"bar\"}) ],
|
64
|
+
[ MyStruct.new(nil, nil), %({\"name\":null,\"value\":null}) ]]
|
65
|
+
CustomTests = [[ Custom.new("custom"), '"custom"' ],
|
66
|
+
[ Custom.new(nil), 'null' ],
|
67
|
+
[ Custom.new(:a), '"a"' ],
|
68
|
+
[ Custom.new([ :foo, "bar" ]), '["foo","bar"]' ],
|
69
|
+
[ Custom.new({ :foo => "hello", :bar => "world" }), '{"bar":"world","foo":"hello"}' ],
|
70
|
+
[ Custom.new(Hashlike.new), '{"bar":"world","foo":"hello"}' ],
|
71
|
+
[ Custom.new(Custom.new(Custom.new(:a))), '"a"' ]]
|
72
|
+
|
73
|
+
RegexpTests = [[ /^a/, '"(?-mix:^a)"' ], [/^\w{1,2}[a-z]+/ix, '"(?ix-m:^\\\\w{1,2}[a-z]+)"']]
|
74
|
+
|
75
|
+
DateTests = [[ Date.new(2005,2,1), %("2005/02/01") ]]
|
76
|
+
TimeTests = [[ Time.utc(2005,2,1,15,15,10), %("2005/02/01 15:15:10 +0000") ]]
|
77
|
+
DateTimeTests = [[ DateTime.civil(2005,2,1,15,15,10), %("2005/02/01 15:15:10 +0000") ]]
|
78
|
+
|
79
|
+
StandardDateTests = [[ Date.new(2005,2,1), %("2005-02-01") ]]
|
80
|
+
StandardTimeTests = [[ Time.utc(2005,2,1,15,15,10), %("2005-02-01T15:15:10.000Z") ]]
|
81
|
+
StandardDateTimeTests = [[ DateTime.civil(2005,2,1,15,15,10), %("2005-02-01T15:15:10.000+00:00") ]]
|
82
|
+
StandardStringTests = [[ 'this is the <string>', %("this is the <string>")]]
|
83
|
+
end
|
84
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'active_support/core_ext/kernel/reporting'
|
2
|
+
|
3
|
+
silence_warnings do
|
4
|
+
Encoding.default_internal = "UTF-8"
|
5
|
+
Encoding.default_external = "UTF-8"
|
6
|
+
end
|
7
|
+
|
8
|
+
require 'bundler/setup'
|
9
|
+
require 'minitest/autorun'
|
10
|
+
require 'active_support'
|
11
|
+
require 'active_support/test_case'
|
12
|
+
require 'active_support/testing/autorun'
|
13
|
+
|
14
|
+
Thread.abort_on_exception = true
|
15
|
+
|
16
|
+
# Show backtraces for deprecated behavior for quicker cleanup.
|
17
|
+
ActiveSupport::Deprecation.debug = true
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module TimeZoneTestHelpers
|
2
|
+
def with_tz_default(tz = nil)
|
3
|
+
old_tz = Time.zone
|
4
|
+
Time.zone = tz
|
5
|
+
yield
|
6
|
+
ensure
|
7
|
+
Time.zone = old_tz
|
8
|
+
end
|
9
|
+
|
10
|
+
def with_env_tz(new_tz = 'US/Eastern')
|
11
|
+
old_tz, ENV['TZ'] = ENV['TZ'], new_tz
|
12
|
+
yield
|
13
|
+
ensure
|
14
|
+
old_tz ? ENV['TZ'] = old_tz : ENV.delete('TZ')
|
15
|
+
end
|
16
|
+
end
|
metadata
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: yagni_json_encoder
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Ian Ker-Seymer
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-02-16 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rake
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: oj
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '2.14'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '2.14'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: activesupport
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 4.1.0
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 4.1.0
|
55
|
+
description:
|
56
|
+
email:
|
57
|
+
- i.kerseymer@gmail.com
|
58
|
+
executables: []
|
59
|
+
extensions: []
|
60
|
+
extra_rdoc_files: []
|
61
|
+
files:
|
62
|
+
- MIT-LICENSE
|
63
|
+
- README.md
|
64
|
+
- lib/activesupport/json_encoder.rb
|
65
|
+
- lib/yagni_json_encoder.rb
|
66
|
+
- test/encoding_test.rb
|
67
|
+
- test/encoding_test_cases.rb
|
68
|
+
- test/test_helper.rb
|
69
|
+
- test/time_zone_test_helpers.rb
|
70
|
+
homepage: https://github.com/ianks/yagni_json_encoder
|
71
|
+
licenses:
|
72
|
+
- MIT
|
73
|
+
metadata: {}
|
74
|
+
post_install_message:
|
75
|
+
rdoc_options: []
|
76
|
+
require_paths:
|
77
|
+
- lib
|
78
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
84
|
+
requirements:
|
85
|
+
- - ">="
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
version: '0'
|
88
|
+
requirements: []
|
89
|
+
rubyforge_project:
|
90
|
+
rubygems_version: 2.4.5.1
|
91
|
+
signing_key:
|
92
|
+
specification_version: 4
|
93
|
+
summary: "~2x faster JSON encoder for Rails"
|
94
|
+
test_files:
|
95
|
+
- test/time_zone_test_helpers.rb
|
96
|
+
- test/encoding_test_cases.rb
|
97
|
+
- test/test_helper.rb
|
98
|
+
- test/encoding_test.rb
|
99
|
+
has_rdoc:
|