yajl-ruby 1.3.0 → 1.4.1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of yajl-ruby might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/.travis.yml +3 -8
- data/ext/yajl/extconf.rb +1 -1
- data/ext/yajl/yajl_encode.c +41 -5
- data/ext/yajl/yajl_ext.c +443 -12
- data/ext/yajl/yajl_ext.h +2 -2
- data/ext/yajl/yajl_lex.c +9 -9
- data/ext/yajl/yajl_lex.h +16 -15
- data/lib/yajl.rb +7 -0
- data/lib/yajl/bzip2.rb +2 -2
- data/lib/yajl/http_stream.rb +3 -2
- data/lib/yajl/version.rb +1 -1
- data/spec/encoding/encoding_spec.rb +16 -0
- data/spec/parsing/one_off_spec.rb +7 -0
- data/spec/projection/project_file.rb +41 -0
- data/spec/projection/projection.rb +498 -0
- data/yajl-ruby.gemspec +4 -4
- metadata +31 -13
data/ext/yajl/yajl_ext.h
CHANGED
@@ -53,10 +53,10 @@ static rb_encoding *utf8Encoding;
|
|
53
53
|
#define RARRAY_LEN(s) (RARRAY(s)->len)
|
54
54
|
#endif
|
55
55
|
|
56
|
-
static VALUE cParseError, cEncodeError, mYajl, cParser, cEncoder;
|
56
|
+
static VALUE cStandardError, cParseError, cEncodeError, mYajl, cParser, cProjector, cEncoder;
|
57
57
|
static ID intern_io_read, intern_call, intern_keys, intern_to_s,
|
58
58
|
intern_to_json, intern_has_key, intern_to_sym, intern_as_json;
|
59
|
-
static ID sym_allow_comments, sym_check_utf8, sym_pretty, sym_indent, sym_terminator, sym_symbolize_keys, sym_symbolize_names, sym_html_safe;
|
59
|
+
static ID sym_allow_comments, sym_check_utf8, sym_pretty, sym_indent, sym_terminator, sym_symbolize_keys, sym_symbolize_names, sym_html_safe, sym_entities;
|
60
60
|
|
61
61
|
#define GetParser(obj, sval) Data_Get_Struct(obj, yajl_parser_wrapper, sval);
|
62
62
|
#define GetEncoder(obj, sval) Data_Get_Struct(obj, yajl_encoder_wrapper, sval);
|
data/ext/yajl/yajl_lex.c
CHANGED
@@ -38,29 +38,25 @@
|
|
38
38
|
#include <assert.h>
|
39
39
|
#include <string.h>
|
40
40
|
|
41
|
-
|
42
|
-
static const char *
|
43
|
-
tokToStr(yajl_tok tok)
|
44
|
-
{
|
41
|
+
const char *yajl_tok_name(yajl_tok tok) {
|
45
42
|
switch (tok) {
|
46
43
|
case yajl_tok_bool: return "bool";
|
47
44
|
case yajl_tok_colon: return "colon";
|
48
45
|
case yajl_tok_comma: return "comma";
|
49
46
|
case yajl_tok_eof: return "eof";
|
50
47
|
case yajl_tok_error: return "error";
|
51
|
-
case yajl_tok_left_brace: return "
|
52
|
-
case yajl_tok_left_bracket: return "
|
48
|
+
case yajl_tok_left_brace: return "open_array";
|
49
|
+
case yajl_tok_left_bracket: return "open_object";
|
53
50
|
case yajl_tok_null: return "null";
|
54
51
|
case yajl_tok_integer: return "integer";
|
55
52
|
case yajl_tok_double: return "double";
|
56
|
-
case yajl_tok_right_brace: return "
|
57
|
-
case yajl_tok_right_bracket: return "
|
53
|
+
case yajl_tok_right_brace: return "close_array";
|
54
|
+
case yajl_tok_right_bracket: return "close_object";
|
58
55
|
case yajl_tok_string: return "string";
|
59
56
|
case yajl_tok_string_with_escapes: return "string_with_escapes";
|
60
57
|
}
|
61
58
|
return "unknown";
|
62
59
|
}
|
63
|
-
#endif
|
64
60
|
|
65
61
|
/* Impact of the stream parsing feature on the lexer:
|
66
62
|
*
|
@@ -740,6 +736,10 @@ yajl_tok yajl_lex_peek(yajl_lexer lexer, const unsigned char * jsonText,
|
|
740
736
|
tok = yajl_lex_lex(lexer, jsonText, jsonTextLen, &offset,
|
741
737
|
&outBuf, &outLen);
|
742
738
|
|
739
|
+
if (tok == yajl_tok_eof) {
|
740
|
+
return tok;
|
741
|
+
}
|
742
|
+
|
743
743
|
lexer->bufOff = bufOff;
|
744
744
|
lexer->bufInUse = bufInUse;
|
745
745
|
yajl_buf_truncate(lexer->buf, bufLen);
|
data/ext/yajl/yajl_lex.h
CHANGED
@@ -36,33 +36,34 @@
|
|
36
36
|
#include "api/yajl_common.h"
|
37
37
|
|
38
38
|
typedef enum {
|
39
|
-
yajl_tok_bool,
|
40
|
-
yajl_tok_colon,
|
41
|
-
yajl_tok_comma,
|
42
|
-
yajl_tok_eof,
|
43
|
-
yajl_tok_error,
|
44
|
-
yajl_tok_left_brace,
|
45
|
-
yajl_tok_left_bracket,
|
46
|
-
yajl_tok_null,
|
47
|
-
yajl_tok_right_brace,
|
48
|
-
yajl_tok_right_bracket,
|
39
|
+
yajl_tok_bool, // 0
|
40
|
+
yajl_tok_colon, // 1
|
41
|
+
yajl_tok_comma, // 2
|
42
|
+
yajl_tok_eof, // 3
|
43
|
+
yajl_tok_error, // 4
|
44
|
+
yajl_tok_left_brace, // 5
|
45
|
+
yajl_tok_left_bracket, // 6
|
46
|
+
yajl_tok_null, // 7
|
47
|
+
yajl_tok_right_brace, // 8
|
48
|
+
yajl_tok_right_bracket, // 9
|
49
49
|
|
50
50
|
/* we differentiate between integers and doubles to allow the
|
51
51
|
* parser to interpret the number without re-scanning */
|
52
|
-
yajl_tok_integer,
|
53
|
-
yajl_tok_double,
|
52
|
+
yajl_tok_integer, // 10
|
53
|
+
yajl_tok_double, // 11
|
54
54
|
|
55
55
|
/* we differentiate between strings which require further processing,
|
56
56
|
* and strings that do not */
|
57
|
-
yajl_tok_string,
|
58
|
-
yajl_tok_string_with_escapes,
|
57
|
+
yajl_tok_string, // 12
|
58
|
+
yajl_tok_string_with_escapes, // 13
|
59
59
|
|
60
60
|
/* comment tokens are not currently returned to the parser, ever */
|
61
|
-
yajl_tok_comment
|
61
|
+
yajl_tok_comment // 14
|
62
62
|
} yajl_tok;
|
63
63
|
|
64
64
|
typedef struct yajl_lexer_t * yajl_lexer;
|
65
65
|
|
66
|
+
const char *yajl_tok_name(yajl_tok tok);
|
66
67
|
|
67
68
|
YAJL_API
|
68
69
|
yajl_lexer yajl_lex_alloc(yajl_alloc_funcs * alloc,
|
data/lib/yajl.rb
CHANGED
@@ -23,6 +23,13 @@ module Yajl
|
|
23
23
|
Encoder.encode(obj, args, &block)
|
24
24
|
end
|
25
25
|
|
26
|
+
class Projector
|
27
|
+
def initialize(stream, read_bufsize=4096)
|
28
|
+
@stream = stream
|
29
|
+
@buffer_size = read_bufsize
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
26
33
|
class Parser
|
27
34
|
# A helper method for parse-and-forget use-cases
|
28
35
|
#
|
data/lib/yajl/bzip2.rb
CHANGED
data/lib/yajl/http_stream.rb
CHANGED
@@ -4,6 +4,7 @@ require 'socket'
|
|
4
4
|
require 'yajl'
|
5
5
|
require 'yajl/version' unless defined? Yajl::VERSION
|
6
6
|
require 'uri'
|
7
|
+
require 'cgi'
|
7
8
|
|
8
9
|
module Yajl
|
9
10
|
# This module is for making HTTP requests to which the response bodies (and possibly requests in the near future)
|
@@ -101,7 +102,7 @@ module Yajl
|
|
101
102
|
default_headers["Content-Type"] = opts["Content-Type"] || "application/x-www-form-urlencoded"
|
102
103
|
body = opts.delete(:body)
|
103
104
|
if body.is_a?(Hash)
|
104
|
-
body = body.keys.collect {|param| "#{
|
105
|
+
body = body.keys.collect {|param| "#{CGI.escape(param.to_s)}=#{CGI.escape(body[param].to_s)}"}.join('&')
|
105
106
|
end
|
106
107
|
default_headers["Content-Length"] = body.length
|
107
108
|
end
|
@@ -161,7 +162,7 @@ module Yajl
|
|
161
162
|
if block_given?
|
162
163
|
chunkLeft = 0
|
163
164
|
while !socket.eof? && (line = socket.gets)
|
164
|
-
break if line.match
|
165
|
+
break if line.match(/^0.*?\r\n/)
|
165
166
|
next if line == "\r\n"
|
166
167
|
size = line.hex
|
167
168
|
json = socket.read(size)
|
data/lib/yajl/version.rb
CHANGED
@@ -275,11 +275,27 @@ describe "Yajl JSON encoder" do
|
|
275
275
|
expect(safe_encoder.encode("</script>")).to eql("\"<\\/script>\"")
|
276
276
|
end
|
277
277
|
|
278
|
+
it "should not encode characters with entities by default" do
|
279
|
+
expect(Yajl.dump("\u2028\u2029><&")).to eql("\"\u2028\u2029><&\"")
|
280
|
+
end
|
281
|
+
|
282
|
+
it "should encode characters with entities when enabled" do
|
283
|
+
expect(Yajl.dump("\u2028\u2029><&", entities: true)).to eql("\"\\u2028\\u2029\\u003E\\u003C\\u0026\"")
|
284
|
+
end
|
285
|
+
|
278
286
|
it "should default to *not* escaping / characters" do
|
279
287
|
unsafe_encoder = Yajl::Encoder.new
|
280
288
|
expect(unsafe_encoder.encode("</script>")).not_to eql("\"<\\/script>\"")
|
281
289
|
end
|
282
290
|
|
291
|
+
it "should encode slashes when enabled" do
|
292
|
+
unsafe_encoder = Yajl::Encoder.new(:entities => false)
|
293
|
+
safe_encoder = Yajl::Encoder.new(:entities => true)
|
294
|
+
|
295
|
+
expect(unsafe_encoder.encode("</script>")).not_to eql("\"<\\/script>\"")
|
296
|
+
expect(safe_encoder.encode("</script>")).to eql("\"\\u003C\\/script\\u003E\"")
|
297
|
+
end
|
298
|
+
|
283
299
|
it "return value of #to_json must be a string" do
|
284
300
|
expect {
|
285
301
|
Yajl::Encoder.encode(TheMindKiller.new)
|
@@ -2,6 +2,13 @@
|
|
2
2
|
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper.rb')
|
3
3
|
|
4
4
|
describe "One-off JSON examples" do
|
5
|
+
it "should not blow up with a bad surrogate trailer" do
|
6
|
+
# https://github.com/brianmario/yajl-ruby/issues/176
|
7
|
+
bad_json = "{\"e\":{\"\\uD800\\\\DC00\":\"a\"}}"
|
8
|
+
|
9
|
+
Yajl::Parser.new.parse(bad_json)
|
10
|
+
end
|
11
|
+
|
5
12
|
it "should parse 23456789012E666 and return Infinity" do
|
6
13
|
infinity = (1.0/0)
|
7
14
|
silence_warnings do
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper.rb')
|
2
|
+
|
3
|
+
require 'benchmark'
|
4
|
+
require 'benchmark/memory'
|
5
|
+
|
6
|
+
describe "file projection" do
|
7
|
+
it "projects file streams" do
|
8
|
+
schema = {
|
9
|
+
"forced" => nil,
|
10
|
+
"created" => nil,
|
11
|
+
"pusher" => {
|
12
|
+
"name" => nil,
|
13
|
+
},
|
14
|
+
"repository" => {
|
15
|
+
"name" => nil,
|
16
|
+
"full_name" => nil,
|
17
|
+
},
|
18
|
+
"ref" => nil,
|
19
|
+
"compare" => nil,
|
20
|
+
"commits" => {
|
21
|
+
"distinct" => nil,
|
22
|
+
"message" => nil,
|
23
|
+
"url" => nil,
|
24
|
+
"id" => nil,
|
25
|
+
"author" => {
|
26
|
+
"username" => nil,
|
27
|
+
}
|
28
|
+
}
|
29
|
+
}
|
30
|
+
|
31
|
+
file_path = ENV['JSON_FILE']
|
32
|
+
if file_path.nil? || file_path.empty?
|
33
|
+
return
|
34
|
+
end
|
35
|
+
|
36
|
+
Benchmark.memory { |x|
|
37
|
+
x.report("project (yajl)") { Yajl::Projector.new(File.open(file_path, 'r')).project(schema) }
|
38
|
+
x.compare!
|
39
|
+
}
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,498 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper.rb')
|
2
|
+
|
3
|
+
require 'stringio'
|
4
|
+
require 'json'
|
5
|
+
|
6
|
+
describe "projection" do
|
7
|
+
it "should work" do
|
8
|
+
stream = StringIO.new('{"name": "keith", "age": 27}')
|
9
|
+
projector = Yajl::Projector.new(stream)
|
10
|
+
projection = projector.project({"name" => nil})
|
11
|
+
expect(projection['name']).to eql("keith")
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should filter" do
|
15
|
+
stream = StringIO.new('{"name": "keith", "age": 27}')
|
16
|
+
projector = Yajl::Projector.new(stream)
|
17
|
+
projection = projector.project({"name" => nil})
|
18
|
+
expect(projection['age']).to eql(nil)
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should raise an exception and not leak memory" do
|
22
|
+
stream = StringIO.new('foo')
|
23
|
+
projector = Yajl::Projector.new(stream)
|
24
|
+
expect {
|
25
|
+
projector.project({"name" => nil})
|
26
|
+
}.to raise_error(Yajl::ParseError)
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should raise an exception and not segv" do
|
30
|
+
stream = StringIO.new('[,,,,]')
|
31
|
+
projector = Yajl::Projector.new(stream)
|
32
|
+
expect {
|
33
|
+
projector.project({"name" => nil})
|
34
|
+
}.to raise_error(Yajl::ParseError)
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should raise an exception and not segv on colons" do
|
38
|
+
stream = StringIO.new('[::::]')
|
39
|
+
projector = Yajl::Projector.new(stream)
|
40
|
+
expect {
|
41
|
+
projector.project({"name" => nil})
|
42
|
+
}.to raise_error(Yajl::ParseError)
|
43
|
+
end
|
44
|
+
|
45
|
+
it "should behave the same way as the regular parser on bad tokens like comma" do
|
46
|
+
bad_json = '{"name": "keith", "age":, 27}'
|
47
|
+
stream = StringIO.new(bad_json)
|
48
|
+
projector = Yajl::Projector.new(stream)
|
49
|
+
expect {
|
50
|
+
projector.project({"name" => nil})
|
51
|
+
}.to raise_error(capture_exception_for(bad_json).class)
|
52
|
+
end
|
53
|
+
|
54
|
+
it "should behave the same way as the regular parser on bad tokens like colon" do
|
55
|
+
bad_json = '{"name": "keith", "age":: 27}'
|
56
|
+
stream = StringIO.new(bad_json)
|
57
|
+
projector = Yajl::Projector.new(stream)
|
58
|
+
expect {
|
59
|
+
projector.project({"name" => nil})
|
60
|
+
}.to raise_error(capture_exception_for(bad_json).class)
|
61
|
+
end
|
62
|
+
|
63
|
+
it "should behave the same way as the regular parser on not enough json" do
|
64
|
+
bad_json = '{"name": "keith", "age":'
|
65
|
+
stream = StringIO.new(bad_json)
|
66
|
+
projector = Yajl::Projector.new(stream)
|
67
|
+
expect {
|
68
|
+
projector.project({"name" => nil})
|
69
|
+
}.to raise_error(capture_exception_for(bad_json).class)
|
70
|
+
end
|
71
|
+
|
72
|
+
def capture_exception_for(bad_json)
|
73
|
+
Yajl::Parser.new.parse(bad_json)
|
74
|
+
rescue Exception => e
|
75
|
+
e
|
76
|
+
end
|
77
|
+
|
78
|
+
def project(schema, over: "", json: nil, stream: nil)
|
79
|
+
if stream.nil?
|
80
|
+
if json.nil?
|
81
|
+
json = over.to_json
|
82
|
+
end
|
83
|
+
|
84
|
+
stream = StringIO.new(json)
|
85
|
+
end
|
86
|
+
|
87
|
+
Yajl::Projector.new(stream).project(schema)
|
88
|
+
end
|
89
|
+
|
90
|
+
it "filters arrays" do
|
91
|
+
json = {
|
92
|
+
"users" => [
|
93
|
+
{
|
94
|
+
"name" => "keith",
|
95
|
+
"company" => "internet plumbing inc",
|
96
|
+
"department" => "janitorial",
|
97
|
+
},
|
98
|
+
{
|
99
|
+
"name" => "justin",
|
100
|
+
"company" => "big blue",
|
101
|
+
"department" => "programming?",
|
102
|
+
},
|
103
|
+
{
|
104
|
+
"name" => "alan",
|
105
|
+
"company" => "different colour of blue",
|
106
|
+
"department" => "drop bear containment",
|
107
|
+
}
|
108
|
+
]
|
109
|
+
}.to_json
|
110
|
+
|
111
|
+
puts json
|
112
|
+
|
113
|
+
schema = {
|
114
|
+
# /users is an array of objects, each having many keys we only want name
|
115
|
+
"users" => {
|
116
|
+
"name" => nil,
|
117
|
+
}
|
118
|
+
}
|
119
|
+
|
120
|
+
expect(project(schema, json: json)).to eql({
|
121
|
+
"users" => [
|
122
|
+
{ "name" => "keith" },
|
123
|
+
{ "name" => "justin" },
|
124
|
+
{ "name" => "alan" }
|
125
|
+
]
|
126
|
+
})
|
127
|
+
end
|
128
|
+
|
129
|
+
it "filters top level arrays" do
|
130
|
+
json = [
|
131
|
+
{
|
132
|
+
"name" => "keith",
|
133
|
+
"personal detail" => "thing",
|
134
|
+
},
|
135
|
+
{
|
136
|
+
"name" => "cory",
|
137
|
+
"phone number" => "unknown",
|
138
|
+
}
|
139
|
+
]
|
140
|
+
|
141
|
+
schema = {
|
142
|
+
"name" => nil,
|
143
|
+
}
|
144
|
+
|
145
|
+
expect(project(schema, over: json)).to eql([
|
146
|
+
{ "name" => "keith" },
|
147
|
+
{ "name" => "cory" },
|
148
|
+
])
|
149
|
+
end
|
150
|
+
|
151
|
+
it "filters nested schemas" do
|
152
|
+
json = {
|
153
|
+
"foo" => 42,
|
154
|
+
|
155
|
+
"bar" => {
|
156
|
+
"name" => "keith",
|
157
|
+
"occupation" => "professional computering",
|
158
|
+
"age" => 26,
|
159
|
+
"hobbies" => [
|
160
|
+
"not computering",
|
161
|
+
]
|
162
|
+
},
|
163
|
+
|
164
|
+
"qux" => {
|
165
|
+
"quux" => [
|
166
|
+
{
|
167
|
+
"name" => "Reactive X",
|
168
|
+
"members" => "many",
|
169
|
+
},
|
170
|
+
{
|
171
|
+
"name" => "lstoll",
|
172
|
+
"members" => "such",
|
173
|
+
},
|
174
|
+
{
|
175
|
+
"name" => "github",
|
176
|
+
"members" => "very",
|
177
|
+
},
|
178
|
+
{
|
179
|
+
"name" => "theleague",
|
180
|
+
"members" => "numerous",
|
181
|
+
}
|
182
|
+
],
|
183
|
+
|
184
|
+
"corge" => {
|
185
|
+
"name" => "Brighton",
|
186
|
+
"address" =>"Buckingham Road",
|
187
|
+
},
|
188
|
+
},
|
189
|
+
|
190
|
+
"grault" => nil,
|
191
|
+
|
192
|
+
"waldo" => true,
|
193
|
+
}
|
194
|
+
|
195
|
+
schema = {
|
196
|
+
# include the /foo subtree (is a single number)
|
197
|
+
"foo" => nil,
|
198
|
+
|
199
|
+
# ignore the bar subtree (is an object)
|
200
|
+
# "bar" => ???
|
201
|
+
|
202
|
+
# include some of the /qux subtree (is an object)
|
203
|
+
"qux" => {
|
204
|
+
# include the whole /qux/quux subtree (is an array of objects)
|
205
|
+
"quux" => nil,
|
206
|
+
|
207
|
+
# include some of the /qux/corge subtree (is another object)
|
208
|
+
"corge" => {
|
209
|
+
# include name (is a string)
|
210
|
+
"name" => nil,
|
211
|
+
# include age (is missing from source doc)
|
212
|
+
"age" => nil,
|
213
|
+
# ignore address
|
214
|
+
# "address" => ???
|
215
|
+
},
|
216
|
+
},
|
217
|
+
|
218
|
+
# include the /grault subtree (is a null literal)
|
219
|
+
"grault" => nil,
|
220
|
+
|
221
|
+
# include the /waldo subtree (is a boolean literal)
|
222
|
+
"waldo" => nil,
|
223
|
+
}
|
224
|
+
|
225
|
+
expect(project(schema, over: json)).to eql({
|
226
|
+
"foo" => 42,
|
227
|
+
|
228
|
+
"qux" => {
|
229
|
+
"quux" => [
|
230
|
+
{
|
231
|
+
"name" => "Reactive X",
|
232
|
+
"members" => "many",
|
233
|
+
},
|
234
|
+
{
|
235
|
+
"name" => "lstoll",
|
236
|
+
"members" => "such",
|
237
|
+
},
|
238
|
+
{
|
239
|
+
"name" => "github",
|
240
|
+
"members" => "very",
|
241
|
+
},
|
242
|
+
{
|
243
|
+
"name" => "theleague",
|
244
|
+
"members" => "numerous",
|
245
|
+
}
|
246
|
+
],
|
247
|
+
|
248
|
+
"corge" => {
|
249
|
+
"name" => "Brighton",
|
250
|
+
},
|
251
|
+
},
|
252
|
+
|
253
|
+
"grault" => nil,
|
254
|
+
|
255
|
+
"waldo" => true,
|
256
|
+
})
|
257
|
+
end
|
258
|
+
|
259
|
+
it "supports incompatible schemas" do
|
260
|
+
json = {
|
261
|
+
# surprise! the json doesn't include an object under the foo key
|
262
|
+
"foo" => 42,
|
263
|
+
}
|
264
|
+
|
265
|
+
schema = {
|
266
|
+
# include some of the /foo subtree
|
267
|
+
"foo" => {
|
268
|
+
# include the whole /foo/baz subtree
|
269
|
+
"baz" => nil,
|
270
|
+
}
|
271
|
+
}
|
272
|
+
|
273
|
+
# expect the 42 to be pulled out
|
274
|
+
expect(project(schema, over: json)).to eql({
|
275
|
+
"foo" => 42
|
276
|
+
})
|
277
|
+
end
|
278
|
+
|
279
|
+
it "supports nil schema" do
|
280
|
+
json = {
|
281
|
+
"foo" => "bar",
|
282
|
+
}
|
283
|
+
|
284
|
+
expect(project(nil, over: json)).to eql({
|
285
|
+
"foo" => "bar"
|
286
|
+
})
|
287
|
+
end
|
288
|
+
|
289
|
+
it "supports empty schema" do
|
290
|
+
json = {
|
291
|
+
"foo" => "bar",
|
292
|
+
}
|
293
|
+
expect(project({}, over: json)).to eql({})
|
294
|
+
end
|
295
|
+
|
296
|
+
it "supports object projection" do
|
297
|
+
json = {
|
298
|
+
"foo" => "bar",
|
299
|
+
"qux" => "quux",
|
300
|
+
}
|
301
|
+
|
302
|
+
schema = {
|
303
|
+
"foo" => nil,
|
304
|
+
}
|
305
|
+
|
306
|
+
expect(project(schema, over: json)).to eql({
|
307
|
+
"foo" => "bar"
|
308
|
+
})
|
309
|
+
end
|
310
|
+
|
311
|
+
it "projects the readme example" do
|
312
|
+
json = <<-EOJ
|
313
|
+
[
|
314
|
+
{
|
315
|
+
"user": {
|
316
|
+
"name": "keith",
|
317
|
+
"age": 26,
|
318
|
+
"jobs": [
|
319
|
+
{
|
320
|
+
"title": "director of overworking",
|
321
|
+
"company": "south coast software",
|
322
|
+
"department": "most"
|
323
|
+
},
|
324
|
+
{
|
325
|
+
"title": "some kind of computering",
|
326
|
+
"company": "github the website dot com",
|
327
|
+
"department": true
|
328
|
+
}
|
329
|
+
]
|
330
|
+
},
|
331
|
+
"another key": {
|
332
|
+
|
333
|
+
},
|
334
|
+
"woah this document is huge": {
|
335
|
+
|
336
|
+
},
|
337
|
+
"many megabytes": {
|
338
|
+
|
339
|
+
},
|
340
|
+
"etc": {
|
341
|
+
|
342
|
+
}
|
343
|
+
}
|
344
|
+
]
|
345
|
+
EOJ
|
346
|
+
|
347
|
+
schema = {
|
348
|
+
"user" => {
|
349
|
+
"name" => nil,
|
350
|
+
"jobs" => {
|
351
|
+
"title" => nil,
|
352
|
+
},
|
353
|
+
},
|
354
|
+
}
|
355
|
+
|
356
|
+
expect(project(schema, json: json)).to eql([{
|
357
|
+
"user" => {
|
358
|
+
"name" => "keith",
|
359
|
+
"jobs" => [
|
360
|
+
{ "title" => "director of overworking" },
|
361
|
+
{ "title" => "some kind of computering" },
|
362
|
+
]
|
363
|
+
}
|
364
|
+
}])
|
365
|
+
end
|
366
|
+
|
367
|
+
it "errors with invalid json" do
|
368
|
+
expect {
|
369
|
+
project({"b" => nil}, json: '{"a":, "b": 2}')
|
370
|
+
}.to raise_error(StandardError)
|
371
|
+
end
|
372
|
+
|
373
|
+
it "errors with ignored unbalanced object syntax" do
|
374
|
+
expect {
|
375
|
+
project({"b" => nil}, json: '{"a": {{, "b": 2}')
|
376
|
+
}.to raise_error(StandardError)
|
377
|
+
end
|
378
|
+
|
379
|
+
it "errors with accepted unbalanced object tokens" do
|
380
|
+
expect {
|
381
|
+
project({"a" => nil}, json: '{"a": {"b": 2}')
|
382
|
+
}.to raise_error(Yajl::ParseError)
|
383
|
+
end
|
384
|
+
|
385
|
+
it "errors when projecting if an object comma is missing" do
|
386
|
+
expect {
|
387
|
+
project({"a" => nil}, json: '{"a": 1 "b": 2}')
|
388
|
+
}.to raise_error(Yajl::ParseError)
|
389
|
+
end
|
390
|
+
|
391
|
+
it "errors when building if an object comma is missing" do
|
392
|
+
expect {
|
393
|
+
project(nil, json: '{"a": {"b": 2 "c": 3}}')
|
394
|
+
}.to raise_error(Yajl::ParseError)
|
395
|
+
end
|
396
|
+
|
397
|
+
it "errors when eof instead of simple value" do
|
398
|
+
expect {
|
399
|
+
project(nil, json: '[')
|
400
|
+
}.to raise_error(Yajl::ParseError)
|
401
|
+
end
|
402
|
+
|
403
|
+
it "errors when arrays don't have a comma between elements" do
|
404
|
+
expect {
|
405
|
+
project(nil, json: '[1 2]')
|
406
|
+
}.to raise_error(Yajl::ParseError)
|
407
|
+
end
|
408
|
+
|
409
|
+
it "supports parsing empty array" do
|
410
|
+
expect(project(nil, json: '[]')).to eql([])
|
411
|
+
end
|
412
|
+
|
413
|
+
it "supports parsing empty object" do
|
414
|
+
expect(project(nil, json: '{}')).to eql({})
|
415
|
+
end
|
416
|
+
|
417
|
+
it "reads a full buffer" do
|
418
|
+
json = "[" + "1,"*2046 + "1 ]"
|
419
|
+
expect(json.size).to eql(4096)
|
420
|
+
expect(project(nil, json: json)).to eql(Array.new(2047, 1))
|
421
|
+
end
|
422
|
+
|
423
|
+
it "reads into a second buffer" do
|
424
|
+
json = "[" + "1,"*2047 + "1 ]"
|
425
|
+
expect(json.size).to eql(4098)
|
426
|
+
expect(JSON.parse(json)).to eql(Array.new(2048, 1))
|
427
|
+
expect(project(nil, json: json)).to eql(Array.new(2048, 1))
|
428
|
+
end
|
429
|
+
|
430
|
+
it "supports parsing big strings" do
|
431
|
+
json = [
|
432
|
+
"a",
|
433
|
+
"b"*10_000,
|
434
|
+
"c",
|
435
|
+
]
|
436
|
+
expect(project(nil, over: json)).to eql(json)
|
437
|
+
end
|
438
|
+
|
439
|
+
it "supports bigger read buffers" do
|
440
|
+
json = {
|
441
|
+
"a"*10_000 => "b"*10_000
|
442
|
+
}.to_json
|
443
|
+
stream = StringIO.new(json)
|
444
|
+
expect(Yajl::Projector.new(stream, 8192).project(nil)).to have_key("a"*10_000)
|
445
|
+
end
|
446
|
+
|
447
|
+
it "errors if starting with closing object" do
|
448
|
+
expect {
|
449
|
+
project(nil, json: '}')
|
450
|
+
}.to raise_error(Yajl::ParseError)
|
451
|
+
end
|
452
|
+
|
453
|
+
it "handles objects with utf16 escape sequences as keys" do
|
454
|
+
projection = project(nil, json: '{"\ud83d\ude00": "grinning face"}')
|
455
|
+
literal = {"😀" => "grinning face"}
|
456
|
+
expect(projection).to eql(literal)
|
457
|
+
end
|
458
|
+
|
459
|
+
it "handles objects with non-ascii utf8 bytes as keys" do
|
460
|
+
expect(project(nil, json: '{"😀": "grinning face"}')).to eql({"😀" => "grinning face"})
|
461
|
+
end
|
462
|
+
|
463
|
+
it "handles strings with utf16 escape sequences as object values" do
|
464
|
+
expect(project(nil, json: '{"grinning face": "\ud83d\ude00"}')).to eql({"grinning face" => "😀"})
|
465
|
+
end
|
466
|
+
|
467
|
+
it "handles strings with utf16 escape sequences as array values" do
|
468
|
+
projection = project(nil, json: '["\ud83d\ude00"]')
|
469
|
+
puts projection.first.inspect
|
470
|
+
puts projection.first.bytes
|
471
|
+
|
472
|
+
literal = ["😀"]
|
473
|
+
puts literal.first.inspect
|
474
|
+
puts literal.first.bytes
|
475
|
+
|
476
|
+
expect(projection).to eql(literal)
|
477
|
+
end
|
478
|
+
|
479
|
+
it "handles strings with non-ascii utf8 bytes as array values" do
|
480
|
+
projection = project(nil, json: '["😀"]')
|
481
|
+
puts projection.first.inspect
|
482
|
+
puts projection.first.bytes
|
483
|
+
|
484
|
+
literal = ["😀"]
|
485
|
+
puts literal.first.inspect
|
486
|
+
puts literal.first.bytes
|
487
|
+
|
488
|
+
expect(projection).to eql(literal)
|
489
|
+
end
|
490
|
+
|
491
|
+
it "ignores strings with utf16 escape sequences" do
|
492
|
+
expect(project({"grinning face with open mouth" => nil}, json: '{"grinning face": "\ud83d\ude00", "grinning face with open mouth": "\ud83d\ude03"}')).to eql({"grinning face with open mouth" => "😃"})
|
493
|
+
end
|
494
|
+
|
495
|
+
it "handles objects whose second key has escape sequences" do
|
496
|
+
expect(project(nil, json: '{"foo": "bar", "\ud83d\ude00": "grinning face"}')).to eql({"foo" => "bar", "😀" => "grinning face"})
|
497
|
+
end
|
498
|
+
end
|