as_json_encoder 0.0.1

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.
@@ -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: