oj 2.18.5 → 3.0.0
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 +4 -4
- data/README.md +33 -226
- data/ext/oj/circarray.c +0 -25
- data/ext/oj/circarray.h +0 -25
- data/ext/oj/code.c +227 -0
- data/ext/oj/code.h +40 -0
- data/ext/oj/compat.c +126 -38
- data/ext/oj/custom.c +1097 -0
- data/ext/oj/dump.c +658 -2376
- data/ext/oj/dump.h +92 -0
- data/ext/oj/dump_compat.c +937 -0
- data/ext/oj/dump_leaf.c +254 -0
- data/ext/oj/dump_object.c +810 -0
- data/ext/oj/dump_rails.c +329 -0
- data/ext/oj/dump_strict.c +416 -0
- data/ext/oj/err.c +0 -25
- data/ext/oj/err.h +8 -2
- data/ext/oj/fast.c +24 -24
- data/ext/oj/mimic_json.c +817 -0
- data/ext/oj/mimic_rails.c +806 -0
- data/ext/oj/mimic_rails.h +17 -0
- data/ext/oj/object.c +18 -72
- data/ext/oj/odd.c +0 -25
- data/ext/oj/odd.h +2 -27
- data/ext/oj/oj.c +655 -1503
- data/ext/oj/oj.h +93 -40
- data/ext/oj/parse.c +99 -46
- data/ext/oj/parse.h +12 -26
- data/ext/oj/reader.c +1 -25
- data/ext/oj/reader.h +3 -25
- data/ext/oj/resolve.c +9 -11
- data/ext/oj/resolve.h +2 -2
- data/ext/oj/rxclass.c +133 -0
- data/ext/oj/rxclass.h +27 -0
- data/ext/oj/saj.c +4 -25
- data/ext/oj/scp.c +3 -25
- data/ext/oj/sparse.c +89 -13
- data/ext/oj/stream_writer.c +301 -0
- data/ext/oj/strict.c +4 -27
- data/ext/oj/string_writer.c +480 -0
- data/ext/oj/val_stack.h +6 -2
- data/lib/oj.rb +1 -23
- data/lib/oj/easy_hash.rb +12 -4
- data/lib/oj/json.rb +172 -0
- data/lib/oj/mimic.rb +123 -18
- data/lib/oj/state.rb +131 -0
- data/lib/oj/version.rb +1 -1
- data/pages/Advanced.md +22 -0
- data/pages/Compatibility.md +25 -0
- data/pages/Custom.md +23 -0
- data/pages/Encoding.md +65 -0
- data/pages/JsonGem.md +79 -0
- data/pages/Modes.md +140 -0
- data/pages/Options.md +250 -0
- data/pages/Rails.md +60 -0
- data/pages/Security.md +20 -0
- data/test/activesupport4/decoding_test.rb +105 -0
- data/test/activesupport4/encoding_test.rb +531 -0
- data/test/activesupport4/test_helper.rb +41 -0
- data/test/activesupport5/decoding_test.rb +125 -0
- data/test/activesupport5/encoding_test.rb +483 -0
- data/test/activesupport5/encoding_test_cases.rb +90 -0
- data/test/activesupport5/test_helper.rb +50 -0
- data/test/activesupport5/time_zone_test_helpers.rb +24 -0
- data/test/json_gem/json_addition_test.rb +216 -0
- data/test/json_gem/json_common_interface_test.rb +143 -0
- data/test/json_gem/json_encoding_test.rb +109 -0
- data/test/json_gem/json_ext_parser_test.rb +20 -0
- data/test/json_gem/json_fixtures_test.rb +35 -0
- data/test/json_gem/json_generator_test.rb +383 -0
- data/test/json_gem/json_generic_object_test.rb +90 -0
- data/test/json_gem/json_parser_test.rb +470 -0
- data/test/json_gem/json_string_matching_test.rb +42 -0
- data/test/json_gem/test_helper.rb +18 -0
- data/test/perf_compat.rb +30 -28
- data/test/perf_object.rb +1 -1
- data/test/perf_strict.rb +18 -1
- data/test/sample.rb +0 -1
- data/test/test_compat.rb +169 -93
- data/test/test_custom.rb +355 -0
- data/test/test_file.rb +0 -8
- data/test/test_null.rb +376 -0
- data/test/test_object.rb +268 -3
- data/test/test_scp.rb +22 -1
- data/test/test_strict.rb +160 -4
- data/test/test_various.rb +52 -620
- data/test/tests.rb +14 -0
- data/test/tests_mimic.rb +14 -0
- data/test/tests_mimic_addition.rb +7 -0
- metadata +89 -47
- data/test/activesupport_datetime_test.rb +0 -23
- data/test/bug.rb +0 -51
- data/test/bug2.rb +0 -10
- data/test/bug3.rb +0 -46
- data/test/bug_fast.rb +0 -32
- data/test/bug_load.rb +0 -24
- data/test/crash.rb +0 -111
- data/test/curl/curl_oj.rb +0 -46
- data/test/curl/get_oj.rb +0 -24
- data/test/curl/just_curl.rb +0 -31
- data/test/curl/just_oj.rb +0 -51
- data/test/example.rb +0 -11
- data/test/foo.rb +0 -24
- data/test/io.rb +0 -48
- data/test/isolated/test_mimic_rails_datetime.rb +0 -27
- data/test/mod.rb +0 -16
- data/test/rails.rb +0 -50
- data/test/russian.rb +0 -18
- data/test/struct.rb +0 -29
- data/test/test_serializer.rb +0 -59
- data/test/write_timebars.rb +0 -31
data/pages/Rails.md
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
# Oj Rails Compatibility
|
2
|
+
|
3
|
+
The `:rails` mode mimics the ActiveSupport version 5 encoder. Rails and
|
4
|
+
ActiveSupport are built around the use of the `as_json(*)` method defined for
|
5
|
+
a class. Oj attempts to provide the same functionality by being a drop in
|
6
|
+
replacement with a few exceptions.
|
7
|
+
|
8
|
+
```ruby
|
9
|
+
require 'oj'
|
10
|
+
|
11
|
+
Oj::Rails.set_encoder()
|
12
|
+
Oj::Rails.set_decoder()
|
13
|
+
Oj::Rails.optimize(Array, BigDecimal, Hash, Range, Regexp, Time)
|
14
|
+
```
|
15
|
+
|
16
|
+
Some of the Oj options are supported as arguments to the encoder if called
|
17
|
+
from Oj::Rails.encode() but when using the Oj::Rails::Encoder class the
|
18
|
+
encode() method does not support optional arguments as required by the
|
19
|
+
ActiveSupport compliance guidelines. The general approach Rails takes for
|
20
|
+
configuring encoding options is to either set global values or to create a new
|
21
|
+
instance of the Encoder class and provide options in the initializer.
|
22
|
+
|
23
|
+
The globals that ActiveSupport uses for encoding are:
|
24
|
+
|
25
|
+
* ActiveSupport::JSON::Encoding.use_standard_json_time_format
|
26
|
+
* ActiveSupport::JSON::Encoding.escape_html_entities_in_json
|
27
|
+
* ActiveSupport::JSON::Encoding.time_precision
|
28
|
+
* ActiveSupport::JSON::Encoding.json_encoder
|
29
|
+
|
30
|
+
Those globals are aliased to also be accessed from the ActiveSupport module
|
31
|
+
directly so ActiveSupport::JSON::Encoding.time_precision can also be accessed
|
32
|
+
from ActiveSupport.time_precision. Oj makes use of these globals in mimicing
|
33
|
+
Rails after the Oj::Rails.set_encode() method is called. That also sets the
|
34
|
+
ActiveSupport.json_encoder to the Oj::Rails::Encoder class.
|
35
|
+
|
36
|
+
Options passed into a call to to_json() are passed to the as_json()
|
37
|
+
methods. These are mostly ignored by Oj and simply passed on without
|
38
|
+
modifications as per the guidelines. The exception to this are the options
|
39
|
+
specific to Oj such as the :circular option which it used to detect circular
|
40
|
+
references while encoding.
|
41
|
+
|
42
|
+
By default Oj acts like the ActiveSupport encoder and honors any changes in
|
43
|
+
the as_json() methods. There are also optimized encoders for some
|
44
|
+
classes. When the optimized encoder it toggled the as_json() methods will not
|
45
|
+
be called for that class but instead the optimized version will be called. The
|
46
|
+
optimized version is the same as the ActiveSupport default encoding for a
|
47
|
+
given class. The optimized versions are toggled with the optimize() and
|
48
|
+
deoptimize() methods.
|
49
|
+
|
50
|
+
The classes that can be put in optimized mode are:
|
51
|
+
|
52
|
+
* Array
|
53
|
+
* BigDecimal
|
54
|
+
* Hash
|
55
|
+
* Range
|
56
|
+
* Regexp
|
57
|
+
* Time
|
58
|
+
|
59
|
+
The ActiveSupport decoder is the JSON.parse() method. Calling the
|
60
|
+
Oj::Rails.set_decoder() method replaces that method with the Oj equivelant.
|
data/pages/Security.md
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
# Security and Optimization
|
2
|
+
|
3
|
+
Two settings in Oj are useful for parsing but do expose a vulnerability if used
|
4
|
+
from an untrusted source. Symbolized keys can cause memory to be filled with
|
5
|
+
previous versions of ruby. Ruby 2.1 and below does not garbage collect
|
6
|
+
Symbols. The same is true for auto defining classes in all versions of ruby;
|
7
|
+
memory will also be exhausted if too many classes are automatically
|
8
|
+
defined. Auto defining is a useful feature during development and from trusted
|
9
|
+
sources but it allows too many classes to be created in the object load mode and
|
10
|
+
auto defined is used with an untrusted source. The `Oj.strict_load()` method
|
11
|
+
sets and uses the most strict and safest options. It should be used by
|
12
|
+
developers who find it difficult to understand the options available in Oj.
|
13
|
+
|
14
|
+
The options in Oj are designed to provide flexibility to the developer. This
|
15
|
+
flexibility allows Objects to be serialized and deserialized. No methods are
|
16
|
+
ever called on these created Objects but that does not stop the developer from
|
17
|
+
calling methods on them. As in any system, check your inputs before working with
|
18
|
+
them. Taking an arbitrary `String` from a user and evaluating it is never a good
|
19
|
+
idea from an unsecure source. The same is true for `Object` attributes as they
|
20
|
+
are not more than `String`s. Always check inputs from untrusted sources.
|
@@ -0,0 +1,105 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'activesupport4/test_helper'
|
3
|
+
require 'active_support/json'
|
4
|
+
require 'active_support/time'
|
5
|
+
|
6
|
+
class TestJSONDecoding < ActiveSupport::TestCase
|
7
|
+
class Foo
|
8
|
+
def self.json_create(object)
|
9
|
+
"Foo"
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
TESTS = {
|
14
|
+
%q({"returnTo":{"\/categories":"\/"}}) => {"returnTo" => {"/categories" => "/"}},
|
15
|
+
%q({"return\\"To\\":":{"\/categories":"\/"}}) => {"return\"To\":" => {"/categories" => "/"}},
|
16
|
+
%q({"returnTo":{"\/categories":1}}) => {"returnTo" => {"/categories" => 1}},
|
17
|
+
%({"returnTo":[1,"a"]}) => {"returnTo" => [1, "a"]},
|
18
|
+
%({"returnTo":[1,"\\"a\\",", "b"]}) => {"returnTo" => [1, "\"a\",", "b"]},
|
19
|
+
%({"a": "'", "b": "5,000"}) => {"a" => "'", "b" => "5,000"},
|
20
|
+
%({"a": "a's, b's and c's", "b": "5,000"}) => {"a" => "a's, b's and c's", "b" => "5,000"},
|
21
|
+
# multibyte
|
22
|
+
%({"matzue": "松江", "asakusa": "浅草"}) => {"matzue" => "松江", "asakusa" => "浅草"},
|
23
|
+
%({"a": "2007-01-01"}) => {'a' => Date.new(2007, 1, 1)},
|
24
|
+
%({"a": "2007-01-01 01:12:34 Z"}) => {'a' => Time.utc(2007, 1, 1, 1, 12, 34)},
|
25
|
+
%(["2007-01-01 01:12:34 Z"]) => [Time.utc(2007, 1, 1, 1, 12, 34)],
|
26
|
+
%(["2007-01-01 01:12:34 Z", "2007-01-01 01:12:35 Z"]) => [Time.utc(2007, 1, 1, 1, 12, 34), Time.utc(2007, 1, 1, 1, 12, 35)],
|
27
|
+
# no time zone
|
28
|
+
%({"a": "2007-01-01 01:12:34"}) => {'a' => "2007-01-01 01:12:34"},
|
29
|
+
# invalid date
|
30
|
+
%({"a": "1089-10-40"}) => {'a' => "1089-10-40"},
|
31
|
+
# xmlschema date notation
|
32
|
+
%({"a": "2009-08-10T19:01:02Z"}) => {'a' => Time.utc(2009, 8, 10, 19, 1, 2)},
|
33
|
+
%({"a": "2009-08-10T19:01:02+02:00"}) => {'a' => Time.utc(2009, 8, 10, 17, 1, 2)},
|
34
|
+
%({"a": "2009-08-10T19:01:02-05:00"}) => {'a' => Time.utc(2009, 8, 11, 00, 1, 2)},
|
35
|
+
# needs to be *exact*
|
36
|
+
%({"a": " 2007-01-01 01:12:34 Z "}) => {'a' => " 2007-01-01 01:12:34 Z "},
|
37
|
+
%({"a": "2007-01-01 : it's your birthday"}) => {'a' => "2007-01-01 : it's your birthday"},
|
38
|
+
%([]) => [],
|
39
|
+
%({}) => {},
|
40
|
+
%({"a":1}) => {"a" => 1},
|
41
|
+
%({"a": ""}) => {"a" => ""},
|
42
|
+
%({"a":"\\""}) => {"a" => "\""},
|
43
|
+
%({"a": null}) => {"a" => nil},
|
44
|
+
%({"a": true}) => {"a" => true},
|
45
|
+
%({"a": false}) => {"a" => false},
|
46
|
+
%q({"bad":"\\\\","trailing":""}) => {"bad" => "\\", "trailing" => ""},
|
47
|
+
%q({"a": "http:\/\/test.host\/posts\/1"}) => {"a" => "http://test.host/posts/1"},
|
48
|
+
%q({"a": "\u003cunicode\u0020escape\u003e"}) => {"a" => "<unicode escape>"},
|
49
|
+
%q({"a": "\\\\u0020skip double backslashes"}) => {"a" => "\\u0020skip double backslashes"},
|
50
|
+
%q({"a": "\u003cbr /\u003e"}) => {'a' => "<br />"},
|
51
|
+
%q({"b":["\u003ci\u003e","\u003cb\u003e","\u003cu\u003e"]}) => {'b' => ["<i>","<b>","<u>"]},
|
52
|
+
# test combination of dates and escaped or unicode encoded data in arrays
|
53
|
+
%q([{"d":"1970-01-01", "s":"\u0020escape"},{"d":"1970-01-01", "s":"\u0020escape"}]) =>
|
54
|
+
[{'d' => Date.new(1970, 1, 1), 's' => ' escape'},{'d' => Date.new(1970, 1, 1), 's' => ' escape'}],
|
55
|
+
%q([{"d":"1970-01-01","s":"http:\/\/example.com"},{"d":"1970-01-01","s":"http:\/\/example.com"}]) =>
|
56
|
+
[{'d' => Date.new(1970, 1, 1), 's' => 'http://example.com'},
|
57
|
+
{'d' => Date.new(1970, 1, 1), 's' => 'http://example.com'}],
|
58
|
+
# tests escaping of "\n" char with Yaml backend
|
59
|
+
%q({"a":"\n"}) => {"a"=>"\n"},
|
60
|
+
%q({"a":"\u000a"}) => {"a"=>"\n"},
|
61
|
+
%q({"a":"Line1\u000aLine2"}) => {"a"=>"Line1\nLine2"},
|
62
|
+
# prevent json unmarshalling
|
63
|
+
%q({"json_class":"TestJSONDecoding::Foo"}) => {"json_class"=>"TestJSONDecoding::Foo"},
|
64
|
+
# json "fragments" - these are invalid JSON, but ActionPack relies on this
|
65
|
+
%q("a string") => "a string",
|
66
|
+
%q(1.1) => 1.1,
|
67
|
+
%q(1) => 1,
|
68
|
+
%q(-1) => -1,
|
69
|
+
%q(true) => true,
|
70
|
+
%q(false) => false,
|
71
|
+
%q(null) => nil
|
72
|
+
}
|
73
|
+
|
74
|
+
TESTS.each_with_index do |(json, expected), index|
|
75
|
+
test "json decodes #{index}" do
|
76
|
+
prev = ActiveSupport.parse_json_times
|
77
|
+
ActiveSupport.parse_json_times = true
|
78
|
+
silence_warnings do
|
79
|
+
assert_equal expected, ActiveSupport::JSON.decode(json), "JSON decoding \
|
80
|
+
failed for #{json}"
|
81
|
+
end
|
82
|
+
ActiveSupport.parse_json_times = prev
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
test "json decodes time json with time parsing disabled" do
|
87
|
+
prev = ActiveSupport.parse_json_times
|
88
|
+
ActiveSupport.parse_json_times = false
|
89
|
+
expected = {"a" => "2007-01-01 01:12:34 Z"}
|
90
|
+
assert_equal expected, ActiveSupport::JSON.decode(%({"a": "2007-01-01 01:12:34 Z"}))
|
91
|
+
ActiveSupport.parse_json_times = prev
|
92
|
+
end
|
93
|
+
|
94
|
+
def test_failed_json_decoding
|
95
|
+
assert_raise(ActiveSupport::JSON.parse_error) { ActiveSupport::JSON.decode(%(undefined)) }
|
96
|
+
assert_raise(ActiveSupport::JSON.parse_error) { ActiveSupport::JSON.decode(%({a: 1})) }
|
97
|
+
assert_raise(ActiveSupport::JSON.parse_error) { ActiveSupport::JSON.decode(%({: 1})) }
|
98
|
+
assert_raise(ActiveSupport::JSON.parse_error) { ActiveSupport::JSON.decode(%()) }
|
99
|
+
end
|
100
|
+
|
101
|
+
def test_cannot_pass_unsupported_options
|
102
|
+
assert_raise(ArgumentError) { ActiveSupport::JSON.decode("", create_additions: true) }
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
@@ -0,0 +1,531 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'activesupport4/test_helper'
|
3
|
+
require 'securerandom'
|
4
|
+
require 'active_support/core_ext/string/inflections'
|
5
|
+
require 'active_support/json'
|
6
|
+
require 'active_support/time'
|
7
|
+
|
8
|
+
|
9
|
+
class TestJSONEncoding < ActiveSupport::TestCase
|
10
|
+
class Foo
|
11
|
+
def initialize(a, b)
|
12
|
+
@a, @b = a, b
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class Hashlike
|
17
|
+
def to_hash
|
18
|
+
{ :foo => "hello", :bar => "world" }
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class Custom
|
23
|
+
def initialize(serialized)
|
24
|
+
@serialized = serialized
|
25
|
+
end
|
26
|
+
|
27
|
+
def as_json(options = nil)
|
28
|
+
@serialized
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
class CustomWithOptions
|
33
|
+
attr_accessor :foo, :bar
|
34
|
+
|
35
|
+
def as_json(options={})
|
36
|
+
options[:only] = %w(foo bar)
|
37
|
+
super(options)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
class OptionsTest
|
42
|
+
def as_json(options = :default)
|
43
|
+
options
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
class HashWithAsJson < Hash
|
48
|
+
attr_accessor :as_json_called
|
49
|
+
|
50
|
+
def initialize(*)
|
51
|
+
super
|
52
|
+
end
|
53
|
+
|
54
|
+
def as_json(options={})
|
55
|
+
@as_json_called = true
|
56
|
+
super
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
TrueTests = [[ true, %(true) ]]
|
61
|
+
FalseTests = [[ false, %(false) ]]
|
62
|
+
NilTests = [[ nil, %(null) ]]
|
63
|
+
NumericTests = [[ 1, %(1) ],
|
64
|
+
[ 2.5, %(2.5) ],
|
65
|
+
[ 0.0/0.0, %(null) ],
|
66
|
+
[ 1.0/0.0, %(null) ],
|
67
|
+
[ -1.0/0.0, %(null) ],
|
68
|
+
[ BigDecimal('0.0')/BigDecimal('0.0'), %(null) ],
|
69
|
+
[ BigDecimal('2.5'), %("#{BigDecimal('2.5').to_s}") ]]
|
70
|
+
|
71
|
+
StringTests = [[ 'this is the <string>', %("this is the \\u003cstring\\u003e")],
|
72
|
+
[ 'a "string" with quotes & an ampersand', %("a \\"string\\" with quotes \\u0026 an ampersand") ],
|
73
|
+
[ 'http://test.host/posts/1', %("http://test.host/posts/1")],
|
74
|
+
[ "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",
|
75
|
+
%("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") ]]
|
76
|
+
|
77
|
+
ArrayTests = [[ ['a', 'b', 'c'], %([\"a\",\"b\",\"c\"]) ],
|
78
|
+
[ [1, 'a', :b, nil, false], %([1,\"a\",\"b\",null,false]) ]]
|
79
|
+
|
80
|
+
RangeTests = [[ 1..2, %("1..2")],
|
81
|
+
[ 1...2, %("1...2")],
|
82
|
+
[ 1.5..2.5, %("1.5..2.5")]]
|
83
|
+
|
84
|
+
SymbolTests = [[ :a, %("a") ],
|
85
|
+
[ :this, %("this") ],
|
86
|
+
[ :"a b", %("a b") ]]
|
87
|
+
|
88
|
+
ObjectTests = [[ Foo.new(1, 2), %({\"a\":1,\"b\":2}) ]]
|
89
|
+
HashlikeTests = [[ Hashlike.new, %({\"bar\":\"world\",\"foo\":\"hello\"}) ]]
|
90
|
+
CustomTests = [[ Custom.new("custom"), '"custom"' ],
|
91
|
+
[ Custom.new(nil), 'null' ],
|
92
|
+
[ Custom.new(:a), '"a"' ],
|
93
|
+
[ Custom.new([ :foo, "bar" ]), '["foo","bar"]' ],
|
94
|
+
[ Custom.new({ :foo => "hello", :bar => "world" }), '{"bar":"world","foo":"hello"}' ],
|
95
|
+
[ Custom.new(Hashlike.new), '{"bar":"world","foo":"hello"}' ],
|
96
|
+
[ Custom.new(Custom.new(Custom.new(:a))), '"a"' ]]
|
97
|
+
|
98
|
+
RegexpTests = [[ /^a/, '"(?-mix:^a)"' ], [/^\w{1,2}[a-z]+/ix, '"(?ix-m:^\\\\w{1,2}[a-z]+)"']]
|
99
|
+
|
100
|
+
DateTests = [[ Date.new(2005,2,1), %("2005/02/01") ]]
|
101
|
+
TimeTests = [[ Time.utc(2005,2,1,15,15,10), %("2005/02/01 15:15:10 +0000") ]]
|
102
|
+
DateTimeTests = [[ DateTime.civil(2005,2,1,15,15,10), %("2005/02/01 15:15:10 +0000") ]]
|
103
|
+
|
104
|
+
StandardDateTests = [[ Date.new(2005,2,1), %("2005-02-01") ]]
|
105
|
+
StandardTimeTests = [[ Time.utc(2005,2,1,15,15,10), %("2005-02-01T15:15:10.000Z") ]]
|
106
|
+
StandardDateTimeTests = [[ DateTime.civil(2005,2,1,15,15,10), %("2005-02-01T15:15:10.000+00:00") ]]
|
107
|
+
StandardStringTests = [[ 'this is the <string>', %("this is the <string>")]]
|
108
|
+
|
109
|
+
def sorted_json(json)
|
110
|
+
return json unless json =~ /^\{.*\}$/
|
111
|
+
'{' + json[1..-2].split(',').sort.join(',') + '}'
|
112
|
+
end
|
113
|
+
|
114
|
+
constants.grep(/Tests$/).each do |class_tests|
|
115
|
+
define_method("test_#{class_tests[0..-6].underscore}") do
|
116
|
+
begin
|
117
|
+
prev = ActiveSupport.use_standard_json_time_format
|
118
|
+
|
119
|
+
ActiveSupport.escape_html_entities_in_json = class_tests !~ /^Standard/
|
120
|
+
ActiveSupport.use_standard_json_time_format = class_tests =~ /^Standard/
|
121
|
+
self.class.const_get(class_tests).each do |pair|
|
122
|
+
assert_equal pair.last, sorted_json(ActiveSupport::JSON.encode(pair.first))
|
123
|
+
end
|
124
|
+
ensure
|
125
|
+
ActiveSupport.escape_html_entities_in_json = false
|
126
|
+
ActiveSupport.use_standard_json_time_format = prev
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def test_process_status
|
132
|
+
# There doesn't seem to be a good way to get a handle on a Process::Status object without actually
|
133
|
+
# creating a child process, hence this to populate $?
|
134
|
+
system("not_a_real_program_#{SecureRandom.hex}")
|
135
|
+
assert_equal %({"exitstatus":#{$?.exitstatus},"pid":#{$?.pid}}), ActiveSupport::JSON.encode($?)
|
136
|
+
end
|
137
|
+
|
138
|
+
def test_hash_encoding
|
139
|
+
assert_equal %({\"a\":\"b\"}), ActiveSupport::JSON.encode(:a => :b)
|
140
|
+
assert_equal %({\"a\":1}), ActiveSupport::JSON.encode('a' => 1)
|
141
|
+
assert_equal %({\"a\":[1,2]}), ActiveSupport::JSON.encode('a' => [1,2])
|
142
|
+
assert_equal %({"1":2}), ActiveSupport::JSON.encode(1 => 2)
|
143
|
+
|
144
|
+
assert_equal %({\"a\":\"b\",\"c\":\"d\"}), sorted_json(ActiveSupport::JSON.encode(:a => :b, :c => :d))
|
145
|
+
end
|
146
|
+
|
147
|
+
def test_hash_keys_encoding
|
148
|
+
ActiveSupport.escape_html_entities_in_json = true
|
149
|
+
assert_equal "{\"\\u003c\\u003e\":\"\\u003c\\u003e\"}", ActiveSupport::JSON.encode("<>" => "<>")
|
150
|
+
ensure
|
151
|
+
ActiveSupport.escape_html_entities_in_json = false
|
152
|
+
end
|
153
|
+
|
154
|
+
def test_utf8_string_encoded_properly
|
155
|
+
result = ActiveSupport::JSON.encode('€2.99')
|
156
|
+
assert_equal '"€2.99"', result
|
157
|
+
assert_equal(Encoding::UTF_8, result.encoding)
|
158
|
+
|
159
|
+
result = ActiveSupport::JSON.encode('✎☺')
|
160
|
+
assert_equal '"✎☺"', result
|
161
|
+
assert_equal(Encoding::UTF_8, result.encoding)
|
162
|
+
end
|
163
|
+
|
164
|
+
def test_non_utf8_string_transcodes
|
165
|
+
s = '二'.encode('Shift_JIS')
|
166
|
+
result = ActiveSupport::JSON.encode(s)
|
167
|
+
assert_equal '"二"', result
|
168
|
+
assert_equal Encoding::UTF_8, result.encoding
|
169
|
+
end
|
170
|
+
|
171
|
+
def test_wide_utf8_chars
|
172
|
+
w = '𠜎'
|
173
|
+
result = ActiveSupport::JSON.encode(w)
|
174
|
+
assert_equal '"𠜎"', result
|
175
|
+
end
|
176
|
+
|
177
|
+
def test_wide_utf8_roundtrip
|
178
|
+
hash = { string: "𐒑" }
|
179
|
+
json = ActiveSupport::JSON.encode(hash)
|
180
|
+
decoded_hash = ActiveSupport::JSON.decode(json)
|
181
|
+
assert_equal "𐒑", decoded_hash['string']
|
182
|
+
end
|
183
|
+
|
184
|
+
def test_reading_encode_big_decimal_as_string_option
|
185
|
+
assert_deprecated do
|
186
|
+
assert ActiveSupport.encode_big_decimal_as_string
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
def test_setting_deprecated_encode_big_decimal_as_string_option
|
191
|
+
assert_raise(NotImplementedError) do
|
192
|
+
ActiveSupport.encode_big_decimal_as_string = true
|
193
|
+
end
|
194
|
+
|
195
|
+
assert_raise(NotImplementedError) do
|
196
|
+
ActiveSupport.encode_big_decimal_as_string = false
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
def test_exception_raised_when_encoding_circular_reference_in_array
|
201
|
+
a = [1]
|
202
|
+
a << a
|
203
|
+
assert_deprecated do
|
204
|
+
assert_raise(ActiveSupport::JSON::Encoding::CircularReferenceError) { ActiveSupport::JSON.encode(a) }
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
def test_exception_raised_when_encoding_circular_reference_in_hash
|
209
|
+
a = { :name => 'foo' }
|
210
|
+
a[:next] = a
|
211
|
+
assert_deprecated do
|
212
|
+
assert_raise(ActiveSupport::JSON::Encoding::CircularReferenceError) { ActiveSupport::JSON.encode(a) }
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
def test_exception_raised_when_encoding_circular_reference_in_hash_inside_array
|
217
|
+
a = { :name => 'foo', :sub => [] }
|
218
|
+
a[:sub] << a
|
219
|
+
assert_deprecated do
|
220
|
+
assert_raise(ActiveSupport::JSON::Encoding::CircularReferenceError) { ActiveSupport::JSON.encode(a) }
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
def test_hash_key_identifiers_are_always_quoted
|
225
|
+
values = {0 => 0, 1 => 1, :_ => :_, "$" => "$", "a" => "a", :A => :A, :A0 => :A0, "A0B" => "A0B"}
|
226
|
+
assert_equal %w( "$" "A" "A0" "A0B" "_" "a" "0" "1" ).sort, object_keys(ActiveSupport::JSON.encode(values))
|
227
|
+
end
|
228
|
+
|
229
|
+
def test_hash_should_allow_key_filtering_with_only
|
230
|
+
assert_equal %({"a":1}), ActiveSupport::JSON.encode({'a' => 1, :b => 2, :c => 3}, :only => 'a')
|
231
|
+
end
|
232
|
+
|
233
|
+
def test_hash_should_allow_key_filtering_with_except
|
234
|
+
assert_equal %({"b":2}), ActiveSupport::JSON.encode({'foo' => 'bar', :b => 2, :c => 3}, :except => ['foo', :c])
|
235
|
+
end
|
236
|
+
|
237
|
+
def test_time_to_json_includes_local_offset
|
238
|
+
with_standard_json_time_format(true) do
|
239
|
+
with_env_tz 'US/Eastern' do
|
240
|
+
assert_equal %("2005-02-01T15:15:10.000-05:00"), ActiveSupport::JSON.encode(Time.local(2005,2,1,15,15,10))
|
241
|
+
end
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
def test_hash_with_time_to_json
|
246
|
+
with_standard_json_time_format(false) do
|
247
|
+
assert_equal '{"time":"2009/01/01 00:00:00 +0000"}', { :time => Time.utc(2009) }.to_json
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
def test_nested_hash_with_float
|
252
|
+
assert_nothing_raised do
|
253
|
+
hash = {
|
254
|
+
"CHI" => {
|
255
|
+
:display_name => "chicago",
|
256
|
+
:latitude => 123.234
|
257
|
+
}
|
258
|
+
}
|
259
|
+
ActiveSupport::JSON.encode(hash)
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
def test_hash_like_with_options
|
264
|
+
h = Hashlike.new
|
265
|
+
json = h.to_json :only => [:foo]
|
266
|
+
|
267
|
+
assert_equal({"foo"=>"hello"}, JSON.parse(json))
|
268
|
+
end
|
269
|
+
|
270
|
+
def test_object_to_json_with_options
|
271
|
+
obj = Object.new
|
272
|
+
obj.instance_variable_set :@foo, "hello"
|
273
|
+
obj.instance_variable_set :@bar, "world"
|
274
|
+
json = obj.to_json :only => ["foo"]
|
275
|
+
|
276
|
+
assert_equal({"foo"=>"hello"}, JSON.parse(json))
|
277
|
+
end
|
278
|
+
|
279
|
+
def test_struct_to_json_with_options
|
280
|
+
struct = Struct.new(:foo, :bar).new
|
281
|
+
struct.foo = "hello"
|
282
|
+
struct.bar = "world"
|
283
|
+
json = struct.to_json :only => [:foo]
|
284
|
+
|
285
|
+
assert_equal({"foo"=>"hello"}, JSON.parse(json))
|
286
|
+
end
|
287
|
+
|
288
|
+
def test_hash_should_pass_encoding_options_to_children_in_as_json
|
289
|
+
person = {
|
290
|
+
:name => 'John',
|
291
|
+
:address => {
|
292
|
+
:city => 'London',
|
293
|
+
:country => 'UK'
|
294
|
+
}
|
295
|
+
}
|
296
|
+
json = person.as_json :only => [:address, :city]
|
297
|
+
|
298
|
+
assert_equal({ 'address' => { 'city' => 'London' }}, json)
|
299
|
+
end
|
300
|
+
|
301
|
+
def test_hash_should_pass_encoding_options_to_children_in_to_json
|
302
|
+
person = {
|
303
|
+
:name => 'John',
|
304
|
+
:address => {
|
305
|
+
:city => 'London',
|
306
|
+
:country => 'UK'
|
307
|
+
}
|
308
|
+
}
|
309
|
+
json = person.to_json :only => [:address, :city]
|
310
|
+
|
311
|
+
assert_equal(%({"address":{"city":"London"}}), json)
|
312
|
+
end
|
313
|
+
|
314
|
+
def test_array_should_pass_encoding_options_to_children_in_as_json
|
315
|
+
people = [
|
316
|
+
{ :name => 'John', :address => { :city => 'London', :country => 'UK' }},
|
317
|
+
{ :name => 'Jean', :address => { :city => 'Paris' , :country => 'France' }}
|
318
|
+
]
|
319
|
+
json = people.as_json :only => [:address, :city]
|
320
|
+
expected = [
|
321
|
+
{ 'address' => { 'city' => 'London' }},
|
322
|
+
{ 'address' => { 'city' => 'Paris' }}
|
323
|
+
]
|
324
|
+
|
325
|
+
assert_equal(expected, json)
|
326
|
+
end
|
327
|
+
|
328
|
+
def test_array_should_pass_encoding_options_to_children_in_to_json
|
329
|
+
people = [
|
330
|
+
{ :name => 'John', :address => { :city => 'London', :country => 'UK' }},
|
331
|
+
{ :name => 'Jean', :address => { :city => 'Paris' , :country => 'France' }}
|
332
|
+
]
|
333
|
+
json = people.to_json :only => [:address, :city]
|
334
|
+
|
335
|
+
assert_equal(%([{"address":{"city":"London"}},{"address":{"city":"Paris"}}]), json)
|
336
|
+
end
|
337
|
+
|
338
|
+
def test_enumerable_should_pass_encoding_options_to_children_in_as_json
|
339
|
+
people = [
|
340
|
+
{ :name => 'John', :address => { :city => 'London', :country => 'UK' }},
|
341
|
+
{ :name => 'Jean', :address => { :city => 'Paris' , :country => 'France' }}
|
342
|
+
]
|
343
|
+
json = people.each.as_json :only => [:address, :city]
|
344
|
+
expected = [
|
345
|
+
{ 'address' => { 'city' => 'London' }},
|
346
|
+
{ 'address' => { 'city' => 'Paris' }}
|
347
|
+
]
|
348
|
+
|
349
|
+
assert_equal(expected, json)
|
350
|
+
end
|
351
|
+
|
352
|
+
def test_enumerable_should_pass_encoding_options_to_children_in_to_json
|
353
|
+
people = [
|
354
|
+
{ :name => 'John', :address => { :city => 'London', :country => 'UK' }},
|
355
|
+
{ :name => 'Jean', :address => { :city => 'Paris' , :country => 'France' }}
|
356
|
+
]
|
357
|
+
json = people.each.to_json :only => [:address, :city]
|
358
|
+
|
359
|
+
assert_equal(%([{"address":{"city":"London"}},{"address":{"city":"Paris"}}]), json)
|
360
|
+
end
|
361
|
+
|
362
|
+
def test_hash_to_json_should_not_keep_options_around
|
363
|
+
f = CustomWithOptions.new
|
364
|
+
f.foo = "hello"
|
365
|
+
f.bar = "world"
|
366
|
+
|
367
|
+
hash = {"foo" => f, "other_hash" => {"foo" => "other_foo", "test" => "other_test"}}
|
368
|
+
assert_equal({"foo"=>{"foo"=>"hello","bar"=>"world"},
|
369
|
+
"other_hash" => {"foo"=>"other_foo","test"=>"other_test"}}, ActiveSupport::JSON.decode(hash.to_json))
|
370
|
+
end
|
371
|
+
|
372
|
+
def test_array_to_json_should_not_keep_options_around
|
373
|
+
f = CustomWithOptions.new
|
374
|
+
f.foo = "hello"
|
375
|
+
f.bar = "world"
|
376
|
+
|
377
|
+
array = [f, {"foo" => "other_foo", "test" => "other_test"}]
|
378
|
+
assert_equal([{"foo"=>"hello","bar"=>"world"},
|
379
|
+
{"foo"=>"other_foo","test"=>"other_test"}], ActiveSupport::JSON.decode(array.to_json))
|
380
|
+
end
|
381
|
+
|
382
|
+
def test_hash_as_json_without_options
|
383
|
+
json = { foo: OptionsTest.new }.as_json
|
384
|
+
assert_equal({"foo" => :default}, json)
|
385
|
+
end
|
386
|
+
|
387
|
+
def test_array_as_json_without_options
|
388
|
+
json = [ OptionsTest.new ].as_json
|
389
|
+
assert_equal([:default], json)
|
390
|
+
end
|
391
|
+
|
392
|
+
def test_struct_encoding
|
393
|
+
Struct.new('UserNameAndEmail', :name, :email)
|
394
|
+
Struct.new('UserNameAndDate', :name, :date)
|
395
|
+
Struct.new('Custom', :name, :sub)
|
396
|
+
user_email = Struct::UserNameAndEmail.new 'David', 'sample@example.com'
|
397
|
+
user_birthday = Struct::UserNameAndDate.new 'David', Date.new(2010, 01, 01)
|
398
|
+
custom = Struct::Custom.new 'David', user_birthday
|
399
|
+
|
400
|
+
|
401
|
+
json_strings = ""
|
402
|
+
json_string_and_date = ""
|
403
|
+
json_custom = ""
|
404
|
+
|
405
|
+
assert_nothing_raised do
|
406
|
+
json_strings = user_email.to_json
|
407
|
+
json_string_and_date = user_birthday.to_json
|
408
|
+
json_custom = custom.to_json
|
409
|
+
end
|
410
|
+
|
411
|
+
assert_equal({"name" => "David",
|
412
|
+
"sub" => {
|
413
|
+
"name" => "David",
|
414
|
+
"date" => "2010-01-01" }}, ActiveSupport::JSON.decode(json_custom))
|
415
|
+
|
416
|
+
assert_equal({"name" => "David", "email" => "sample@example.com"},
|
417
|
+
ActiveSupport::JSON.decode(json_strings))
|
418
|
+
|
419
|
+
assert_equal({"name" => "David", "date" => "2010-01-01"},
|
420
|
+
ActiveSupport::JSON.decode(json_string_and_date))
|
421
|
+
end
|
422
|
+
|
423
|
+
def test_nil_true_and_false_represented_as_themselves
|
424
|
+
assert_equal nil, nil.as_json
|
425
|
+
assert_equal true, true.as_json
|
426
|
+
assert_equal false, false.as_json
|
427
|
+
end
|
428
|
+
|
429
|
+
def test_json_gem_dump_by_passing_active_support_encoder
|
430
|
+
h = HashWithAsJson.new
|
431
|
+
h[:foo] = "hello"
|
432
|
+
h[:bar] = "world"
|
433
|
+
|
434
|
+
assert_equal %({"foo":"hello","bar":"world"}), JSON.dump(h)
|
435
|
+
assert_nil h.as_json_called
|
436
|
+
end
|
437
|
+
|
438
|
+
def test_json_gem_generate_by_passing_active_support_encoder
|
439
|
+
h = HashWithAsJson.new
|
440
|
+
h[:foo] = "hello"
|
441
|
+
h[:bar] = "world"
|
442
|
+
|
443
|
+
assert_equal %({"foo":"hello","bar":"world"}), JSON.generate(h)
|
444
|
+
assert_nil h.as_json_called
|
445
|
+
end
|
446
|
+
|
447
|
+
def test_json_gem_pretty_generate_by_passing_active_support_encoder
|
448
|
+
h = HashWithAsJson.new
|
449
|
+
h[:foo] = "hello"
|
450
|
+
h[:bar] = "world"
|
451
|
+
|
452
|
+
assert_equal <<EXPECTED.chomp, JSON.pretty_generate(h)
|
453
|
+
{
|
454
|
+
"foo": "hello",
|
455
|
+
"bar": "world"
|
456
|
+
}
|
457
|
+
EXPECTED
|
458
|
+
assert_nil h.as_json_called
|
459
|
+
end
|
460
|
+
|
461
|
+
def test_twz_to_json_with_use_standard_json_time_format_config_set_to_false
|
462
|
+
with_standard_json_time_format(false) do
|
463
|
+
zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)']
|
464
|
+
time = ActiveSupport::TimeWithZone.new(Time.utc(2000), zone)
|
465
|
+
assert_equal "\"1999/12/31 19:00:00 -0500\"", ActiveSupport::JSON.encode(time)
|
466
|
+
end
|
467
|
+
end
|
468
|
+
|
469
|
+
def test_twz_to_json_with_use_standard_json_time_format_config_set_to_true
|
470
|
+
with_standard_json_time_format(true) do
|
471
|
+
zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)']
|
472
|
+
time = ActiveSupport::TimeWithZone.new(Time.utc(2000), zone)
|
473
|
+
assert_equal "\"1999-12-31T19:00:00.000-05:00\"", ActiveSupport::JSON.encode(time)
|
474
|
+
end
|
475
|
+
end
|
476
|
+
|
477
|
+
def test_twz_to_json_with_custom_time_precision
|
478
|
+
with_standard_json_time_format(true) do
|
479
|
+
ActiveSupport::JSON::Encoding.time_precision = 0
|
480
|
+
zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)']
|
481
|
+
time = ActiveSupport::TimeWithZone.new(Time.utc(2000), zone)
|
482
|
+
assert_equal "\"1999-12-31T19:00:00-05:00\"", ActiveSupport::JSON.encode(time)
|
483
|
+
end
|
484
|
+
ensure
|
485
|
+
ActiveSupport::JSON::Encoding.time_precision = 3
|
486
|
+
end
|
487
|
+
|
488
|
+
def test_time_to_json_with_custom_time_precision
|
489
|
+
with_standard_json_time_format(true) do
|
490
|
+
ActiveSupport::JSON::Encoding.time_precision = 0
|
491
|
+
assert_equal "\"2000-01-01T00:00:00Z\"", ActiveSupport::JSON.encode(Time.utc(2000))
|
492
|
+
end
|
493
|
+
ensure
|
494
|
+
ActiveSupport::JSON::Encoding.time_precision = 3
|
495
|
+
end
|
496
|
+
|
497
|
+
def test_datetime_to_json_with_custom_time_precision
|
498
|
+
with_standard_json_time_format(true) do
|
499
|
+
ActiveSupport::JSON::Encoding.time_precision = 0
|
500
|
+
assert_equal "\"2000-01-01T00:00:00+00:00\"", ActiveSupport::JSON.encode(DateTime.new(2000))
|
501
|
+
end
|
502
|
+
ensure
|
503
|
+
ActiveSupport::JSON::Encoding.time_precision = 3
|
504
|
+
end
|
505
|
+
|
506
|
+
def test_twz_to_json_when_wrapping_a_date_time
|
507
|
+
zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)']
|
508
|
+
time = ActiveSupport::TimeWithZone.new(DateTime.new(2000), zone)
|
509
|
+
assert_equal '"1999-12-31T19:00:00.000-05:00"', ActiveSupport::JSON.encode(time)
|
510
|
+
end
|
511
|
+
|
512
|
+
protected
|
513
|
+
|
514
|
+
def object_keys(json_object)
|
515
|
+
json_object[1..-2].scan(/([^{}:,\s]+):/).flatten.sort
|
516
|
+
end
|
517
|
+
|
518
|
+
def with_env_tz(new_tz = 'US/Eastern')
|
519
|
+
old_tz, ENV['TZ'] = ENV['TZ'], new_tz
|
520
|
+
yield
|
521
|
+
ensure
|
522
|
+
old_tz ? ENV['TZ'] = old_tz : ENV.delete('TZ')
|
523
|
+
end
|
524
|
+
|
525
|
+
def with_standard_json_time_format(boolean = true)
|
526
|
+
old, ActiveSupport.use_standard_json_time_format = ActiveSupport.use_standard_json_time_format, boolean
|
527
|
+
yield
|
528
|
+
ensure
|
529
|
+
ActiveSupport.use_standard_json_time_format = old
|
530
|
+
end
|
531
|
+
end
|