ffi-yajl 0.1.7 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/bin/ffi-yajl-bench +1 -0
- data/ext/ffi_yajl/ext/parser/parser.c +50 -23
- data/lib/ffi_yajl/benchmark/encode.rb +12 -7
- data/lib/ffi_yajl/benchmark/parse.rb +13 -0
- data/lib/ffi_yajl/ext.rb +1 -0
- data/lib/ffi_yajl/ffi.rb +1 -1
- data/lib/ffi_yajl/ffi/parser.rb +21 -3
- data/lib/ffi_yajl/json_gem.rb +1 -2
- data/lib/ffi_yajl/parser.rb +20 -1
- data/lib/ffi_yajl/version.rb +1 -1
- data/spec/ffi_yajl/json_gem_spec.rb +21 -27
- data/spec/ffi_yajl/parser_spec.rb +444 -50
- data/spec/spec_helper.rb +1 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ad912796d62ff75534ab7056d42be6ddb3b340bd
|
4
|
+
data.tar.gz: 1368094d6809554303390c8dc38e33b837d6da5d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7e7d9eaedc22292ea2817b75cba4fa04c90cf6ca7032675a0d8b400e4b1f7df8bc7120ff3b9a0952c55f8c7c3129d6279f92f5ae97671368979cf9f0af99c024
|
7
|
+
data.tar.gz: 690d495c1fe962fefae46cb73520e8690151ab40424cb2ebca0f162fe2b5bbcf1cac481be5b0ce73f408e4c03eaa676ae7284601e0b992e6b85c8e13449e1919
|
data/bin/ffi-yajl-bench
CHANGED
@@ -10,6 +10,7 @@ static VALUE mFFI_Yajl, mExt, mParser, cParseError;
|
|
10
10
|
|
11
11
|
typedef struct {
|
12
12
|
VALUE self;
|
13
|
+
int symbolizeKeys;
|
13
14
|
} CTX;
|
14
15
|
|
15
16
|
void set_value(CTX *ctx, VALUE val) {
|
@@ -74,7 +75,7 @@ int double_callback(void *ctx, double doubleVal) {
|
|
74
75
|
}
|
75
76
|
|
76
77
|
int number_callback(void *ctx, const char *numberVal, size_t numberLen) {
|
77
|
-
char buf
|
78
|
+
char *buf = (char *)malloc(numberLen+1);
|
78
79
|
buf[numberLen] = 0;
|
79
80
|
memcpy(buf, numberVal, numberLen);
|
80
81
|
if (memchr(buf, '.', numberLen) ||
|
@@ -84,21 +85,14 @@ int number_callback(void *ctx, const char *numberVal, size_t numberLen) {
|
|
84
85
|
} else {
|
85
86
|
set_value(ctx, rb_cstr2inum(buf, 10));
|
86
87
|
}
|
88
|
+
free(buf);
|
87
89
|
return 1;
|
88
90
|
}
|
89
91
|
|
90
92
|
int string_callback(void *ctx, const unsigned char *stringVal, size_t stringLen) {
|
91
|
-
char
|
92
|
-
VALUE str;
|
93
|
+
VALUE str = rb_str_new((const char *)stringVal, stringLen);
|
93
94
|
#ifdef HAVE_RUBY_ENCODING_H
|
94
|
-
rb_encoding *default_internal_enc;
|
95
|
-
#endif
|
96
|
-
|
97
|
-
buf[stringLen] = 0;
|
98
|
-
memcpy(buf, stringVal, stringLen);
|
99
|
-
str = rb_str_new2(buf);
|
100
|
-
#ifdef HAVE_RUBY_ENCODING_H
|
101
|
-
default_internal_enc = rb_default_internal_encoding();
|
95
|
+
rb_encoding *default_internal_enc = rb_default_internal_encoding();
|
102
96
|
rb_enc_associate(str, utf8Encoding);
|
103
97
|
if (default_internal_enc) {
|
104
98
|
str = rb_str_export_to_enc(str, default_internal_enc);
|
@@ -114,23 +108,30 @@ int start_map_callback(void *ctx) {
|
|
114
108
|
}
|
115
109
|
|
116
110
|
int map_key_callback(void *ctx, const unsigned char *stringVal, size_t stringLen) {
|
117
|
-
|
118
|
-
VALUE str;
|
111
|
+
VALUE key;
|
119
112
|
#ifdef HAVE_RUBY_ENCODING_H
|
120
113
|
rb_encoding *default_internal_enc;
|
121
114
|
#endif
|
122
115
|
|
123
|
-
|
124
|
-
memcpy(buf, stringVal, stringLen);
|
125
|
-
str = rb_str_new2(buf);
|
116
|
+
if ( ((CTX *)ctx)->symbolizeKeys ) {
|
126
117
|
#ifdef HAVE_RUBY_ENCODING_H
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
str =
|
131
|
-
|
118
|
+
ID id = rb_intern3((const char *)stringVal, stringLen, utf8Encoding);
|
119
|
+
key = ID2SYM(id);
|
120
|
+
#else
|
121
|
+
VALUE str = rb_str_new((const char *)stringVal, stringLen);
|
122
|
+
key = rb_str_intern(str);
|
123
|
+
#endif
|
124
|
+
} else {
|
125
|
+
key = rb_str_new((const char *)stringVal, stringLen);
|
126
|
+
#ifdef HAVE_RUBY_ENCODING_H
|
127
|
+
default_internal_enc = rb_default_internal_encoding();
|
128
|
+
rb_enc_associate(key, utf8Encoding);
|
129
|
+
if (default_internal_enc) {
|
130
|
+
key = rb_str_export_to_enc(key, default_internal_enc);
|
131
|
+
}
|
132
132
|
#endif
|
133
|
-
|
133
|
+
}
|
134
|
+
set_key(ctx, key);
|
134
135
|
return 1;
|
135
136
|
}
|
136
137
|
|
@@ -163,7 +164,15 @@ static yajl_callbacks callbacks = {
|
|
163
164
|
end_array_callback,
|
164
165
|
};
|
165
166
|
|
166
|
-
|
167
|
+
int get_opts_key(VALUE self, char *key) {
|
168
|
+
VALUE opts = rb_iv_get(self, "@opts");
|
169
|
+
if (TYPE(opts) != T_HASH) {
|
170
|
+
rb_raise(rb_eTypeError, "opts is not a valid hash");
|
171
|
+
}
|
172
|
+
return rb_hash_aref(opts, ID2SYM(rb_intern(key))) == Qtrue;
|
173
|
+
}
|
174
|
+
|
175
|
+
static VALUE mParser_do_yajl_parse(VALUE self, VALUE str, VALUE yajl_opts) {
|
167
176
|
yajl_handle hand;
|
168
177
|
yajl_status stat;
|
169
178
|
unsigned char *err;
|
@@ -173,8 +182,26 @@ static VALUE mParser_do_yajl_parse(VALUE self, VALUE str, VALUE opts) {
|
|
173
182
|
rb_ivar_set(self, rb_intern("key_stack"), rb_ary_new());
|
174
183
|
|
175
184
|
ctx.self = self;
|
185
|
+
ctx.symbolizeKeys = get_opts_key(self, "symbolize_keys");
|
176
186
|
|
177
187
|
hand = yajl_alloc(&callbacks, NULL, (void *)&ctx);
|
188
|
+
|
189
|
+
if (rb_hash_aref(yajl_opts, ID2SYM(rb_intern("yajl_allow_comments"))) == Qtrue) {
|
190
|
+
yajl_config(hand, yajl_allow_comments, 1);
|
191
|
+
}
|
192
|
+
if (rb_hash_aref(yajl_opts, ID2SYM(rb_intern("yajl_dont_validate_strings"))) == Qtrue) {
|
193
|
+
yajl_config(hand, yajl_dont_validate_strings, 1);
|
194
|
+
}
|
195
|
+
if (rb_hash_aref(yajl_opts, ID2SYM(rb_intern("yajl_allow_trailing_garbage"))) == Qtrue) {
|
196
|
+
yajl_config(hand, yajl_allow_trailing_garbage, 1);
|
197
|
+
}
|
198
|
+
if (rb_hash_aref(yajl_opts, ID2SYM(rb_intern("yajl_allow_multiple_values"))) == Qtrue) {
|
199
|
+
yajl_config(hand, yajl_allow_multiple_values, 1);
|
200
|
+
}
|
201
|
+
if (rb_hash_aref(yajl_opts, ID2SYM(rb_intern("yajl_allow_partial_values"))) == Qtrue) {
|
202
|
+
yajl_config(hand, yajl_allow_partial_values, 1);
|
203
|
+
}
|
204
|
+
|
178
205
|
if ((stat = yajl_parse(hand, (unsigned char *)RSTRING_PTR(str), RSTRING_LEN(str))) != yajl_status_ok) {
|
179
206
|
err = yajl_get_error(hand, 1, (unsigned char *)RSTRING_PTR(str), RSTRING_LEN(str));
|
180
207
|
goto raise;
|
@@ -101,6 +101,11 @@ module FFI_Yajl
|
|
101
101
|
JSON.generate(hash)
|
102
102
|
}
|
103
103
|
}
|
104
|
+
x.report("JSON.fast_generate") {
|
105
|
+
times.times {
|
106
|
+
JSON.fast_generate(hash)
|
107
|
+
}
|
108
|
+
}
|
104
109
|
end
|
105
110
|
if defined?(Psych)
|
106
111
|
x.report("Psych.to_json") {
|
@@ -120,13 +125,13 @@ module FFI_Yajl
|
|
120
125
|
}
|
121
126
|
end
|
122
127
|
end
|
123
|
-
if defined?(ActiveSupport::JSON)
|
124
|
-
x.report("ActiveSupport::JSON.encode") {
|
125
|
-
times.times {
|
126
|
-
ActiveSupport::JSON.encode(hash)
|
127
|
-
}
|
128
|
-
}
|
129
|
-
end
|
128
|
+
# if defined?(ActiveSupport::JSON)
|
129
|
+
# x.report("ActiveSupport::JSON.encode") {
|
130
|
+
# times.times {
|
131
|
+
# ActiveSupport::JSON.encode(hash)
|
132
|
+
# }
|
133
|
+
# }
|
134
|
+
# end
|
130
135
|
}
|
131
136
|
end
|
132
137
|
end
|
@@ -28,6 +28,10 @@ begin
|
|
28
28
|
require 'active_support'
|
29
29
|
rescue LoadError
|
30
30
|
end
|
31
|
+
begin
|
32
|
+
require 'oj'
|
33
|
+
rescue LoadError
|
34
|
+
end
|
31
35
|
|
32
36
|
class FFI_Yajl::Benchmark::Parse
|
33
37
|
|
@@ -79,6 +83,15 @@ class FFI_Yajl::Benchmark::Parse
|
|
79
83
|
}
|
80
84
|
}
|
81
85
|
end
|
86
|
+
if defined?(Oj)
|
87
|
+
x.report {
|
88
|
+
puts "Oj.load"
|
89
|
+
times.times {
|
90
|
+
json.rewind
|
91
|
+
Oj.load(json.read)
|
92
|
+
}
|
93
|
+
}
|
94
|
+
end
|
82
95
|
if defined?(JSON)
|
83
96
|
x.report {
|
84
97
|
puts "JSON.parse"
|
data/lib/ffi_yajl/ext.rb
CHANGED
data/lib/ffi_yajl/ffi.rb
CHANGED
@@ -87,7 +87,7 @@ module FFI_Yajl
|
|
87
87
|
attach_function :yajl_free_error, [:yajl_handle, :ustring], :void
|
88
88
|
|
89
89
|
#
|
90
|
-
attach_function :yajl_config, [:yajl_handle, :yajl_option, :
|
90
|
+
attach_function :yajl_config, [:yajl_handle, :yajl_option, :varargs], :int
|
91
91
|
|
92
92
|
attach_function :yajl_gen_config, [:yajl_gen, :yajl_gen_option, :varargs], :int
|
93
93
|
|
data/lib/ffi_yajl/ffi/parser.rb
CHANGED
@@ -51,7 +51,7 @@ module FFI_Yajl
|
|
51
51
|
s = stringval.slice(0,stringlen)
|
52
52
|
s.force_encoding('UTF-8') if defined? Encoding
|
53
53
|
# XXX: I can't think of a better way to do this right now. need to call to_f if and only if its a float.
|
54
|
-
v = ( s =~
|
54
|
+
v = ( s =~ /[\.eE]/ ) ? s.to_f : s.to_i
|
55
55
|
set_value(v)
|
56
56
|
1
|
57
57
|
end
|
@@ -73,7 +73,7 @@ module FFI_Yajl
|
|
73
73
|
@map_key_callback = ::FFI::Function.new(:int, [:pointer, :string, :size_t]) do |ctx, key, keylen|
|
74
74
|
s = key.slice(0,keylen)
|
75
75
|
s.force_encoding('UTF-8') if defined? Encoding
|
76
|
-
self.key = s
|
76
|
+
self.key = @opts[:symbolize_keys] ? s.to_sym : s
|
77
77
|
1
|
78
78
|
end
|
79
79
|
@end_map_callback = ::FFI::Function.new(:int, [:pointer]) do |ctx|
|
@@ -94,7 +94,7 @@ module FFI_Yajl
|
|
94
94
|
end
|
95
95
|
|
96
96
|
|
97
|
-
def do_yajl_parse(str,
|
97
|
+
def do_yajl_parse(str, yajl_opts = {})
|
98
98
|
setup_callbacks
|
99
99
|
callback_ptr = ::FFI::MemoryPointer.new(::FFI_Yajl::YajlCallbacks)
|
100
100
|
callbacks = ::FFI_Yajl::YajlCallbacks.new(callback_ptr)
|
@@ -110,6 +110,24 @@ module FFI_Yajl
|
|
110
110
|
callbacks[:yajl_start_array] = @start_array_callback
|
111
111
|
callbacks[:yajl_end_array] = @end_array_callback
|
112
112
|
yajl_handle = ::FFI_Yajl.yajl_alloc(callback_ptr, nil, nil)
|
113
|
+
|
114
|
+
# configure the yajl parser
|
115
|
+
if yajl_opts[:yajl_allow_comments]
|
116
|
+
::FFI_Yajl.yajl_config(yajl_handle, :yajl_allow_comments, :int, 1)
|
117
|
+
end
|
118
|
+
if yajl_opts[:yajl_dont_validate_strings]
|
119
|
+
::FFI_Yajl.yajl_config(yajl_handle, :yajl_dont_validate_strings, :int, 1)
|
120
|
+
end
|
121
|
+
if yajl_opts[:yajl_allow_trailing_garbage]
|
122
|
+
::FFI_Yajl.yajl_config(yajl_handle, :yajl_allow_trailing_garbage, :int, 1)
|
123
|
+
end
|
124
|
+
if yajl_opts[:yajl_allow_multiple_values]
|
125
|
+
::FFI_Yajl.yajl_config(yajl_handle, :yajl_allow_multiple_values, :int, 1)
|
126
|
+
end
|
127
|
+
if yajl_opts[:yajl_allow_partial_values]
|
128
|
+
::FFI_Yajl.yajl_config(yajl_handle, :yajl_allow_partial_values, :int, 1)
|
129
|
+
end
|
130
|
+
|
113
131
|
if ( stat = ::FFI_Yajl.yajl_parse(yajl_handle, str, str.bytesize) != :yajl_status_ok )
|
114
132
|
# FIXME: dup the error and call yajl_free_error?
|
115
133
|
error = ::FFI_Yajl.yajl_get_error(yajl_handle, 1, str, str.bytesize)
|
data/lib/ffi_yajl/json_gem.rb
CHANGED
data/lib/ffi_yajl/parser.rb
CHANGED
@@ -25,16 +25,35 @@ module FFI_Yajl
|
|
25
25
|
end
|
26
26
|
|
27
27
|
def initialize(opts = {})
|
28
|
-
@opts = opts
|
28
|
+
@opts = opts ? opts.dup : {}
|
29
|
+
# JSON gem uses 'symbolize_names' and ruby-yajl supports this as well
|
30
|
+
@opts[:symbolize_keys] = true if @opts[:symbolize_names]
|
29
31
|
end
|
30
32
|
|
31
33
|
def parse(str)
|
32
34
|
# initialization that we can do in pure ruby
|
33
35
|
yajl_opts = {}
|
34
36
|
|
37
|
+
if @opts[:check_utf8] == false && @opts[:dont_validate_strings] == false
|
38
|
+
raise ArgumentError, "options check_utf8 and dont_validate_strings are both false which conflict"
|
39
|
+
end
|
40
|
+
if @opts[:check_utf8] == true && @opts[:dont_validate_strings] == true
|
41
|
+
raise ArgumentError, "options check_utf8 and dont_validate_strings are both true which conflict"
|
42
|
+
end
|
43
|
+
|
44
|
+
yajl_opts[:yajl_allow_comments] = @opts[:allow_comments]
|
45
|
+
yajl_opts[:yajl_dont_validate_strings] = (@opts[:check_utf8] == false || @opts[:dont_validate_strings])
|
46
|
+
yajl_opts[:yajl_allow_trailing_garbage] = @opts[:allow_trailing_garbage]
|
47
|
+
yajl_opts[:yajl_allow_multiple_values] = @opts[:allow_multiple_values]
|
48
|
+
yajl_opts[:yajl_allow_partial_values] = @opts[:allow_partial_values]
|
49
|
+
|
35
50
|
# XXX: bug-compat with ruby-yajl
|
36
51
|
return nil if str == ""
|
37
52
|
|
53
|
+
if str.respond_to?(:read)
|
54
|
+
str = str.read()
|
55
|
+
end
|
56
|
+
|
38
57
|
# call either the ext or ffi hook
|
39
58
|
do_yajl_parse(str, yajl_opts)
|
40
59
|
end
|
data/lib/ffi_yajl/version.rb
CHANGED
@@ -11,7 +11,7 @@ describe "JSON Gem Compat API" do
|
|
11
11
|
|
12
12
|
# Magic to make the before loading tests actually run before loading
|
13
13
|
RSpec.configure do |config|
|
14
|
-
config.
|
14
|
+
config.register_ordering(:global) do |list|
|
15
15
|
list.sort_by { |item| item.description }
|
16
16
|
end
|
17
17
|
end
|
@@ -19,39 +19,39 @@ describe "JSON Gem Compat API" do
|
|
19
19
|
context "A: before loading the compat library" do
|
20
20
|
it "should not mixin #to_json on random objects" do
|
21
21
|
d = Dummy.new
|
22
|
-
expect(d.respond_to?(:to_json)).to
|
22
|
+
expect(d.respond_to?(:to_json)).to be false
|
23
23
|
end
|
24
24
|
|
25
25
|
it "should not mixin #to_json to a string" do
|
26
|
-
expect("".respond_to?(:to_json)).to
|
26
|
+
expect("".respond_to?(:to_json)).to be false
|
27
27
|
end
|
28
28
|
|
29
29
|
it "should not mixin #to_json to a fixnum" do
|
30
|
-
expect(1.respond_to?(:to_json)).to
|
30
|
+
expect(1.respond_to?(:to_json)).to be false
|
31
31
|
end
|
32
32
|
|
33
33
|
it "should not mixin #to_json on a float" do
|
34
|
-
expect("1.5".to_f.respond_to?(:to_json)).to
|
34
|
+
expect("1.5".to_f.respond_to?(:to_json)).to be false
|
35
35
|
end
|
36
36
|
|
37
37
|
it "should not mixin #to_json on an array" do
|
38
|
-
expect([].respond_to?(:to_json)).to
|
38
|
+
expect([].respond_to?(:to_json)).to be false
|
39
39
|
end
|
40
40
|
|
41
41
|
it "should not mixin #to_json on a hash" do
|
42
|
-
expect({ :foo => "bar" }.respond_to?(:to_json)).to
|
42
|
+
expect({ :foo => "bar" }.respond_to?(:to_json)).to be false
|
43
43
|
end
|
44
44
|
|
45
45
|
it "should not mixin #to_json on a trueclass" do
|
46
|
-
expect(true.respond_to?(:to_json)).to
|
46
|
+
expect(true.respond_to?(:to_json)).to be false
|
47
47
|
end
|
48
48
|
|
49
49
|
it "should not mixin #to_json on a falseclass" do
|
50
|
-
expect(false.respond_to?(:to_json)).to
|
50
|
+
expect(false.respond_to?(:to_json)).to be false
|
51
51
|
end
|
52
52
|
|
53
53
|
it "should not mixin #to_json on a nilclass" do
|
54
|
-
expect(nil.respond_to?(:to_json)).to
|
54
|
+
expect(nil.respond_to?(:to_json)).to be false
|
55
55
|
end
|
56
56
|
end
|
57
57
|
|
@@ -62,66 +62,60 @@ describe "JSON Gem Compat API" do
|
|
62
62
|
end
|
63
63
|
|
64
64
|
it "should define JSON class" do
|
65
|
-
expect(defined?(JSON)).to
|
65
|
+
expect(defined?(JSON)).to be_truthy
|
66
66
|
end
|
67
67
|
|
68
68
|
it "should implement JSON#parse" do
|
69
|
-
expect(JSON.respond_to?(:parse)).to
|
69
|
+
expect(JSON.respond_to?(:parse)).to be true
|
70
70
|
end
|
71
71
|
|
72
72
|
it "should implement JSON#generate" do
|
73
|
-
expect(JSON.respond_to?(:generate)).to
|
73
|
+
expect(JSON.respond_to?(:generate)).to be true
|
74
74
|
end
|
75
75
|
|
76
76
|
it "should implement JSON#pretty_generate" do
|
77
|
-
expect(JSON.respond_to?(:pretty_generate)).to
|
77
|
+
expect(JSON.respond_to?(:pretty_generate)).to be true
|
78
78
|
end
|
79
79
|
|
80
80
|
it "should implement JSON#load" do
|
81
|
-
expect(JSON.respond_to?(:load)).to
|
81
|
+
expect(JSON.respond_to?(:load)).to be true
|
82
82
|
end
|
83
83
|
|
84
84
|
it "should implement JSON#dump" do
|
85
|
-
expect(JSON.respond_to?(:dump)).to
|
85
|
+
expect(JSON.respond_to?(:dump)).to be true
|
86
86
|
end
|
87
87
|
|
88
88
|
context "when setting symbolize_keys via JSON.default_options" do
|
89
|
-
|
90
|
-
after(:all) { JSON.default_options[:symbolize_keys] = @saved_default }
|
89
|
+
after(:each) { JSON.default_options[:symbolize_keys] = false }
|
91
90
|
|
92
91
|
it "the default behavior should be to not symbolize keys" do
|
93
92
|
expect(JSON.parse('{"foo": 1234}')).to eq( "foo" => 1234 )
|
94
93
|
end
|
95
94
|
|
96
95
|
it "changing the default_options should change the behavior to true" do
|
97
|
-
skip("implement symbolize keys")
|
98
96
|
JSON.default_options[:symbolize_keys] = true
|
99
97
|
expect(JSON.parse('{"foo": 1234}')).to eq( :foo => 1234 )
|
100
98
|
end
|
101
99
|
end
|
102
100
|
|
103
101
|
context "when setting symbolize_names via JSON.default_options" do
|
104
|
-
|
105
|
-
after(:all) { JSON.default_options[:symbolize_names] = @saved_default }
|
102
|
+
after { JSON.default_options.delete(:symbolize_names)}
|
106
103
|
|
107
104
|
it "the default behavior should be to not symbolize keys" do
|
108
105
|
expect(JSON.parse('{"foo": 1234}')).to eq( "foo" => 1234 )
|
109
106
|
end
|
110
107
|
|
111
108
|
it "changing the default_options should change the behavior to true" do
|
112
|
-
skip("implement symbolize keys")
|
113
109
|
JSON.default_options[:symbolize_names] = true
|
114
110
|
expect(JSON.parse('{"foo": 1234}')).to eq( :foo => 1234 )
|
115
111
|
end
|
116
112
|
end
|
117
113
|
|
118
114
|
it "should support passing symbolize_names to JSON.parse" do
|
119
|
-
skip("implement symbolize keys")
|
120
115
|
expect(JSON.parse('{"foo": 1234}', :symbolize_names => true)).to eq( :foo => 1234 )
|
121
116
|
end
|
122
117
|
|
123
118
|
it "should support passing symbolize_keys to JSON.parse" do
|
124
|
-
skip("implement symbolize keys")
|
125
119
|
expect(JSON.parse('{"foo": 1234}', :symbolize_keys => true)).to eq( :foo => 1234 )
|
126
120
|
end
|
127
121
|
|
@@ -149,13 +143,13 @@ describe "JSON Gem Compat API" do
|
|
149
143
|
|
150
144
|
context "JSON exception classes" do
|
151
145
|
it "should define JSON::JSONError as a StandardError" do
|
152
|
-
expect(JSON::JSONError.new.is_a?(StandardError)).to
|
146
|
+
expect(JSON::JSONError.new.is_a?(StandardError)).to be true
|
153
147
|
end
|
154
148
|
it "should define JSON::ParserError as a JSON::JSONError" do
|
155
|
-
expect(JSON::ParserError.new.is_a?(JSON::JSONError)).to
|
149
|
+
expect(JSON::ParserError.new.is_a?(JSON::JSONError)).to be true
|
156
150
|
end
|
157
151
|
it "should define JSON::GeneratorError as a JSON::JSONError" do
|
158
|
-
expect(JSON::GeneratorError.new.is_a?(JSON::JSONError)).to
|
152
|
+
expect(JSON::GeneratorError.new.is_a?(JSON::JSONError)).to be true
|
159
153
|
end
|
160
154
|
it "should raise JSON::ParserError on a bad parse" do
|
161
155
|
expect{ JSON.parse("blah") }.to raise_error(JSON::ParserError)
|
@@ -4,79 +4,473 @@ require 'spec_helper'
|
|
4
4
|
|
5
5
|
describe "FFI_Yajl::Parser" do
|
6
6
|
|
7
|
-
|
7
|
+
shared_examples_for "correct json parsing" do
|
8
|
+
context "when json has 23456789012E666" do
|
9
|
+
let(:json) { '{"key": 23456789012E666}' }
|
8
10
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
11
|
+
it "should return infinity" do
|
12
|
+
infinity = (1.0/0)
|
13
|
+
expect(parser).to eq({"key" => infinity})
|
14
|
+
end
|
15
|
+
end
|
13
16
|
|
14
|
-
|
15
|
-
|
16
|
-
expect { parser.parse(json) }.to raise_error(FFI_Yajl::ParseError)
|
17
|
-
end
|
17
|
+
context "when json has comments" do
|
18
|
+
let(:json) { '{"key": /* this is a comment */ "value"}' }
|
18
19
|
|
19
|
-
|
20
|
-
|
21
|
-
expect { parser.parse(json) }.to raise_error(FFI_Yajl::ParseError)
|
22
|
-
end
|
20
|
+
context "when allow_comments is false" do
|
21
|
+
let(:options) { { :allow_comments => false } }
|
23
22
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
23
|
+
it "should not parse" do
|
24
|
+
expect{parser}.to raise_error(FFI_Yajl::ParseError)
|
25
|
+
end
|
26
|
+
end
|
28
27
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
28
|
+
context "when allow_comments is true" do
|
29
|
+
let(:options) { { :allow_comments => true } }
|
30
|
+
|
31
|
+
it "should parse" do
|
32
|
+
expect(parser).to eq({"key"=>"value"})
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
context "when json has multiline comments" do
|
38
|
+
let(:json) { %Q{{"key": \n/*\n this is a multiline comment \n*/\n "value"}} }
|
39
|
+
|
40
|
+
context "when allow_comments is false" do
|
41
|
+
let(:options) { { :allow_comments => false } }
|
42
|
+
|
43
|
+
it "should not parse" do
|
44
|
+
expect{parser}.to raise_error(FFI_Yajl::ParseError)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
context "when allow_comments is true" do
|
49
|
+
let(:options) { { :allow_comments => true } }
|
50
|
+
|
51
|
+
it "should parse" do
|
52
|
+
expect(parser).to eq({"key"=>"value"})
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
context "when json has inline comments" do
|
58
|
+
let(:json) { %Q{{"key": \n// this is an inline comment\n "value"}} }
|
59
|
+
|
60
|
+
context "when allow_comments is false" do
|
61
|
+
let(:options) { { :allow_comments => false } }
|
62
|
+
|
63
|
+
it "should not parse" do
|
64
|
+
expect{parser}.to raise_error(FFI_Yajl::ParseError)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
context "when allow_comments is true" do
|
69
|
+
let(:options) { { :allow_comments => true } }
|
70
|
+
|
71
|
+
it "should parse" do
|
72
|
+
expect(parser).to eq({"key"=>"value"})
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
context "when json is invalid UTF8" do
|
78
|
+
let(:json) { "[\"#{"\201\203"}\"]" }
|
79
|
+
|
80
|
+
it "should not parse by default" do
|
81
|
+
expect{parser}.to raise_error(FFI_Yajl::ParseError)
|
82
|
+
end
|
83
|
+
|
84
|
+
context "when :dont_validate_strings is set to true" do
|
85
|
+
let(:options) { { :dont_validate_strings => true } }
|
86
|
+
|
87
|
+
it "should parse" do
|
88
|
+
expect(parser).to eq(["\x81\x83"])
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
context "when :dont_validate_strings is set to false" do
|
93
|
+
let(:options) { { :dont_validate_strings => false } }
|
94
|
+
|
95
|
+
it "should not parse" do
|
96
|
+
expect{parser}.to raise_error(FFI_Yajl::ParseError)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
context "when :check_utf8 is set to true" do
|
101
|
+
let(:options) { { :check_utf8 => true } }
|
102
|
+
|
103
|
+
it "should not parse" do
|
104
|
+
expect{parser}.to raise_error(FFI_Yajl::ParseError)
|
105
|
+
end
|
106
|
+
|
107
|
+
context "when :dont_validate_strings is set to true" do
|
108
|
+
let(:options) { { :check_utf8 => true, :dont_validate_strings => true } }
|
109
|
+
|
110
|
+
it "should raise an ArgumentError" do
|
111
|
+
expect{parser}.to raise_error(ArgumentError)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
context "when :dont_validate_strings is set to false" do
|
116
|
+
let(:options) { { :check_utf8 => true, :dont_validate_strings => false } }
|
117
|
+
|
118
|
+
it "should not parse" do
|
119
|
+
expect{parser}.to raise_error(FFI_Yajl::ParseError)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
context "when :check_utf8 is set to false" do
|
125
|
+
let(:options) { { :check_utf8 => false } }
|
126
|
+
|
127
|
+
it "should parse" do
|
128
|
+
expect(parser).to eq(["\x81\x83"])
|
129
|
+
end
|
130
|
+
|
131
|
+
context "when :dont_validate_strings is set to true" do
|
132
|
+
let(:options) { { :check_utf8 => false, :dont_validate_strings => true } }
|
133
|
+
|
134
|
+
it "should parse" do
|
135
|
+
expect(parser).to eq(["\x81\x83"])
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
context "when :dont_validate_strings is set to false" do
|
140
|
+
let(:options) { { :check_utf8 => false, :dont_validate_strings => false } }
|
141
|
+
|
142
|
+
it "should raise an ArgumentError" do
|
143
|
+
expect{parser}.to raise_error(ArgumentError)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
context "when JSON is a StringIO" do
|
150
|
+
let(:json) { StringIO.new('{"key": 1234}') }
|
151
|
+
|
152
|
+
it "should parse" do
|
153
|
+
expect(parser).to eq({"key" => 1234})
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
context "when parsing a JSON string" do
|
158
|
+
let(:json) { '{"key": 1234}' }
|
159
|
+
|
160
|
+
it "should parse correctly" do
|
161
|
+
expect(parser).to eq({"key" => 1234})
|
162
|
+
end
|
163
|
+
|
164
|
+
context "when symbolize_keys is true" do
|
165
|
+
let(:options) { { :symbolize_keys => true } }
|
166
|
+
|
167
|
+
it "should symbolize keys correctly" do
|
168
|
+
expect(parser).to eq({:key => 1234})
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
context "when passing a block" do
|
173
|
+
it "should parse correctly" do
|
174
|
+
skip "handle blocks"
|
175
|
+
output = nil
|
176
|
+
parser do |obj|
|
177
|
+
output = obj
|
178
|
+
end
|
179
|
+
expect(output).to eq({"key" => 1234})
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
context "when parsing a JSON hash with only strings" do
|
185
|
+
let(:json) { '{"key": "value"}' }
|
186
|
+
|
187
|
+
if RUBY_VERSION.to_f >= 1.9
|
188
|
+
context "when Encoding.default_internal is nil" do
|
189
|
+
before do
|
190
|
+
@saved_encoding = Encoding.default_internal
|
191
|
+
Encoding.default_internal = nil
|
192
|
+
end
|
193
|
+
after do
|
194
|
+
Encoding.default_internal = @saved_encoding
|
195
|
+
end
|
196
|
+
it "encodes keys to UTF-8" do
|
197
|
+
expect(parser.keys.first.encoding).to eql(Encoding.find('utf-8'))
|
198
|
+
end
|
199
|
+
it "encodes values to UTF-8" do
|
200
|
+
expect(parser.values.first.encoding).to eql(Encoding.find('utf-8'))
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
%w{utf-8 us-ascii}.each do |encoding|
|
205
|
+
context "when Encoding.default_internal is #{encoding}" do
|
206
|
+
before do
|
207
|
+
@saved_encoding = Encoding.default_internal
|
208
|
+
Encoding.default_internal = nil
|
209
|
+
end
|
210
|
+
after do
|
211
|
+
Encoding.default_internal = @saved_encoding
|
212
|
+
end
|
213
|
+
it "encodes keys to #{encoding}" do
|
214
|
+
skip "fix us-ascii" if encoding == "us-ascii"
|
215
|
+
expect(parser.keys.first.encoding).to eql(Encoding.find(encoding))
|
216
|
+
end
|
217
|
+
it "encodes values to #{encoding}" do
|
218
|
+
skip "fix us-ascii" if encoding == "us-ascii"
|
219
|
+
expect(parser.values.first.encoding).to eql(Encoding.find(encoding))
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
context "when a parsed key has utf-8 multibyte characters" do
|
227
|
+
let(:json) { '{"日本語": 1234}' }
|
228
|
+
|
229
|
+
it "should parse correctly" do
|
230
|
+
expect(parser).to eq({"日本語" => 1234})
|
231
|
+
end
|
232
|
+
|
233
|
+
context "when symbolize_keys is true" do
|
234
|
+
let(:options) { { :symbolize_keys => true } }
|
235
|
+
|
236
|
+
it "should symbolize keys correctly" do
|
237
|
+
expect(parser).to eq({:"日本語" => 1234})
|
238
|
+
end
|
33
239
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
240
|
+
if RUBY_VERSION.to_f >= 1.9
|
241
|
+
it "should parse non-ascii symbols in UTF-8" do
|
242
|
+
expect(parser.keys.fetch(0).encoding).to eq(Encoding::UTF_8)
|
243
|
+
end
|
244
|
+
end
|
245
|
+
end
|
38
246
|
end
|
39
247
|
|
40
|
-
|
41
|
-
json
|
42
|
-
|
248
|
+
context "when parsing 2147483649" do
|
249
|
+
let(:json) { "{\"id\": 2147483649}" }
|
250
|
+
|
251
|
+
it "should parse corectly" do
|
252
|
+
expect(parser).to eql({"id" => 2147483649})
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
context "when parsing 5687389800" do
|
257
|
+
let(:json) { "{\"id\": 5687389800}" }
|
258
|
+
|
259
|
+
it "should parse corectly" do
|
260
|
+
expect(parser).to eql({"id" => 5687389800})
|
261
|
+
end
|
262
|
+
end
|
263
|
+
|
264
|
+
context "when parsing 1046289770033519442869495707521600000000" do
|
265
|
+
let(:json) { "{\"id\": 1046289770033519442869495707521600000000}" }
|
266
|
+
|
267
|
+
it "should parse corectly" do
|
268
|
+
expect(parser).to eql({"id" => 1046289770033519442869495707521600000000})
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
# NOTE: we are choosing to be compatible with yajl-ruby here vs. JSON
|
273
|
+
# gem and libyajl C behavior (which is to throw an exception in this case)
|
274
|
+
context "when the JSON is empty string" do
|
275
|
+
let(:json) { '' }
|
276
|
+
|
277
|
+
it "returns nil" do
|
278
|
+
expect(parser).to be_nil
|
279
|
+
end
|
280
|
+
end
|
281
|
+
|
282
|
+
# NOTE: this fixes yajl-ruby being too permissive
|
283
|
+
context "when dealing with too much or too little input" do
|
284
|
+
context "when trailing braces are missing" do
|
285
|
+
let(:json) { '{"foo":{"foo": 1234}' }
|
286
|
+
|
287
|
+
it "raises an exception" do
|
288
|
+
expect { parser }.to raise_error(FFI_Yajl::ParseError)
|
289
|
+
end
|
290
|
+
end
|
291
|
+
|
292
|
+
context "when trailing brackets are missing" do
|
293
|
+
let(:json) { '[["foo", "bar"]' }
|
294
|
+
|
295
|
+
it "raises an exception" do
|
296
|
+
expect { parser }.to raise_error(FFI_Yajl::ParseError)
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
context "when an extra brace is present" do
|
301
|
+
let(:json) { '{"foo":{"foo": 1234}}}' }
|
302
|
+
|
303
|
+
it "raises an exception" do
|
304
|
+
expect { parser }.to raise_error(FFI_Yajl::ParseError)
|
305
|
+
end
|
306
|
+
|
307
|
+
context "with allow_trailing_garbage" do
|
308
|
+
let(:options) { { :allow_trailing_garbage => true } }
|
309
|
+
it "parses" do
|
310
|
+
expect(parser).to eq({"foo"=>{"foo"=>1234}})
|
311
|
+
end
|
312
|
+
end
|
313
|
+
|
314
|
+
end
|
315
|
+
|
316
|
+
context "when an extra bracket is present" do
|
317
|
+
let(:json) { '[["foo", "bar"]]]' }
|
318
|
+
|
319
|
+
it "raises an exception" do
|
320
|
+
expect { parser }.to raise_error(FFI_Yajl::ParseError)
|
321
|
+
end
|
322
|
+
end
|
323
|
+
end
|
324
|
+
|
325
|
+
context "when parsing heavy metal umlauts in keys" do
|
326
|
+
let(:json) { '{"München": "Bayern"}' }
|
327
|
+
|
328
|
+
it "correctly parses" do
|
329
|
+
expect(parser).to eql( "München" => "Bayern" )
|
330
|
+
end
|
331
|
+
end
|
332
|
+
|
333
|
+
context "when parsing floats" do
|
334
|
+
context "parses simple floating point values" do
|
335
|
+
let(:json) { '{"foo": 3.14159265358979}' }
|
336
|
+
|
337
|
+
it "correctly parses" do
|
338
|
+
expect(parser).to eql( "foo" => 3.14159265358979 )
|
339
|
+
end
|
340
|
+
end
|
341
|
+
|
342
|
+
context "parses simple negative floating point values" do
|
343
|
+
let(:json) { '{"foo":-2.00231930436153}' }
|
344
|
+
|
345
|
+
it "correctly parses" do
|
346
|
+
expect(parser).to eql( "foo" => -2.00231930436153 )
|
347
|
+
end
|
348
|
+
end
|
349
|
+
|
350
|
+
context "parses floats with negative exponents and a large E" do
|
351
|
+
let(:json) { '{"foo": 1.602176565E-19}' }
|
352
|
+
|
353
|
+
it "correctly parses" do
|
354
|
+
expect(parser).to eql( "foo" => 1.602176565e-19 )
|
355
|
+
end
|
356
|
+
end
|
357
|
+
|
358
|
+
context "parses floats with negative exponents and a small e" do
|
359
|
+
let(:json) { '{"foo": 6.6260689633e-34 }' }
|
360
|
+
|
361
|
+
it "correctly parses" do
|
362
|
+
expect(parser).to eql( "foo" => 6.6260689633e-34 )
|
363
|
+
end
|
364
|
+
end
|
365
|
+
|
366
|
+
context "parses floats with positive exponents and a large E" do
|
367
|
+
let(:json) { '{"foo": 6.0221413E+23}' }
|
368
|
+
|
369
|
+
it "correctly parses" do
|
370
|
+
expect(parser).to eql( "foo" => 6.0221413e+23 )
|
371
|
+
end
|
372
|
+
end
|
373
|
+
|
374
|
+
context "parses floats with positive exponents and a small e" do
|
375
|
+
let(:json) { '{"foo": 8.9875517873681764e+9 }' }
|
376
|
+
|
377
|
+
it "correctly parses" do
|
378
|
+
expect(parser).to eql( "foo" => 8.9875517873681764e+9 )
|
379
|
+
end
|
380
|
+
end
|
381
|
+
|
382
|
+
context "parses floats with an exponent without a sign and a large E" do
|
383
|
+
let(:json) { '{"foo": 2.99792458E8 }' }
|
384
|
+
|
385
|
+
it "correctly parses" do
|
386
|
+
expect(parser).to eql( "foo" => 2.99792458e+8 )
|
387
|
+
end
|
388
|
+
end
|
389
|
+
|
390
|
+
context "parses floats with an exponent without a sign and a small e" do
|
391
|
+
let(:json) { '{"foo": 1.0973731568539e7 }' }
|
392
|
+
|
393
|
+
it "correctly parses" do
|
394
|
+
expect(parser).to eql( "foo" => 1.0973731568539e+7 )
|
395
|
+
end
|
396
|
+
end
|
43
397
|
end
|
44
398
|
|
45
|
-
|
46
|
-
json
|
47
|
-
|
399
|
+
context "when parsing big floats", :ruby_gte_193 => true do
|
400
|
+
let(:json) { '[0.' + '1' * 2**23 + ']' }
|
401
|
+
|
402
|
+
it "parses" do
|
403
|
+
expect{ parser }.not_to raise_error
|
404
|
+
end
|
48
405
|
end
|
49
406
|
|
50
|
-
|
51
|
-
json
|
52
|
-
|
407
|
+
context "when parsing long hash keys with symbolize_keys option", :ruby_gte_193 => true do
|
408
|
+
let(:json) { '{"' + 'a' * 2**23 + '": 0}' }
|
409
|
+
let(:options) { { :symbolize_keys => true } }
|
410
|
+
|
411
|
+
it "parses" do
|
412
|
+
expect{ parser }.not_to raise_error
|
413
|
+
end
|
53
414
|
end
|
415
|
+
end
|
416
|
+
|
417
|
+
context "when options are set to empty hash" do
|
418
|
+
let(:options) { {} }
|
419
|
+
|
420
|
+
context "when using a parsing object" do
|
421
|
+
let(:parser) { FFI_Yajl::Parser.new(options).parse(json) }
|
54
422
|
|
55
|
-
|
56
|
-
json = '{"foo": 6.0221413E+23}'
|
57
|
-
expect(parser.parse(json)).to eql( "foo" => 6.0221413e+23 )
|
423
|
+
it_behaves_like "correct json parsing"
|
58
424
|
end
|
59
425
|
|
60
|
-
|
61
|
-
|
62
|
-
|
426
|
+
context "when using the class method" do
|
427
|
+
let(:parser) { FFI_Yajl::Parser.parse(json, options) }
|
428
|
+
|
429
|
+
it_behaves_like "correct json parsing"
|
63
430
|
end
|
431
|
+
end
|
432
|
+
|
433
|
+
context "when options are set to nil" do
|
434
|
+
let(:options) { nil }
|
64
435
|
|
65
|
-
|
66
|
-
|
67
|
-
|
436
|
+
context "when using a parsing object" do
|
437
|
+
let(:parser) { FFI_Yajl::Parser.new(options).parse(json) }
|
438
|
+
|
439
|
+
it_behaves_like "correct json parsing"
|
68
440
|
end
|
69
441
|
|
70
|
-
|
71
|
-
|
72
|
-
|
442
|
+
context "when using the class method" do
|
443
|
+
let(:parser) { FFI_Yajl::Parser.parse(json, options) }
|
444
|
+
|
445
|
+
it_behaves_like "correct json parsing"
|
73
446
|
end
|
74
447
|
end
|
75
448
|
|
76
|
-
context "when
|
77
|
-
|
78
|
-
|
79
|
-
|
449
|
+
context "when options default to nothing" do
|
450
|
+
let(:options) { nil }
|
451
|
+
|
452
|
+
context "when using a parsing object" do
|
453
|
+
let(:parser) do
|
454
|
+
if options.nil?
|
455
|
+
FFI_Yajl::Parser.new.parse(json)
|
456
|
+
else
|
457
|
+
FFI_Yajl::Parser.new(options).parse(json)
|
458
|
+
end
|
459
|
+
end
|
460
|
+
|
461
|
+
it_behaves_like "correct json parsing"
|
462
|
+
end
|
463
|
+
|
464
|
+
context "when using the class method" do
|
465
|
+
let(:parser) do
|
466
|
+
if options.nil?
|
467
|
+
FFI_Yajl::Parser.parse(json)
|
468
|
+
else
|
469
|
+
FFI_Yajl::Parser.parse(json, options)
|
470
|
+
end
|
471
|
+
end
|
472
|
+
|
473
|
+
it_behaves_like "correct json parsing"
|
80
474
|
end
|
81
475
|
end
|
82
476
|
end
|
data/spec/spec_helper.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ffi-yajl
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Lamont Granquist
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-06-
|
11
|
+
date: 2014-06-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake
|