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.
- checksums.yaml +7 -0
- data/.gitignore +14 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +39 -0
- data/Rakefile +12 -0
- data/as_json_encoder.gemspec +24 -0
- data/lib/as_json_encoder.rb +213 -0
- data/lib/as_json_encoder/version.rb +3 -0
- data/test/as_json_encoder_test.rb +534 -0
- metadata +97 -0
checksums.yaml
ADDED
@@ -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
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -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.
|
data/README.md
ADDED
@@ -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
|
data/Rakefile
ADDED
@@ -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,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:
|