as_json_encoder 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: f12af43b6033a45912b528319b4fb4fd0e32666e
4
+ data.tar.gz: fcece079f19081419aeb7b70f2a61797ebe5ee22
5
+ SHA512:
6
+ metadata.gz: 9814d2c38fe198823c0a5370cbda25e64c74f8937eb26d32180728807f021145411c2f01b136587092a8134582f8e222eace9bc18cf0e514bcd877f8d14b5514
7
+ data.tar.gz: b677dd00fb73dbe18c1e549655f2a3a0c841068ea71764b0bdde7b13e5be94d3eed771222b568b010258e67e907aadf43965ef5e0eb7582fe22ac6c2082fa69b
@@ -0,0 +1,14 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in as_json_encoder.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 Godfrey Chan
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,39 @@
1
+ # as_json JSON Encoder
2
+
3
+ A JSON encoder that is tailored to the needs of Rails. The goal is to take
4
+ advantage of the domain knowledge and speed up the JSON encoding process in
5
+ Rails applications.
6
+
7
+ This gem is compatible with Rails 4.1+.
8
+
9
+ ## Current Status
10
+
11
+ At the moment, this is highly experimental. Performance is competitive with the
12
+ current Rails JSON encoder, but we have not achieved our performance goals yet.
13
+
14
+ Using this in production is **not** recommended.
15
+
16
+ ## Installation
17
+
18
+ Add this line to your Rails application's Gemfile:
19
+
20
+ ```ruby
21
+ gem 'as_json_encoder'
22
+ ```
23
+
24
+ And then execute:
25
+
26
+ $ bundle
27
+
28
+ ## Usage
29
+
30
+ Simply follow the instructions above to add this to your Gemfile. No further
31
+ configuration is necessary.
32
+
33
+ ## Contributing
34
+
35
+ 1. Fork it ( https://github.com/chancancode/as_json_encoder/fork )
36
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
37
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
38
+ 4. Push to the branch (`git push origin my-new-feature`)
39
+ 5. Create a new Pull Request
@@ -0,0 +1,12 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ require "rake/testtask"
4
+
5
+ task :default => :test
6
+
7
+ Rake::TestTask.new do |t|
8
+ t.libs << "test"
9
+ t.pattern = "test/**/*_test.rb"
10
+ t.warning = true
11
+ t.verbose = true
12
+ end
@@ -0,0 +1,24 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'as_json_encoder/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "as_json_encoder"
8
+ spec.version = AsJsonEncoder::VERSION
9
+ spec.authors = ["Godfrey Chan"]
10
+ spec.email = ["godfreykfc@gmail.com"]
11
+ spec.summary = "A JSON encoder that is tailored to the needs of Rails."
12
+ spec.homepage = "https://github.com/chancancode/as_json_encoder"
13
+ spec.license = "MIT"
14
+
15
+ spec.files = `git ls-files -z`.split("\x0")
16
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
17
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
+ spec.require_paths = ["lib"]
19
+
20
+ spec.add_runtime_dependency "activesupport", ">= 4.1"
21
+
22
+ spec.add_development_dependency "bundler", "~> 1.7"
23
+ spec.add_development_dependency "rake", "~> 10.0"
24
+ end
@@ -0,0 +1,213 @@
1
+ require "as_json_encoder/version"
2
+
3
+ require "active_support"
4
+ require "active_support/json"
5
+ require "active_support/cache/memory_store"
6
+
7
+ module AsJsonEncoder
8
+
9
+ # Configs
10
+ class << self
11
+ # The cache to use for storing JSON fragments. This will grow unbounded at
12
+ # runtime, so it should be set to a bounded LRU cache implementation, such
13
+ # as +ActiveSupport::Cache::MemoryStore+.
14
+ attr_accessor :cache
15
+
16
+ # The namespace to use for the cache keys.
17
+ attr_accessor :namespace
18
+ end
19
+
20
+ # Defaults
21
+ self.cache = ActiveSupport::Cache::MemoryStore.new
22
+ self.namespace = nil
23
+
24
+ # Include this into any class to mark them as cacheable.
25
+ module Cachable
26
+ def cache_key
27
+ nil
28
+ end
29
+ end
30
+
31
+ class Encoder # :nodoc:
32
+
33
+ STRING_ESCAPE_CHAR_MAP = {
34
+ # Stolen from json-pure
35
+ "\x0" => '\u0000',
36
+ "\x1" => '\u0001',
37
+ "\x2" => '\u0002',
38
+ "\x3" => '\u0003',
39
+ "\x4" => '\u0004',
40
+ "\x5" => '\u0005',
41
+ "\x6" => '\u0006',
42
+ "\x7" => '\u0007',
43
+ "\b" => '\b',
44
+ "\t" => '\t',
45
+ "\n" => '\n',
46
+ "\xb" => '\u000b',
47
+ "\f" => '\f',
48
+ "\r" => '\r',
49
+ "\xe" => '\u000e',
50
+ "\xf" => '\u000f',
51
+ "\x10" => '\u0010',
52
+ "\x11" => '\u0011',
53
+ "\x12" => '\u0012',
54
+ "\x13" => '\u0013',
55
+ "\x14" => '\u0014',
56
+ "\x15" => '\u0015',
57
+ "\x16" => '\u0016',
58
+ "\x17" => '\u0017',
59
+ "\x18" => '\u0018',
60
+ "\x19" => '\u0019',
61
+ "\x1a" => '\u001a',
62
+ "\x1b" => '\u001b',
63
+ "\x1c" => '\u001c',
64
+ "\x1d" => '\u001d',
65
+ "\x1e" => '\u001e',
66
+ "\x1f" => '\u001f',
67
+ '"' => '\"',
68
+ '\\' => '\\\\',
69
+
70
+ # Rails-specific
71
+ "\xE2\x80\xA8".force_encoding(::Encoding::ASCII_8BIT) => '\u2028',
72
+ "\xE2\x80\xA9".force_encoding(::Encoding::ASCII_8BIT) => '\u2029',
73
+ ">" => '\u003e',
74
+ "<" => '\u003c',
75
+ "&" => '\u0026',
76
+ }.freeze
77
+
78
+ STRING_ESCAPE_REGEX_WITH_HTML_ENTITIES = /[><&"\\\x0-\x1f]|(?:\xE2\x80\xA8)|(?:\xE2\x80\xA9)/n
79
+
80
+ STRING_ESCAPE_REGEX_WITHOUT_HTML_ENTITIES = /["\\\x0-\x1f]|(?:\xE2\x80\xA8)|(?:\xE2\x80\xA9)/n
81
+
82
+ private_constant :STRING_ESCAPE_CHAR_MAP,
83
+ :STRING_ESCAPE_REGEX_WITH_HTML_ENTITIES,
84
+ :STRING_ESCAPE_REGEX_WITHOUT_HTML_ENTITIES
85
+
86
+ def encode(value)
87
+ fetch_or_encode_value(value, '', true)
88
+ end
89
+
90
+ private
91
+
92
+ def fetch_or_encode_value(value, buffer, unwrap = false)
93
+ cache, namespace = AsJsonEncoder.cache, AsJsonEncoder.namespace
94
+
95
+ if Cachable === value
96
+ buffer << cache.fetch(value.cache_key, namespace: namespace) do
97
+ encode_value(unwrap ? value.as_json : value, '')
98
+ end
99
+ else
100
+ encode_value(unwrap ? value.as_json : value, buffer)
101
+ end
102
+ end
103
+
104
+ def encode_value(value, buffer)
105
+ case value
106
+ when String
107
+ encode_string(value, buffer)
108
+ when Symbol
109
+ encode_string(value.to_s, buffer)
110
+ when Float
111
+ buffer << (value.finite? ? value.to_s : 'null'.freeze)
112
+ when Numeric
113
+ buffer << value.to_s
114
+ when NilClass
115
+ buffer << 'null'.freeze
116
+ when TrueClass
117
+ buffer << 'true'.freeze
118
+ when FalseClass
119
+ buffer << 'false'.freeze
120
+ when Hash
121
+ encode_hash(value, buffer)
122
+ when Array
123
+ encode_array(value, buffer)
124
+ else
125
+ fetch_or_encode_value(value.as_json, buffer)
126
+ end
127
+ end
128
+
129
+ def encode_string(str, buffer)
130
+ if ActiveSupport::JSON::Encoding.escape_html_entities_in_json
131
+ regexp = STRING_ESCAPE_REGEX_WITH_HTML_ENTITIES
132
+ else
133
+ regexp = STRING_ESCAPE_REGEX_WITHOUT_HTML_ENTITIES
134
+ end
135
+
136
+ # Stolen from json-pure
137
+
138
+ if str.encoding == ::Encoding::UTF_8
139
+ escaped = str.dup
140
+ else
141
+ escaped = str.encode(::Encoding::UTF_8)
142
+ end
143
+
144
+ escaped.force_encoding(::Encoding::ASCII_8BIT)
145
+ escaped.gsub!(regexp, STRING_ESCAPE_CHAR_MAP)
146
+ escaped.force_encoding(::Encoding::UTF_8)
147
+
148
+ buffer << '"'.freeze << escaped << '"'.freeze
149
+ end
150
+
151
+ def encode_hash(hash, buffer)
152
+ buffer << '{'.freeze
153
+
154
+ first = true
155
+
156
+ hash.each_pair do |key, value|
157
+ if first
158
+ first = false
159
+ else
160
+ buffer << ','.freeze
161
+ end
162
+
163
+ if String === key
164
+ encode_string(key, buffer)
165
+ else
166
+ encode_string(key.to_s, buffer)
167
+ end
168
+
169
+ buffer << ':'.freeze
170
+
171
+ fetch_or_encode_value(value, buffer)
172
+ end
173
+
174
+ buffer << '}'.freeze
175
+ end
176
+
177
+ def encode_array(array, buffer)
178
+ buffer << '['.freeze
179
+
180
+ first = true
181
+
182
+ array.each do |value|
183
+ if first
184
+ first = false
185
+ else
186
+ buffer << ','.freeze
187
+ end
188
+
189
+ fetch_or_encode_value(value, buffer)
190
+ end
191
+
192
+ buffer << ']'.freeze
193
+ end
194
+ end
195
+
196
+ SharedEncoderInstance = Encoder.new
197
+
198
+ class ActiveSupportAdapter # :nodoc:
199
+ def initialize(options = nil)
200
+ if options && !options.empty?
201
+ @_encoder = ActiveSupport::JSON::Encoding::JSONGemEncoder.new(options)
202
+ else
203
+ @_encoder = SharedEncoderInstance
204
+ end
205
+ end
206
+
207
+ def encode(value)
208
+ @_encoder.encode(value)
209
+ end
210
+ end
211
+
212
+ ActiveSupport::JSON::Encoding.json_encoder = ActiveSupportAdapter
213
+ end
@@ -0,0 +1,3 @@
1
+ module AsJsonEncoder
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,534 @@
1
+ require 'as_json_encoder'
2
+
3
+ # Stolen from activesupport/test/json/encoding_test.rb
4
+
5
+ require 'securerandom'
6
+ require 'active_support/testing/autorun'
7
+ require 'active_support/core_ext/string/inflections'
8
+ require 'active_support/time'
9
+
10
+ # Skips the current run on Rubinius using Minitest::Assertions#skip
11
+ def rubinius_skip(message = '')
12
+ skip message if RUBY_ENGINE == 'rbx'
13
+ end
14
+
15
+ module TimeZoneTestHelpers
16
+ def with_tz_default(tz = nil)
17
+ old_tz = Time.zone
18
+ Time.zone = tz
19
+ yield
20
+ ensure
21
+ Time.zone = old_tz
22
+ end
23
+
24
+ def with_env_tz(new_tz = 'US/Eastern')
25
+ old_tz, ENV['TZ'] = ENV['TZ'], new_tz
26
+ yield
27
+ ensure
28
+ old_tz ? ENV['TZ'] = old_tz : ENV.delete('TZ')
29
+ end
30
+ end
31
+
32
+ class TestJSONEncoding < ActiveSupport::TestCase
33
+ include TimeZoneTestHelpers
34
+
35
+ self.test_order = :random
36
+
37
+ class Foo
38
+ def initialize(a, b)
39
+ @a, @b = a, b
40
+ end
41
+ end
42
+
43
+ class Hashlike
44
+ def to_hash
45
+ { :foo => "hello", :bar => "world" }
46
+ end
47
+ end
48
+
49
+ class Custom
50
+ def initialize(serialized)
51
+ @serialized = serialized
52
+ end
53
+
54
+ def as_json(options = nil)
55
+ @serialized
56
+ end
57
+ end
58
+
59
+ class CustomWithOptions
60
+ attr_accessor :foo, :bar
61
+
62
+ def as_json(options={})
63
+ options[:only] = %w(foo bar)
64
+ super(options)
65
+ end
66
+ end
67
+
68
+ class OptionsTest
69
+ def as_json(options = :default)
70
+ options
71
+ end
72
+ end
73
+
74
+ class HashWithAsJson < Hash
75
+ attr_accessor :as_json_called
76
+
77
+ def initialize(*)
78
+ super
79
+ end
80
+
81
+ def as_json(options={})
82
+ @as_json_called = true
83
+ super
84
+ end
85
+ end
86
+
87
+ TrueTests = [[ true, %(true) ]]
88
+ FalseTests = [[ false, %(false) ]]
89
+ NilTests = [[ nil, %(null) ]]
90
+ NumericTests = [[ 1, %(1) ],
91
+ [ 2.5, %(2.5) ],
92
+ [ 0.0/0.0, %(null) ],
93
+ [ 1.0/0.0, %(null) ],
94
+ [ -1.0/0.0, %(null) ],
95
+ [ BigDecimal('0.0')/BigDecimal('0.0'), %(null) ],
96
+ [ BigDecimal('2.5'), %("#{BigDecimal('2.5')}") ]]
97
+
98
+ StringTests = [[ 'this is the <string>', %("this is the \\u003cstring\\u003e")],
99
+ [ 'a "string" with quotes & an ampersand', %("a \\"string\\" with quotes \\u0026 an ampersand") ],
100
+ [ 'http://test.host/posts/1', %("http://test.host/posts/1")],
101
+ [ "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",
102
+ %("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") ]]
103
+
104
+ ArrayTests = [[ ['a', 'b', 'c'], %([\"a\",\"b\",\"c\"]) ],
105
+ [ [1, 'a', :b, nil, false], %([1,\"a\",\"b\",null,false]) ]]
106
+
107
+ RangeTests = [[ 1..2, %("1..2")],
108
+ [ 1...2, %("1...2")],
109
+ [ 1.5..2.5, %("1.5..2.5")]]
110
+
111
+ SymbolTests = [[ :a, %("a") ],
112
+ [ :this, %("this") ],
113
+ [ :"a b", %("a b") ]]
114
+
115
+ ObjectTests = [[ Foo.new(1, 2), %({\"a\":1,\"b\":2}) ]]
116
+ HashlikeTests = [[ Hashlike.new, %({\"bar\":\"world\",\"foo\":\"hello\"}) ]]
117
+ CustomTests = [[ Custom.new("custom"), '"custom"' ],
118
+ [ Custom.new(nil), 'null' ],
119
+ [ Custom.new(:a), '"a"' ],
120
+ [ Custom.new([ :foo, "bar" ]), '["foo","bar"]' ],
121
+ [ Custom.new({ :foo => "hello", :bar => "world" }), '{"bar":"world","foo":"hello"}' ],
122
+ [ Custom.new(Hashlike.new), '{"bar":"world","foo":"hello"}' ],
123
+ [ Custom.new(Custom.new(Custom.new(:a))), '"a"' ]]
124
+
125
+ RegexpTests = [[ /^a/, '"(?-mix:^a)"' ], [/^\w{1,2}[a-z]+/ix, '"(?ix-m:^\\\\w{1,2}[a-z]+)"']]
126
+
127
+ DateTests = [[ Date.new(2005,2,1), %("2005/02/01") ]]
128
+ TimeTests = [[ Time.utc(2005,2,1,15,15,10), %("2005/02/01 15:15:10 +0000") ]]
129
+ DateTimeTests = [[ DateTime.civil(2005,2,1,15,15,10), %("2005/02/01 15:15:10 +0000") ]]
130
+
131
+ StandardDateTests = [[ Date.new(2005,2,1), %("2005-02-01") ]]
132
+ StandardTimeTests = [[ Time.utc(2005,2,1,15,15,10), %("2005-02-01T15:15:10.000Z") ]]
133
+ StandardDateTimeTests = [[ DateTime.civil(2005,2,1,15,15,10), %("2005-02-01T15:15:10.000+00:00") ]]
134
+ StandardStringTests = [[ 'this is the <string>', %("this is the <string>")]]
135
+
136
+ def sorted_json(json)
137
+ return json unless json =~ /^\{.*\}$/
138
+ '{' + json[1..-2].split(',').sort.join(',') + '}'
139
+ end
140
+
141
+ constants.grep(/Tests$/).each do |class_tests|
142
+ define_method("test_#{class_tests[0..-6].underscore}") do
143
+ begin
144
+ prev = ActiveSupport.use_standard_json_time_format
145
+
146
+ ActiveSupport.escape_html_entities_in_json = class_tests !~ /^Standard/
147
+ ActiveSupport.use_standard_json_time_format = class_tests =~ /^Standard/
148
+ self.class.const_get(class_tests).each do |pair|
149
+ assert_equal pair.last, sorted_json(ActiveSupport::JSON.encode(pair.first))
150
+ end
151
+ ensure
152
+ ActiveSupport.escape_html_entities_in_json = false
153
+ ActiveSupport.use_standard_json_time_format = prev
154
+ end
155
+ end
156
+ end
157
+
158
+ def test_process_status
159
+ rubinius_skip "https://github.com/rubinius/rubinius/issues/3334"
160
+
161
+ # There doesn't seem to be a good way to get a handle on a Process::Status object without actually
162
+ # creating a child process, hence this to populate $?
163
+ system("not_a_real_program_#{SecureRandom.hex}")
164
+ assert_equal %({"exitstatus":#{$?.exitstatus},"pid":#{$?.pid}}), ActiveSupport::JSON.encode($?)
165
+ end
166
+
167
+ def test_hash_encoding
168
+ assert_equal %({\"a\":\"b\"}), ActiveSupport::JSON.encode(:a => :b)
169
+ assert_equal %({\"a\":1}), ActiveSupport::JSON.encode('a' => 1)
170
+ assert_equal %({\"a\":[1,2]}), ActiveSupport::JSON.encode('a' => [1,2])
171
+ assert_equal %({"1":2}), ActiveSupport::JSON.encode(1 => 2)
172
+
173
+ assert_equal %({\"a\":\"b\",\"c\":\"d\"}), sorted_json(ActiveSupport::JSON.encode(:a => :b, :c => :d))
174
+ end
175
+
176
+ def test_utf8_string_encoded_properly
177
+ result = ActiveSupport::JSON.encode('€2.99')
178
+ assert_equal '"€2.99"', result
179
+ assert_equal(Encoding::UTF_8, result.encoding)
180
+
181
+ result = ActiveSupport::JSON.encode('✎☺')
182
+ assert_equal '"✎☺"', result
183
+ assert_equal(Encoding::UTF_8, result.encoding)
184
+ end
185
+
186
+ def test_non_utf8_string_transcodes
187
+ s = '二'.encode('Shift_JIS')
188
+ result = ActiveSupport::JSON.encode(s)
189
+ assert_equal '"二"', result
190
+ assert_equal Encoding::UTF_8, result.encoding
191
+ end
192
+
193
+ def test_wide_utf8_chars
194
+ w = '𠜎'
195
+ result = ActiveSupport::JSON.encode(w)
196
+ assert_equal '"𠜎"', result
197
+ end
198
+
199
+ def test_wide_utf8_roundtrip
200
+ hash = { string: "𐒑" }
201
+ json = ActiveSupport::JSON.encode(hash)
202
+ decoded_hash = ActiveSupport::JSON.decode(json)
203
+ assert_equal "𐒑", decoded_hash['string']
204
+ end
205
+
206
+ def test_hash_key_identifiers_are_always_quoted
207
+ values = {0 => 0, 1 => 1, :_ => :_, "$" => "$", "a" => "a", :A => :A, :A0 => :A0, "A0B" => "A0B"}
208
+ assert_equal %w( "$" "A" "A0" "A0B" "_" "a" "0" "1" ).sort, object_keys(ActiveSupport::JSON.encode(values))
209
+ end
210
+
211
+ def test_hash_should_allow_key_filtering_with_only
212
+ assert_equal %({"a":1}), ActiveSupport::JSON.encode({'a' => 1, :b => 2, :c => 3}, :only => 'a')
213
+ end
214
+
215
+ def test_hash_should_allow_key_filtering_with_except
216
+ assert_equal %({"b":2}), ActiveSupport::JSON.encode({'foo' => 'bar', :b => 2, :c => 3}, :except => ['foo', :c])
217
+ end
218
+
219
+ def test_time_to_json_includes_local_offset
220
+ with_standard_json_time_format(true) do
221
+ with_env_tz 'US/Eastern' do
222
+ assert_equal %("2005-02-01T15:15:10.000-05:00"), ActiveSupport::JSON.encode(Time.local(2005,2,1,15,15,10))
223
+ end
224
+ end
225
+ end
226
+
227
+ def test_hash_with_time_to_json
228
+ with_standard_json_time_format(false) do
229
+ assert_equal '{"time":"2009/01/01 00:00:00 +0000"}', { :time => Time.utc(2009) }.to_json
230
+ end
231
+ end
232
+
233
+ def test_nested_hash_with_float
234
+ assert_nothing_raised do
235
+ hash = {
236
+ "CHI" => {
237
+ :display_name => "chicago",
238
+ :latitude => 123.234
239
+ }
240
+ }
241
+ ActiveSupport::JSON.encode(hash)
242
+ end
243
+ end
244
+
245
+ def test_hash_like_with_options
246
+ h = Hashlike.new
247
+ json = h.to_json :only => [:foo]
248
+
249
+ assert_equal({"foo"=>"hello"}, JSON.parse(json))
250
+ end
251
+
252
+ def test_object_to_json_with_options
253
+ obj = Object.new
254
+ obj.instance_variable_set :@foo, "hello"
255
+ obj.instance_variable_set :@bar, "world"
256
+ json = obj.to_json :only => ["foo"]
257
+
258
+ assert_equal({"foo"=>"hello"}, JSON.parse(json))
259
+ end
260
+
261
+ def test_struct_to_json_with_options
262
+ struct = Struct.new(:foo, :bar).new
263
+ struct.foo = "hello"
264
+ struct.bar = "world"
265
+ json = struct.to_json :only => [:foo]
266
+
267
+ assert_equal({"foo"=>"hello"}, JSON.parse(json))
268
+ end
269
+
270
+ def test_hash_should_pass_encoding_options_to_children_in_as_json
271
+ person = {
272
+ :name => 'John',
273
+ :address => {
274
+ :city => 'London',
275
+ :country => 'UK'
276
+ }
277
+ }
278
+ json = person.as_json :only => [:address, :city]
279
+
280
+ assert_equal({ 'address' => { 'city' => 'London' }}, json)
281
+ end
282
+
283
+ def test_hash_should_pass_encoding_options_to_children_in_to_json
284
+ person = {
285
+ :name => 'John',
286
+ :address => {
287
+ :city => 'London',
288
+ :country => 'UK'
289
+ }
290
+ }
291
+ json = person.to_json :only => [:address, :city]
292
+
293
+ assert_equal(%({"address":{"city":"London"}}), json)
294
+ end
295
+
296
+ def test_array_should_pass_encoding_options_to_children_in_as_json
297
+ people = [
298
+ { :name => 'John', :address => { :city => 'London', :country => 'UK' }},
299
+ { :name => 'Jean', :address => { :city => 'Paris' , :country => 'France' }}
300
+ ]
301
+ json = people.as_json :only => [:address, :city]
302
+ expected = [
303
+ { 'address' => { 'city' => 'London' }},
304
+ { 'address' => { 'city' => 'Paris' }}
305
+ ]
306
+
307
+ assert_equal(expected, json)
308
+ end
309
+
310
+ def test_array_should_pass_encoding_options_to_children_in_to_json
311
+ people = [
312
+ { :name => 'John', :address => { :city => 'London', :country => 'UK' }},
313
+ { :name => 'Jean', :address => { :city => 'Paris' , :country => 'France' }}
314
+ ]
315
+ json = people.to_json :only => [:address, :city]
316
+
317
+ assert_equal(%([{"address":{"city":"London"}},{"address":{"city":"Paris"}}]), json)
318
+ end
319
+
320
+ People = Class.new(BasicObject) do
321
+ include Enumerable
322
+ def initialize()
323
+ @people = [
324
+ { :name => 'John', :address => { :city => 'London', :country => 'UK' }},
325
+ { :name => 'Jean', :address => { :city => 'Paris' , :country => 'France' }}
326
+ ]
327
+ end
328
+ def each(*, &blk)
329
+ @people.each do |p|
330
+ yield p if blk
331
+ p
332
+ end.each
333
+ end
334
+ end
335
+
336
+ def test_enumerable_should_generate_json_with_as_json
337
+ json = People.new.as_json :only => [:address, :city]
338
+ expected = [
339
+ { 'address' => { 'city' => 'London' }},
340
+ { 'address' => { 'city' => 'Paris' }}
341
+ ]
342
+
343
+ assert_equal(expected, json)
344
+ end
345
+
346
+ def test_enumerable_should_generate_json_with_to_json
347
+ json = People.new.to_json :only => [:address, :city]
348
+ assert_equal(%([{"address":{"city":"London"}},{"address":{"city":"Paris"}}]), json)
349
+ end
350
+
351
+ def test_enumerable_should_pass_encoding_options_to_children_in_as_json
352
+ json = People.new.each.as_json :only => [:address, :city]
353
+ expected = [
354
+ { 'address' => { 'city' => 'London' }},
355
+ { 'address' => { 'city' => 'Paris' }}
356
+ ]
357
+
358
+ assert_equal(expected, json)
359
+ end
360
+
361
+ def test_enumerable_should_pass_encoding_options_to_children_in_to_json
362
+ json = People.new.each.to_json :only => [:address, :city]
363
+
364
+ assert_equal(%([{"address":{"city":"London"}},{"address":{"city":"Paris"}}]), json)
365
+ end
366
+
367
+ def test_hash_to_json_should_not_keep_options_around
368
+ f = CustomWithOptions.new
369
+ f.foo = "hello"
370
+ f.bar = "world"
371
+
372
+ hash = {"foo" => f, "other_hash" => {"foo" => "other_foo", "test" => "other_test"}}
373
+ assert_equal({"foo"=>{"foo"=>"hello","bar"=>"world"},
374
+ "other_hash" => {"foo"=>"other_foo","test"=>"other_test"}}, ActiveSupport::JSON.decode(hash.to_json))
375
+ end
376
+
377
+ def test_array_to_json_should_not_keep_options_around
378
+ f = CustomWithOptions.new
379
+ f.foo = "hello"
380
+ f.bar = "world"
381
+
382
+ array = [f, {"foo" => "other_foo", "test" => "other_test"}]
383
+ assert_equal([{"foo"=>"hello","bar"=>"world"},
384
+ {"foo"=>"other_foo","test"=>"other_test"}], ActiveSupport::JSON.decode(array.to_json))
385
+ end
386
+
387
+ def test_hash_as_json_without_options
388
+ json = { foo: OptionsTest.new }.as_json
389
+ assert_equal({"foo" => :default}, json)
390
+ end
391
+
392
+ def test_array_as_json_without_options
393
+ json = [ OptionsTest.new ].as_json
394
+ assert_equal([:default], json)
395
+ end
396
+
397
+ def test_struct_encoding
398
+ Struct.new('UserNameAndEmail', :name, :email)
399
+ Struct.new('UserNameAndDate', :name, :date)
400
+ Struct.new('Custom', :name, :sub)
401
+ user_email = Struct::UserNameAndEmail.new 'David', 'sample@example.com'
402
+ user_birthday = Struct::UserNameAndDate.new 'David', Date.new(2010, 01, 01)
403
+ custom = Struct::Custom.new 'David', user_birthday
404
+
405
+
406
+ json_strings = ""
407
+ json_string_and_date = ""
408
+ json_custom = ""
409
+
410
+ assert_nothing_raised do
411
+ json_strings = user_email.to_json
412
+ json_string_and_date = user_birthday.to_json
413
+ json_custom = custom.to_json
414
+ end
415
+
416
+ assert_equal({"name" => "David",
417
+ "sub" => {
418
+ "name" => "David",
419
+ "date" => "2010-01-01" }}, ActiveSupport::JSON.decode(json_custom))
420
+
421
+ assert_equal({"name" => "David", "email" => "sample@example.com"},
422
+ ActiveSupport::JSON.decode(json_strings))
423
+
424
+ assert_equal({"name" => "David", "date" => "2010-01-01"},
425
+ ActiveSupport::JSON.decode(json_string_and_date))
426
+ end
427
+
428
+ def test_nil_true_and_false_represented_as_themselves
429
+ assert_equal nil, nil.as_json
430
+ assert_equal true, true.as_json
431
+ assert_equal false, false.as_json
432
+ end
433
+
434
+ def test_json_gem_dump_by_passing_active_support_encoder
435
+ h = HashWithAsJson.new
436
+ h[:foo] = "hello"
437
+ h[:bar] = "world"
438
+
439
+ assert_equal %({"foo":"hello","bar":"world"}), JSON.dump(h)
440
+ assert_nil h.as_json_called
441
+ end
442
+
443
+ def test_json_gem_generate_by_passing_active_support_encoder
444
+ h = HashWithAsJson.new
445
+ h[:foo] = "hello"
446
+ h[:bar] = "world"
447
+
448
+ assert_equal %({"foo":"hello","bar":"world"}), JSON.generate(h)
449
+ assert_nil h.as_json_called
450
+ end
451
+
452
+ def test_json_gem_pretty_generate_by_passing_active_support_encoder
453
+ h = HashWithAsJson.new
454
+ h[:foo] = "hello"
455
+ h[:bar] = "world"
456
+
457
+ assert_equal <<EXPECTED.chomp, JSON.pretty_generate(h)
458
+ {
459
+ "foo": "hello",
460
+ "bar": "world"
461
+ }
462
+ EXPECTED
463
+ assert_nil h.as_json_called
464
+ end
465
+
466
+ def test_twz_to_json_with_use_standard_json_time_format_config_set_to_false
467
+ with_standard_json_time_format(false) do
468
+ zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)']
469
+ time = ActiveSupport::TimeWithZone.new(Time.utc(2000), zone)
470
+ assert_equal "\"1999/12/31 19:00:00 -0500\"", ActiveSupport::JSON.encode(time)
471
+ end
472
+ end
473
+
474
+ def test_twz_to_json_with_use_standard_json_time_format_config_set_to_true
475
+ with_standard_json_time_format(true) do
476
+ zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)']
477
+ time = ActiveSupport::TimeWithZone.new(Time.utc(2000), zone)
478
+ assert_equal "\"1999-12-31T19:00:00.000-05:00\"", ActiveSupport::JSON.encode(time)
479
+ end
480
+ end
481
+
482
+ def test_twz_to_json_with_custom_time_precision
483
+ with_standard_json_time_format(true) do
484
+ with_time_precision(0) do
485
+ zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)']
486
+ time = ActiveSupport::TimeWithZone.new(Time.utc(2000), zone)
487
+ assert_equal "\"1999-12-31T19:00:00-05:00\"", ActiveSupport::JSON.encode(time)
488
+ end
489
+ end
490
+ end
491
+
492
+ def test_time_to_json_with_custom_time_precision
493
+ with_standard_json_time_format(true) do
494
+ with_time_precision(0) do
495
+ assert_equal "\"2000-01-01T00:00:00Z\"", ActiveSupport::JSON.encode(Time.utc(2000))
496
+ end
497
+ end
498
+ end
499
+
500
+ def test_datetime_to_json_with_custom_time_precision
501
+ with_standard_json_time_format(true) do
502
+ with_time_precision(0) do
503
+ assert_equal "\"2000-01-01T00:00:00+00:00\"", ActiveSupport::JSON.encode(DateTime.new(2000))
504
+ end
505
+ end
506
+ end
507
+
508
+ def test_twz_to_json_when_wrapping_a_date_time
509
+ zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)']
510
+ time = ActiveSupport::TimeWithZone.new(DateTime.new(2000), zone)
511
+ assert_equal '"1999-12-31T19:00:00.000-05:00"', ActiveSupport::JSON.encode(time)
512
+ end
513
+
514
+ protected
515
+
516
+ def object_keys(json_object)
517
+ json_object[1..-2].scan(/([^{}:,\s]+):/).flatten.sort
518
+ end
519
+
520
+ def with_standard_json_time_format(boolean = true)
521
+ old, ActiveSupport.use_standard_json_time_format = ActiveSupport.use_standard_json_time_format, boolean
522
+ yield
523
+ ensure
524
+ ActiveSupport.use_standard_json_time_format = old
525
+ end
526
+
527
+ def with_time_precision(value)
528
+ old_value = ActiveSupport::JSON::Encoding.time_precision
529
+ ActiveSupport::JSON::Encoding.time_precision = value
530
+ yield
531
+ ensure
532
+ ActiveSupport::JSON::Encoding.time_precision = old_value
533
+ end
534
+ end
metadata ADDED
@@ -0,0 +1,97 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: as_json_encoder
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Godfrey Chan
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-03-13 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activesupport
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '4.1'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '4.1'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.7'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.7'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '10.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '10.0'
55
+ description:
56
+ email:
57
+ - godfreykfc@gmail.com
58
+ executables: []
59
+ extensions: []
60
+ extra_rdoc_files: []
61
+ files:
62
+ - ".gitignore"
63
+ - Gemfile
64
+ - LICENSE.txt
65
+ - README.md
66
+ - Rakefile
67
+ - as_json_encoder.gemspec
68
+ - lib/as_json_encoder.rb
69
+ - lib/as_json_encoder/version.rb
70
+ - test/as_json_encoder_test.rb
71
+ homepage: https://github.com/chancancode/as_json_encoder
72
+ licenses:
73
+ - MIT
74
+ metadata: {}
75
+ post_install_message:
76
+ rdoc_options: []
77
+ require_paths:
78
+ - lib
79
+ required_ruby_version: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - ">="
82
+ - !ruby/object:Gem::Version
83
+ version: '0'
84
+ required_rubygems_version: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: '0'
89
+ requirements: []
90
+ rubyforge_project:
91
+ rubygems_version: 2.4.5
92
+ signing_key:
93
+ specification_version: 4
94
+ summary: A JSON encoder that is tailored to the needs of Rails.
95
+ test_files:
96
+ - test/as_json_encoder_test.rb
97
+ has_rdoc: