yajl-ruby 1.3.0 → 1.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
|