brianmario-yajl-ruby 0.5.2 → 0.5.3
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.md +10 -0
- data/README.rdoc +4 -3
- data/VERSION.yml +2 -2
- data/benchmark/encode.rb +10 -3
- data/benchmark/parse.rb +11 -3
- data/ext/yajl_ext.c +187 -36
- data/ext/yajl_ext.h +6 -3
- data/lib/yajl/bzip2/stream_reader.rb +11 -4
- data/lib/yajl/bzip2/stream_writer.rb +5 -1
- data/lib/yajl/deflate/stream_reader.rb +16 -5
- data/lib/yajl/deflate/stream_writer.rb +6 -1
- data/lib/yajl/gzip/stream_reader.rb +10 -4
- data/lib/yajl/gzip/stream_writer.rb +4 -1
- data/lib/yajl/http_stream.rb +2 -6
- data/lib/yajl.rb +44 -18
- data/spec/encoding/encoding_spec.rb +107 -13
- data/spec/parsing/active_support_spec.rb +18 -2
- data/spec/parsing/chunked_spec.rb +28 -2
- data/spec/parsing/fixtures_spec.rb +20 -2
- data/spec/parsing/one_off_spec.rb +22 -7
- data/yajl-ruby.gemspec +3 -3
- metadata +2 -2
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,15 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## 0.5.3 (Jun 7th, 2009)
|
4
|
+
* The IO parameter for Yajl::Encode#encode is now optional, and accepts a block
|
5
|
+
** it will return the resulting JSON string if no IO is passed to stream to
|
6
|
+
** if a block is passed, it will call and pass it the resulting JSON string
|
7
|
+
* Yajl::Parser#parse can now parse from a String as well as an IO
|
8
|
+
* Added and updated lot of in-code documentation.
|
9
|
+
** all the C code exposed to Ruby should now have comments
|
10
|
+
* Added :symbolize_keys option to the Yajl::Parser class, which defaults to true.
|
11
|
+
** Having this option enabled has shown around an 18% speedup in parsing time according to my benchmarks
|
12
|
+
|
3
13
|
## 0.5.2 (May 30th, 2009)
|
4
14
|
* Added class helper methods Yajl::Encoder.encode(obj, io) and Yajl::Parser.parse(io)
|
5
15
|
* added tests for the above
|
data/README.rdoc
CHANGED
@@ -142,8 +142,8 @@ Some ideas are:
|
|
142
142
|
* a Rails plugin (http://github.com/technoweenie/yajl-rails)
|
143
143
|
* builtin Rails 3 support?
|
144
144
|
* Rack middleware (ideally the JSON body could be handed to the parser while it's still being received)
|
145
|
-
* use with ohai
|
146
|
-
* JSON API clients
|
145
|
+
* use with ohai (http://github.com/brianmario/ohai)
|
146
|
+
* JSON API clients (http://github.com/brianmario/crack, http://github.com/brianmario/freckle-api)
|
147
147
|
* Patch Marshal#load and Marshal#dump to use JSON? ;)
|
148
148
|
* etc...
|
149
149
|
|
@@ -214,4 +214,5 @@ I've had a lot of inspiration, and a lot of help. Thanks to everyone who's been
|
|
214
214
|
* Tom Smith - http://github.com/rtomsmith - pointer-hacking help
|
215
215
|
* Rick http://github.com/technoweenie - for making an ActiveSupport patch with support for this library and teasing me that it might go into Rails 3. You sure lit a fire under my ass and I got a ton of work done because of it! :)
|
216
216
|
* The entire Github Crew - http://github.com/ - my inspiration, time spent writing this, finding Yajl, So many-MANY other things wouldn't have been possible without this awesome service. I owe you guys some whiskey at Kilowatt.
|
217
|
-
* benburkert - http://github.com/benburkert
|
217
|
+
* benburkert - http://github.com/benburkert
|
218
|
+
* Aman Gupta - http://github.com/tmm1 - tons of suggestions and inspiration for the most recent features, and hopefully more to come ;)
|
data/VERSION.yml
CHANGED
data/benchmark/encode.rb
CHANGED
@@ -15,11 +15,18 @@ json.close
|
|
15
15
|
times = ARGV[1] ? ARGV[1].to_i : 1
|
16
16
|
puts "Starting benchmark encoding #{filename} #{times} times\n\n"
|
17
17
|
Benchmark.bm { |x|
|
18
|
-
|
18
|
+
io_encoder = Yajl::Encoder.new
|
19
19
|
x.report {
|
20
|
-
puts "Yajl::Encoder#encode"
|
20
|
+
puts "Yajl::Encoder#encode (to an IO)"
|
21
21
|
times.times {
|
22
|
-
|
22
|
+
io_encoder.encode(hash, StringIO.new)
|
23
|
+
}
|
24
|
+
}
|
25
|
+
string_encoder = Yajl::Encoder.new
|
26
|
+
x.report {
|
27
|
+
puts "Yajl::Encoder#encode (to a String)"
|
28
|
+
times.times {
|
29
|
+
output = string_encoder.encode(hash)
|
23
30
|
}
|
24
31
|
}
|
25
32
|
x.report {
|
data/benchmark/parse.rb
CHANGED
@@ -15,12 +15,20 @@ json.rewind
|
|
15
15
|
times = ARGV[1] ? ARGV[1].to_i : 1
|
16
16
|
puts "Starting benchmark parsing #{File.size(filename)} bytes of JSON data #{times} times\n\n"
|
17
17
|
Benchmark.bm { |x|
|
18
|
-
|
18
|
+
io_parser = Yajl::Parser.new
|
19
19
|
x.report {
|
20
|
-
puts "Yajl::Parser#parse"
|
20
|
+
puts "Yajl::Parser#parse (from an IO)"
|
21
21
|
times.times {
|
22
22
|
json.rewind
|
23
|
-
|
23
|
+
io_parser.parse(json)
|
24
|
+
}
|
25
|
+
}
|
26
|
+
string_parser = Yajl::Parser.new
|
27
|
+
x.report {
|
28
|
+
puts "Yajl::Parser#parse (from a String)"
|
29
|
+
times.times {
|
30
|
+
json.rewind
|
31
|
+
string_parser.parse(json.read)
|
24
32
|
}
|
25
33
|
}
|
26
34
|
x.report {
|
data/ext/yajl_ext.c
CHANGED
@@ -11,6 +11,14 @@ inline void yajl_check_and_fire_callback(void * ctx) {
|
|
11
11
|
if (len == 1 && wrapper->nestedArrayLevel == 0 && wrapper->nestedHashLevel == 0) {
|
12
12
|
rb_funcall(wrapper->parse_complete_callback, intern_call, 1, rb_ary_pop(wrapper->builderStack));
|
13
13
|
}
|
14
|
+
} else {
|
15
|
+
int len = RARRAY_LEN(wrapper->builderStack);
|
16
|
+
if (len == 1 && wrapper->nestedArrayLevel == 0 && wrapper->nestedHashLevel == 0) {
|
17
|
+
wrapper->objectsFound++;
|
18
|
+
if (wrapper->objectsFound > 1) {
|
19
|
+
rb_raise(cParseError, "%s", "Found multiple JSON objects in the stream but no block or the on_parse_complete callback was assigned to handle them.");
|
20
|
+
}
|
21
|
+
}
|
14
22
|
}
|
15
23
|
}
|
16
24
|
|
@@ -36,6 +44,7 @@ inline void yajl_set_static_value(void * ctx, VALUE val) {
|
|
36
44
|
rb_ary_push(wrapper->builderStack, val);
|
37
45
|
break;
|
38
46
|
case T_STRING:
|
47
|
+
case T_SYMBOL:
|
39
48
|
hash = rb_ary_entry(wrapper->builderStack, len-2);
|
40
49
|
if (TYPE(hash) == T_HASH) {
|
41
50
|
rb_hash_aset(hash, lastEntry, val);
|
@@ -58,11 +67,13 @@ void yajl_encode_part(yajl_gen hand, VALUE obj, VALUE io) {
|
|
58
67
|
const unsigned char * buffer;
|
59
68
|
unsigned int len;
|
60
69
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
70
|
+
if (io != Qnil) {
|
71
|
+
yajl_gen_get_buf(hand, &buffer, &len);
|
72
|
+
if (len >= WRITE_BUFSIZE) {
|
73
|
+
outBuff = rb_str_new((const char *)buffer, len);
|
74
|
+
rb_io_write(io, outBuff);
|
75
|
+
yajl_gen_clear(hand);
|
76
|
+
}
|
66
77
|
}
|
67
78
|
|
68
79
|
switch (TYPE(obj)) {
|
@@ -126,6 +137,18 @@ void yajl_parser_wrapper_mark(void * wrapper) {
|
|
126
137
|
rb_gc_mark(w->parse_complete_callback);
|
127
138
|
}
|
128
139
|
|
140
|
+
void yajl_parse_chunk(const unsigned char * chunk, unsigned int len, yajl_handle parser) {
|
141
|
+
yajl_status stat;
|
142
|
+
|
143
|
+
stat = yajl_parse(parser, chunk, len);
|
144
|
+
|
145
|
+
if (stat != yajl_status_ok && stat != yajl_status_insufficient_data) {
|
146
|
+
unsigned char * str = yajl_get_error(parser, 1, chunk, len);
|
147
|
+
rb_raise(cParseError, "%s", (const char *) str);
|
148
|
+
yajl_free_error(parser, str);
|
149
|
+
}
|
150
|
+
}
|
151
|
+
|
129
152
|
// YAJL Callbacks
|
130
153
|
static int yajl_found_null(void * ctx) {
|
131
154
|
yajl_set_static_value(ctx, Qnil);
|
@@ -161,7 +184,18 @@ static int yajl_found_string(void * ctx, const unsigned char * stringVal, unsign
|
|
161
184
|
}
|
162
185
|
|
163
186
|
static int yajl_found_hash_key(void * ctx, const unsigned char * stringVal, unsigned int stringLen) {
|
164
|
-
|
187
|
+
struct yajl_parser_wrapper * wrapper;
|
188
|
+
GetParser((VALUE)ctx, wrapper);
|
189
|
+
|
190
|
+
if (wrapper->symbolizeKeys) {
|
191
|
+
char keyStr[stringLen];
|
192
|
+
ID key;
|
193
|
+
sprintf(keyStr, "%.*s", stringLen, stringVal);
|
194
|
+
key = rb_intern(keyStr);
|
195
|
+
yajl_set_static_value(ctx, ID2SYM(key));
|
196
|
+
} else {
|
197
|
+
yajl_set_static_value(ctx, rb_str_new((const char *)stringVal, stringLen));
|
198
|
+
}
|
165
199
|
yajl_check_and_fire_callback(ctx);
|
166
200
|
return 1;
|
167
201
|
}
|
@@ -205,14 +239,30 @@ static int yajl_found_end_array(void * ctx) {
|
|
205
239
|
}
|
206
240
|
|
207
241
|
|
208
|
-
|
242
|
+
// Ruby Interface
|
209
243
|
|
210
|
-
|
244
|
+
/*
|
245
|
+
* Document-class: Yajl::Parser
|
246
|
+
*
|
247
|
+
* This class contains methods for parsing JSON directly from an IO object.
|
248
|
+
* The only basic requirment currently is that the IO object respond to #read(len) and #eof?
|
249
|
+
* The IO is parsed until a complete JSON object has been read and a ruby object will be returned.
|
250
|
+
*/
|
251
|
+
|
252
|
+
/*
|
253
|
+
* Document-method: new
|
254
|
+
*
|
255
|
+
* call-seq: new([:allow_comments => false, :check_utf8 => false])
|
256
|
+
*
|
257
|
+
* :allow_comments will turn on/off the check for comments inside the JSON stream.
|
258
|
+
*
|
259
|
+
* :check_utf8 will validate UTF8 characters found in the JSON stream.
|
260
|
+
*/
|
211
261
|
static VALUE rb_yajl_parser_new(int argc, VALUE * argv, VALUE klass) {
|
212
262
|
struct yajl_parser_wrapper * wrapper;
|
213
263
|
yajl_parser_config cfg;
|
214
264
|
VALUE opts, obj;
|
215
|
-
int allowComments = 1, checkUTF8 = 1;
|
265
|
+
int allowComments = 1, checkUTF8 = 1, symbolizeKeys = 1;
|
216
266
|
|
217
267
|
// Scan off config vars
|
218
268
|
if (rb_scan_args(argc, argv, "01", &opts) == 1) {
|
@@ -224,6 +274,9 @@ static VALUE rb_yajl_parser_new(int argc, VALUE * argv, VALUE klass) {
|
|
224
274
|
if (rb_hash_aref(opts, ID2SYM(sym_check_utf8)) == Qfalse) {
|
225
275
|
checkUTF8 = 0;
|
226
276
|
}
|
277
|
+
if (rb_hash_aref(opts, ID2SYM(sym_symbolize_keys)) == Qfalse) {
|
278
|
+
symbolizeKeys = 0;
|
279
|
+
}
|
227
280
|
}
|
228
281
|
cfg = (yajl_parser_config){allowComments, checkUTF8};
|
229
282
|
|
@@ -231,44 +284,76 @@ static VALUE rb_yajl_parser_new(int argc, VALUE * argv, VALUE klass) {
|
|
231
284
|
wrapper->parser = yajl_alloc(&callbacks, &cfg, NULL, (void *)obj);
|
232
285
|
wrapper->nestedArrayLevel = 0;
|
233
286
|
wrapper->nestedHashLevel = 0;
|
287
|
+
wrapper->objectsFound = 0;
|
288
|
+
wrapper->symbolizeKeys = symbolizeKeys;
|
234
289
|
wrapper->builderStack = rb_ary_new();
|
235
290
|
wrapper->parse_complete_callback = Qnil;
|
236
291
|
rb_obj_call_init(obj, 0, 0);
|
237
292
|
return obj;
|
238
293
|
}
|
239
294
|
|
295
|
+
/*
|
296
|
+
* Document-method: initialize
|
297
|
+
*
|
298
|
+
* call-seq: initialize([:allow_comments => false, :check_utf8 => false])
|
299
|
+
*
|
300
|
+
* :allow_comments will turn on/off the check for comments inside the JSON stream.
|
301
|
+
*
|
302
|
+
* :check_utf8 will validate UTF8 characters found in the JSON stream.
|
303
|
+
*/
|
240
304
|
static VALUE rb_yajl_parser_init(int argc, VALUE * argv, VALUE self) {
|
241
305
|
return self;
|
242
306
|
}
|
243
307
|
|
308
|
+
/*
|
309
|
+
* Document-method: parse
|
310
|
+
*
|
311
|
+
* call-seq:
|
312
|
+
* parse(input, buffer_size=8092)
|
313
|
+
* parse(input, buffer_size=8092) { |obj| ... }
|
314
|
+
*
|
315
|
+
* +input+ can either be a string or an IO to parse JSON from
|
316
|
+
*
|
317
|
+
* +buffer_size+ is the size of chunk that will be parsed off the input (if it's an IO) for each loop of the parsing process.
|
318
|
+
* 8092 is a good balance between the different types of streams (off disk, off a socket, etc...), but this option
|
319
|
+
* is here so the caller can better tune their parsing depending on the type of stream being passed.
|
320
|
+
* A larger read buffer will perform better for files off disk, where as a smaller size may be more efficient for
|
321
|
+
* reading off of a socket directly.
|
322
|
+
*
|
323
|
+
* If a block was passed, it's called when an object has been parsed off the stream. This is especially
|
324
|
+
* usefull when parsing a stream of multiple JSON objects.
|
325
|
+
*
|
326
|
+
* NOTE: you can optionally assign the +on_parse_complete+ callback, and it will be called the same way the optional
|
327
|
+
* block is for this method.
|
328
|
+
*/
|
244
329
|
static VALUE rb_yajl_parser_parse(int argc, VALUE * argv, VALUE self) {
|
245
|
-
struct yajl_parser_wrapper * wrapper;
|
246
330
|
yajl_status stat;
|
247
|
-
|
331
|
+
struct yajl_parser_wrapper * wrapper;
|
332
|
+
VALUE parsed, rbufsize, input, blk;
|
248
333
|
|
249
334
|
GetParser(self, wrapper);
|
250
335
|
parsed = rb_str_new2("");
|
251
336
|
|
252
337
|
// setup our parameters
|
253
|
-
rb_scan_args(argc, argv, "11", &
|
338
|
+
rb_scan_args(argc, argv, "11&", &input, &rbufsize, &blk);
|
254
339
|
if (NIL_P(rbufsize)) {
|
255
340
|
rbufsize = INT2FIX(READ_BUFSIZE);
|
256
341
|
} else {
|
257
342
|
Check_Type(rbufsize, T_FIXNUM);
|
258
343
|
}
|
344
|
+
if (!NIL_P(blk)) {
|
345
|
+
rb_yajl_set_complete_cb(self, blk);
|
346
|
+
}
|
259
347
|
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
if (stat != yajl_status_ok && stat != yajl_status_insufficient_data) {
|
267
|
-
unsigned char * str = yajl_get_error(wrapper->parser, 1, (const unsigned char *)RSTRING_PTR(parsed), RSTRING_LEN(parsed));
|
268
|
-
rb_raise(cParseError, "%s", (const char *) str);
|
269
|
-
yajl_free_error(wrapper->parser, str);
|
270
|
-
break;
|
348
|
+
if (TYPE(input) == T_STRING) {
|
349
|
+
yajl_parse_chunk((const unsigned char *)RSTRING_PTR(input), RSTRING_LEN(input), wrapper->parser);
|
350
|
+
} else if (rb_respond_to(input, intern_eof)) {
|
351
|
+
while (rb_funcall(input, intern_eof, 0) != Qtrue) {
|
352
|
+
rb_funcall(input, intern_io_read, 2, rbufsize, parsed);
|
353
|
+
yajl_parse_chunk((const unsigned char *)RSTRING_PTR(parsed), RSTRING_LEN(parsed), wrapper->parser);
|
271
354
|
}
|
355
|
+
} else {
|
356
|
+
rb_raise(cParseError, "input must be a string or IO");
|
272
357
|
}
|
273
358
|
|
274
359
|
// parse any remaining buffered data
|
@@ -282,9 +367,19 @@ static VALUE rb_yajl_parser_parse(int argc, VALUE * argv, VALUE self) {
|
|
282
367
|
return rb_ary_pop(wrapper->builderStack);
|
283
368
|
}
|
284
369
|
|
370
|
+
/*
|
371
|
+
* Document-method: parse_chunk
|
372
|
+
*
|
373
|
+
* call-seq: parse_chunk(string_chunk)
|
374
|
+
*
|
375
|
+
* +string_chunk+ can be a partial or full JSON string to push on the parser.
|
376
|
+
*
|
377
|
+
* This method will throw an exception if the +on_parse_complete+ callback hasn't been assigned yet.
|
378
|
+
* The +on_parse_complete+ callback assignment is required so the user can handle objects that have been
|
379
|
+
* parsed off the stream as they're found.
|
380
|
+
*/
|
285
381
|
static VALUE rb_yajl_parser_parse_chunk(VALUE self, VALUE chunk) {
|
286
382
|
struct yajl_parser_wrapper * wrapper;
|
287
|
-
yajl_status stat;
|
288
383
|
|
289
384
|
GetParser(self, wrapper);
|
290
385
|
if (NIL_P(chunk)) {
|
@@ -293,12 +388,7 @@ static VALUE rb_yajl_parser_parse_chunk(VALUE self, VALUE chunk) {
|
|
293
388
|
}
|
294
389
|
|
295
390
|
if (wrapper->parse_complete_callback != Qnil) {
|
296
|
-
|
297
|
-
if (stat != yajl_status_ok && stat != yajl_status_insufficient_data) {
|
298
|
-
unsigned char * str = yajl_get_error(wrapper->parser, 1, (const unsigned char *)RSTRING_PTR(chunk), RSTRING_LEN(chunk));
|
299
|
-
rb_raise(cParseError, "%s", (const char *) str);
|
300
|
-
yajl_free_error(wrapper->parser, str);
|
301
|
-
}
|
391
|
+
yajl_parse_chunk((const unsigned char *)RSTRING_PTR(chunk), RSTRING_LEN(chunk), wrapper->parser);
|
302
392
|
} else {
|
303
393
|
rb_raise(cParseError, "The on_parse_complete callback isn't setup, parsing useless.");
|
304
394
|
}
|
@@ -306,6 +396,15 @@ static VALUE rb_yajl_parser_parse_chunk(VALUE self, VALUE chunk) {
|
|
306
396
|
return Qnil;
|
307
397
|
}
|
308
398
|
|
399
|
+
/*
|
400
|
+
* Document-method: on_parse_complete=
|
401
|
+
*
|
402
|
+
* call-seq: on_parse_complete = Proc.new { |obj| ... }
|
403
|
+
*
|
404
|
+
* This callback setter allows you to pass a Proc/lambda or any other object that response to #call.
|
405
|
+
*
|
406
|
+
* It will pass a single parameter, the ruby object built from the last parsed JSON object
|
407
|
+
*/
|
309
408
|
static VALUE rb_yajl_set_complete_cb(VALUE self, VALUE callback) {
|
310
409
|
struct yajl_parser_wrapper * wrapper;
|
311
410
|
GetParser(self, wrapper);
|
@@ -313,7 +412,23 @@ static VALUE rb_yajl_set_complete_cb(VALUE self, VALUE callback) {
|
|
313
412
|
return Qnil;
|
314
413
|
}
|
315
414
|
|
316
|
-
|
415
|
+
/*
|
416
|
+
* Document-class: Yajl::Encoder
|
417
|
+
*
|
418
|
+
* This class contains methods for encoding a Ruby object into JSON, streaming it's output into an IO object.
|
419
|
+
* The IO object need only respond to #write(str)
|
420
|
+
* The JSON stream created is written to the IO in chunks, as it's being created.
|
421
|
+
*/
|
422
|
+
|
423
|
+
/*
|
424
|
+
* Document-method: new
|
425
|
+
*
|
426
|
+
* call-seq: new([:pretty => false[, :indent => ' ']])
|
427
|
+
*
|
428
|
+
* :pretty will enable/disable beautifying or "pretty priting" the output string.
|
429
|
+
*
|
430
|
+
* :indent is the character(s) used to indent the output string.
|
431
|
+
*/
|
317
432
|
static VALUE rb_yajl_encoder_new(int argc, VALUE * argv, VALUE klass) {
|
318
433
|
yajl_gen_config cfg;
|
319
434
|
yajl_gen encoder;
|
@@ -342,27 +457,61 @@ static VALUE rb_yajl_encoder_new(int argc, VALUE * argv, VALUE klass) {
|
|
342
457
|
return obj;
|
343
458
|
}
|
344
459
|
|
460
|
+
/*
|
461
|
+
* Document-method: initialize
|
462
|
+
*
|
463
|
+
* call-seq: initialize([:pretty => false[, :indent => ' ']])
|
464
|
+
*
|
465
|
+
* :pretty will enable/disable beautifying or "pretty priting" the output string.
|
466
|
+
*
|
467
|
+
* :indent is the character(s) used to indent the output string.
|
468
|
+
*/
|
345
469
|
static VALUE rb_yajl_encoder_init(int argc, VALUE * argv, VALUE self) {
|
346
470
|
return self;
|
347
471
|
}
|
348
472
|
|
349
|
-
|
473
|
+
/*
|
474
|
+
* Document-method: encode
|
475
|
+
*
|
476
|
+
* call-seq: encode(obj[, io[, &block]])
|
477
|
+
*
|
478
|
+
* +obj+ is the Ruby object to encode to JSON
|
479
|
+
*
|
480
|
+
* +io+ is an optional IO used to stream the encoded JSON string to.
|
481
|
+
* If +io+ isn't specified, this method will return the resulting JSON string. If +io+ is specified, this method returns nil
|
482
|
+
*
|
483
|
+
* If an optional block is passed, it's called when encoding is complete and passed the resulting JSON string
|
484
|
+
*
|
485
|
+
* It should be noted that you can reuse an instance of this class to continue encoding multiple JSON
|
486
|
+
* to the same stream. Just continue calling this method, passing it the same IO object with new/different
|
487
|
+
* ruby objects to encode. This is how streaming is accomplished.
|
488
|
+
*/
|
489
|
+
static VALUE rb_yajl_encoder_encode(int argc, VALUE * argv, VALUE self) {
|
350
490
|
yajl_gen encoder;
|
351
491
|
const unsigned char * buffer;
|
352
492
|
unsigned int len;
|
353
|
-
VALUE outBuff;
|
493
|
+
VALUE obj, io, blk, outBuff;
|
354
494
|
|
355
495
|
GetEncoder(self, encoder);
|
356
496
|
|
497
|
+
rb_scan_args(argc, argv, "11&", &obj, &io, &blk);
|
498
|
+
|
357
499
|
// begin encode process
|
358
500
|
yajl_encode_part(encoder, obj, io);
|
359
501
|
|
360
502
|
// just make sure we output the remaining buffer
|
361
503
|
yajl_gen_get_buf(encoder, &buffer, &len);
|
362
504
|
outBuff = rb_str_new((const char *)buffer, len);
|
363
|
-
rb_io_write(io, outBuff);
|
364
505
|
yajl_gen_clear(encoder);
|
365
|
-
|
506
|
+
if (io != Qnil) {
|
507
|
+
rb_io_write(io, outBuff);
|
508
|
+
return Qnil;
|
509
|
+
} else if (blk != Qnil) {
|
510
|
+
rb_funcall(blk, intern_call, 1, outBuff);
|
511
|
+
return Qnil;
|
512
|
+
} else {
|
513
|
+
return outBuff;
|
514
|
+
}
|
366
515
|
return Qnil;
|
367
516
|
}
|
368
517
|
|
@@ -372,6 +521,7 @@ void Init_yajl_ext() {
|
|
372
521
|
|
373
522
|
VALUE rb_cStandardError = rb_const_get(rb_cObject, rb_intern("StandardError"));
|
374
523
|
cParseError = rb_define_class_under(mYajl, "ParseError", rb_cStandardError);
|
524
|
+
cEncodeError = rb_define_class_under(mYajl, "EncodeError", rb_cStandardError);
|
375
525
|
|
376
526
|
cParser = rb_define_class_under(mYajl, "Parser", rb_cObject);
|
377
527
|
rb_define_singleton_method(cParser, "new", rb_yajl_parser_new, -1);
|
@@ -384,7 +534,7 @@ void Init_yajl_ext() {
|
|
384
534
|
cEncoder = rb_define_class_under(mYajl, "Encoder", rb_cObject);
|
385
535
|
rb_define_singleton_method(cEncoder, "new", rb_yajl_encoder_new, -1);
|
386
536
|
rb_define_method(cEncoder, "initialize", rb_yajl_encoder_init, -1);
|
387
|
-
rb_define_method(cEncoder, "encode", rb_yajl_encoder_encode,
|
537
|
+
rb_define_method(cEncoder, "encode", rb_yajl_encoder_encode, -1);
|
388
538
|
|
389
539
|
intern_io_read = rb_intern("read");
|
390
540
|
intern_eof = rb_intern("eof?");
|
@@ -395,4 +545,5 @@ void Init_yajl_ext() {
|
|
395
545
|
sym_check_utf8 = rb_intern("check_utf8");
|
396
546
|
sym_pretty = rb_intern("pretty");
|
397
547
|
sym_indent = rb_intern("indent");
|
548
|
+
sym_symbolize_keys = rb_intern("symbolize_keys");
|
398
549
|
}
|
data/ext/yajl_ext.h
CHANGED
@@ -5,9 +5,9 @@
|
|
5
5
|
#define READ_BUFSIZE 8092
|
6
6
|
#define WRITE_BUFSIZE 8092
|
7
7
|
|
8
|
-
static VALUE cParseError, mYajl, cParser, cEncoder;
|
8
|
+
static VALUE cParseError, cEncodeError, mYajl, cParser, cEncoder;
|
9
9
|
static ID intern_io_read, intern_eof, intern_call, intern_keys, intern_to_s,
|
10
|
-
sym_allow_comments, sym_check_utf8, sym_pretty, sym_indent;
|
10
|
+
sym_allow_comments, sym_check_utf8, sym_pretty, sym_indent, sym_symbolize_keys;
|
11
11
|
|
12
12
|
#define GetParser(obj, sval) (sval = (struct yajl_parser_wrapper*)DATA_PTR(obj));
|
13
13
|
#define GetEncoder(obj, sval) (sval = (yajl_gen*)DATA_PTR(obj));
|
@@ -15,6 +15,7 @@ static ID intern_io_read, intern_eof, intern_call, intern_keys, intern_to_s,
|
|
15
15
|
inline void yajl_check_and_fire_callback(void * ctx);
|
16
16
|
inline void yajl_set_static_value(void * ctx, VALUE val);
|
17
17
|
void yajl_encode_part(yajl_gen hand, VALUE obj, VALUE io);
|
18
|
+
void yajl_parse_chunk(const unsigned char * chunk, unsigned int len, yajl_handle parser);
|
18
19
|
|
19
20
|
static int yajl_found_null(void * ctx);
|
20
21
|
static int yajl_found_boolean(void * ctx, int boolean);
|
@@ -44,6 +45,8 @@ struct yajl_parser_wrapper {
|
|
44
45
|
VALUE parse_complete_callback;
|
45
46
|
int nestedArrayLevel;
|
46
47
|
int nestedHashLevel;
|
48
|
+
int objectsFound;
|
49
|
+
int symbolizeKeys;
|
47
50
|
yajl_handle parser;
|
48
51
|
};
|
49
52
|
static void yajl_parser_wrapper_free(void * wrapper);
|
@@ -57,4 +60,4 @@ static VALUE rb_yajl_set_complete_cb(VALUE self, VALUE callback);
|
|
57
60
|
|
58
61
|
static VALUE rb_yajl_encoder_new(int argc, VALUE * argv, VALUE klass);
|
59
62
|
static VALUE rb_yajl_encoder_init(int argc, VALUE * argv, VALUE self);
|
60
|
-
static VALUE rb_yajl_encoder_encode(
|
63
|
+
static VALUE rb_yajl_encoder_encode(int argc, VALUE * argv, VALUE self);
|
@@ -1,11 +1,11 @@
|
|
1
1
|
# encoding: UTF-8
|
2
2
|
module Yajl
|
3
3
|
module Bzip2
|
4
|
-
# === Yajl::Bzip2::StreamReader
|
5
|
-
#
|
6
4
|
# This is a wrapper around Bzip::Reader to allow it's #read method to adhere
|
7
5
|
# to the IO spec, allowing for two parameters (length, and buffer)
|
8
6
|
class StreamReader < ::Bzip2::Reader
|
7
|
+
|
8
|
+
# A helper method to allow use similar to IO#read
|
9
9
|
def read(len=nil, buffer=nil)
|
10
10
|
unless buffer.nil?
|
11
11
|
buffer.replace super(len)
|
@@ -14,8 +14,15 @@ module Yajl
|
|
14
14
|
super(len)
|
15
15
|
end
|
16
16
|
|
17
|
-
|
18
|
-
|
17
|
+
# Helper method for one-off parsing from a bzip2-compressed stream
|
18
|
+
#
|
19
|
+
# See Yajl::Parser#parse for parameter documentation
|
20
|
+
def self.parse(input, options={}, buffer_size=nil, &block)
|
21
|
+
if input.is_a?(String)
|
22
|
+
input = StringIO.new(input)
|
23
|
+
end
|
24
|
+
|
25
|
+
Yajl::Parser.new(options).parse(new(input), buffer_size, &block)
|
19
26
|
end
|
20
27
|
end
|
21
28
|
end
|
@@ -1,8 +1,12 @@
|
|
1
1
|
# encoding: UTF-8
|
2
2
|
module Yajl
|
3
3
|
module Bzip2
|
4
|
-
#
|
4
|
+
# A wrapper around the Bzip2::Writer class for easier JSON stream encoding
|
5
5
|
class StreamWriter < ::Bzip2::Writer
|
6
|
+
|
7
|
+
# A helper method for encoding to a bzip2-compressed stream
|
8
|
+
#
|
9
|
+
# Look up Yajl::Encoder#encode for parameter documentation
|
6
10
|
def self.encode(obj, io)
|
7
11
|
Yajl::Encoder.new.encode(obj, new(io))
|
8
12
|
end
|
@@ -1,25 +1,36 @@
|
|
1
1
|
# encoding: UTF-8
|
2
2
|
module Yajl
|
3
3
|
module Deflate
|
4
|
-
# === Yajl::Deflate::StreamReader
|
5
|
-
#
|
6
4
|
# This is a wrapper around Zlib::Inflate, creating a #read method that adheres
|
7
5
|
# to the IO spec, allowing for two parameters (length, and buffer)
|
8
6
|
class StreamReader < ::Zlib::Inflate
|
7
|
+
|
8
|
+
# Wrapper to the initialize method so we can set the initial IO to parse from.
|
9
9
|
def initialize(io, options)
|
10
10
|
@io = io
|
11
11
|
super(options)
|
12
12
|
end
|
13
13
|
|
14
|
+
# A helper method to allow use similar to IO#read
|
14
15
|
def read(len=nil, buffer=nil)
|
15
16
|
buffer.replace inflate(@io.read(len)) and return unless buffer.nil?
|
16
17
|
inflate(@io.read(len))
|
17
18
|
end
|
18
|
-
|
19
19
|
alias :eof? :finished?
|
20
20
|
|
21
|
-
|
22
|
-
|
21
|
+
# Helper method for one-off parsing from a deflate-compressed stream
|
22
|
+
#
|
23
|
+
# See Yajl::Parser#parse for parameter documentation
|
24
|
+
def self.parse(input, options={}, buffer_size=nil, &block)
|
25
|
+
if input.is_a?(String)
|
26
|
+
input = StringIO.new(input)
|
27
|
+
end
|
28
|
+
|
29
|
+
if options.is_a?(Hash)
|
30
|
+
Yajl::Parser.new(options).parse(new(input), buffer_size, &block)
|
31
|
+
elsif options.is_a?(Fixnum)
|
32
|
+
Yajl::Parser.new.parse(new(input, options), buffer_size, &block)
|
33
|
+
end
|
23
34
|
end
|
24
35
|
end
|
25
36
|
end
|
@@ -1,13 +1,18 @@
|
|
1
1
|
# encoding: UTF-8
|
2
2
|
module Yajl
|
3
3
|
module Deflate
|
4
|
-
#
|
4
|
+
# A wrapper around the Zlib::Deflate class for easier JSON stream parsing
|
5
5
|
class StreamWriter < ::Zlib::Deflate
|
6
|
+
|
7
|
+
# A helper method to allow use similar to IO#write
|
6
8
|
def write(str)
|
7
9
|
deflate(str)
|
8
10
|
str.size unless str.nil?
|
9
11
|
end
|
10
12
|
|
13
|
+
# A helper method for one-off encoding to a deflate-compressed stream
|
14
|
+
#
|
15
|
+
# Look up Yajl::Encoder#encode for parameter documentation
|
11
16
|
def self.encode(obj, io)
|
12
17
|
Yajl::Encoder.new.encode(obj, new(io))
|
13
18
|
end
|
@@ -1,11 +1,11 @@
|
|
1
1
|
# encoding: UTF-8
|
2
2
|
module Yajl
|
3
3
|
module Gzip
|
4
|
-
# === Yajl::GzipStreamReader
|
5
|
-
#
|
6
4
|
# This is a wrapper around Zlib::GzipReader to allow it's #read method to adhere
|
7
5
|
# to the IO spec, allowing for two parameters (length, and buffer)
|
8
6
|
class StreamReader < ::Zlib::GzipReader
|
7
|
+
|
8
|
+
# Wrapper method to allow use similar to IO#read
|
9
9
|
def read(len=nil, buffer=nil)
|
10
10
|
unless buffer.nil?
|
11
11
|
buffer.replace super(len)
|
@@ -14,8 +14,14 @@ module Yajl
|
|
14
14
|
super(len)
|
15
15
|
end
|
16
16
|
|
17
|
-
|
18
|
-
|
17
|
+
# Helper method for one-off parsing from a gzip-compressed stream
|
18
|
+
#
|
19
|
+
# See Yajl::Parser#parse for parameter documentation
|
20
|
+
def self.parse(input, options={}, buffer_size=nil, &block)
|
21
|
+
if input.is_a?(String)
|
22
|
+
input = StringIO.new(input)
|
23
|
+
end
|
24
|
+
Yajl::Parser.new(options).parse(new(input), buffer_size, &block)
|
19
25
|
end
|
20
26
|
end
|
21
27
|
end
|
@@ -1,8 +1,11 @@
|
|
1
1
|
# encoding: UTF-8
|
2
2
|
module Yajl
|
3
3
|
module Gzip
|
4
|
-
#
|
4
|
+
# Wraper around the Zlib::GzipWriter class
|
5
5
|
class StreamWriter < ::Zlib::GzipWriter
|
6
|
+
# A helper method for one-off encoding to a gzip-compressed stream
|
7
|
+
#
|
8
|
+
# Look up Yajl::Encoder#encode for parameter documentation
|
6
9
|
def self.encode(obj, io)
|
7
10
|
Yajl::Encoder.new.encode(obj, new(io))
|
8
11
|
end
|
data/lib/yajl/http_stream.rb
CHANGED
@@ -3,13 +3,10 @@ require 'socket' unless defined?(Socket)
|
|
3
3
|
require 'yajl' unless defined?(Yajl::Parser)
|
4
4
|
|
5
5
|
module Yajl
|
6
|
-
# == Yajl::HttpStream
|
7
|
-
#
|
8
6
|
# This module is for making HTTP requests to which the response bodies (and possibly requests in the near future)
|
9
7
|
# are streamed directly into Yajl.
|
10
8
|
class HttpStream
|
11
|
-
|
12
|
-
#
|
9
|
+
|
13
10
|
# This Exception is thrown when an HTTP response isn't application/json
|
14
11
|
# and therefore cannot be parsed.
|
15
12
|
class InvalidContentType < Exception; end
|
@@ -22,8 +19,7 @@ module Yajl
|
|
22
19
|
# 1. a raw socket is opened to the server/host provided
|
23
20
|
# 2. the request is made using HTTP/1.0, Accept-encoding: gzip (deflate support coming soon, too)
|
24
21
|
# 3. the response is read until the end of the headers
|
25
|
-
# 4. the _socket itself_ is passed directly to Yajl, for direct parsing off the stream;
|
26
|
-
# As it's being received over the wire!
|
22
|
+
# 4. the _socket itself_ is passed directly to Yajl, for direct parsing off the stream; As it's being received over the wire!
|
27
23
|
def self.get(uri, opts = {}, &block)
|
28
24
|
user_agent = opts.has_key?(['User-Agent']) ? opts['User-Agent'] : "Yajl::HttpStream #{Yajl::VERSION}"
|
29
25
|
|
data/lib/yajl.rb
CHANGED
@@ -13,39 +13,65 @@ require 'yajl_ext'
|
|
13
13
|
#
|
14
14
|
# Ruby bindings to the excellent Yajl (Yet Another JSON Parser) ANSI C library.
|
15
15
|
module Yajl
|
16
|
-
VERSION = "0.5.
|
16
|
+
VERSION = "0.5.3"
|
17
17
|
|
18
|
-
# == Yajl::Parser
|
19
|
-
#
|
20
|
-
# This class contains methods for parsing JSON directly from an IO object.
|
21
|
-
# The only basic requirment currently is that the IO object respond to #read(len) and #eof?
|
22
|
-
# The IO is parsed until a complete JSON object has been read and a ruby object will be returned.
|
23
18
|
class Parser
|
24
|
-
|
25
|
-
|
19
|
+
# A helper method for parse-and-forget use-cases
|
20
|
+
#
|
21
|
+
# +io+ is the stream to parse JSON from
|
22
|
+
#
|
23
|
+
# The +options+ hash allows you to set two parsing options - :allow_comments and :check_utf8
|
24
|
+
#
|
25
|
+
# :allow_comments accepts a boolean will enable/disable checks for in-line comments in the JSON stream
|
26
|
+
#
|
27
|
+
# :check_utf8 accepts a boolean will enable/disable UTF8 validation for the JSON stream
|
28
|
+
def self.parse(io, options={}, read_bufsize=nil, &block)
|
29
|
+
new(options).parse(io, read_bufsize, &block)
|
26
30
|
end
|
27
31
|
end
|
28
32
|
|
29
|
-
# == Yajl::Encoder
|
30
|
-
#
|
31
|
-
# This class contains methods for encoding a Ruby object into JSON, streaming it's output into an IO object.
|
32
|
-
# The IO object need only respond to #write(str)
|
33
|
-
# The JSON stream created is written to the IO in chunks, as it's being created.
|
34
33
|
class Encoder
|
35
|
-
|
36
|
-
|
34
|
+
# A helper method for encode-and-forget use-cases
|
35
|
+
#
|
36
|
+
# Examples:
|
37
|
+
# Yajl::Encoder.encode(obj[, io, :pretty => true, :indent => "\t"])
|
38
|
+
#
|
39
|
+
# output = Yajl::Encoder.encode(obj[, :pretty => true, :indent => "\t"])
|
40
|
+
#
|
41
|
+
# +obj+ is a ruby object to encode to JSON format
|
42
|
+
#
|
43
|
+
# +io+ is the optional IO stream to encode the ruby object to.
|
44
|
+
# If +io+ isn't passed, the resulting JSON string is returned. If +io+ is passed, nil is returned.
|
45
|
+
#
|
46
|
+
# The +options+ hash allows you to set two encoding options - :pretty and :indent
|
47
|
+
#
|
48
|
+
# :pretty accepts a boolean and will enable/disable "pretty printing" the resulting output
|
49
|
+
#
|
50
|
+
# :indent accepts a string and will be used as the indent character(s) during the pretty print process
|
51
|
+
def self.encode(obj, *args, &block)
|
52
|
+
# TODO: this code smells, any ideas?
|
53
|
+
options = {}
|
54
|
+
io = nil
|
55
|
+
args.each do |arg|
|
56
|
+
if arg.is_a?(Hash)
|
57
|
+
options = arg
|
58
|
+
elsif arg.respond_to?(:read)
|
59
|
+
io = arg
|
60
|
+
end
|
61
|
+
end if args.any?
|
62
|
+
new(options).encode(obj, io, &block)
|
37
63
|
end
|
38
64
|
end
|
39
65
|
|
40
|
-
#
|
66
|
+
# DEPRECATED - See Yajl::Parser and Yajl::Encoder
|
41
67
|
module Stream
|
42
|
-
#
|
68
|
+
# DEPRECATED - See Yajl::Parser
|
43
69
|
def self.parse(io)
|
44
70
|
STDERR.puts "WARNING: Yajl::Stream has be deprecated and will most likely be gone in the next release. Use the Yajl::Parser class instead."
|
45
71
|
Parser.new.parse(io)
|
46
72
|
end
|
47
73
|
|
48
|
-
#
|
74
|
+
# DEPRECATED - See Yajl::Encoder
|
49
75
|
def self.encode(obj, io)
|
50
76
|
STDERR.puts "WARNING: Yajl::Stream has be deprecated and will most likely be gone in the next release. Use the Yajl::Encoder class instead."
|
51
77
|
Encoder.new.encode(obj, io)
|
@@ -5,28 +5,57 @@ describe "Yajl JSON encoder" do
|
|
5
5
|
FILES = Dir[File.dirname(__FILE__)+'/../../benchmark/subjects/*.json']
|
6
6
|
|
7
7
|
FILES.each do |file|
|
8
|
-
it "should encode #{File.basename(file)}" do
|
8
|
+
it "should encode #{File.basename(file)} to an IO" do
|
9
9
|
# we don't care about testing the stream subject as it has multiple JSON strings in it
|
10
10
|
if File.basename(file) != 'twitter_stream.json'
|
11
11
|
input = File.new(File.expand_path(file), 'r')
|
12
12
|
io = StringIO.new
|
13
|
-
parser = Yajl::Parser.new
|
14
13
|
encoder = Yajl::Encoder.new
|
15
|
-
|
16
|
-
hash
|
17
|
-
output = encoder.encode(hash, io)
|
14
|
+
hash = Yajl::Parser.parse(input)
|
15
|
+
encoder.encode(hash, io)
|
18
16
|
io.rewind
|
19
|
-
hash2 =
|
20
|
-
|
17
|
+
hash2 = Yajl::Parser.parse(io)
|
21
18
|
io.close
|
22
19
|
input.close
|
23
|
-
|
24
20
|
hash.should == hash2
|
25
21
|
end
|
26
22
|
end
|
27
23
|
end
|
28
24
|
|
29
|
-
|
25
|
+
FILES.each do |file|
|
26
|
+
it "should encode #{File.basename(file)} and return a String" do
|
27
|
+
# we don't care about testing the stream subject as it has multiple JSON strings in it
|
28
|
+
if File.basename(file) != 'twitter_stream.json'
|
29
|
+
input = File.new(File.expand_path(file), 'r')
|
30
|
+
encoder = Yajl::Encoder.new
|
31
|
+
hash = Yajl::Parser.parse(input)
|
32
|
+
output = encoder.encode(hash)
|
33
|
+
hash2 = Yajl::Parser.parse(output)
|
34
|
+
input.close
|
35
|
+
hash.should == hash2
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
FILES.each do |file|
|
41
|
+
it "should encode #{File.basename(file)} call the passed block, passing it a String" do
|
42
|
+
# we don't care about testing the stream subject as it has multiple JSON strings in it
|
43
|
+
if File.basename(file) != 'twitter_stream.json'
|
44
|
+
input = File.new(File.expand_path(file), 'r')
|
45
|
+
encoder = Yajl::Encoder.new
|
46
|
+
hash = Yajl::Parser.parse(input)
|
47
|
+
output = ''
|
48
|
+
encoder.encode(hash) do |json_str|
|
49
|
+
output = json_str
|
50
|
+
end
|
51
|
+
hash2 = Yajl::Parser.parse(output)
|
52
|
+
input.close
|
53
|
+
hash.should == hash2
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
it "should encode with :pretty turned on and a single space indent, to an IO" do
|
30
59
|
output = "{\n \"foo\": {\n \"name\": \"bar\",\n \"id\": 1234\n }\n}\n"
|
31
60
|
if RUBY_VERSION.include?('1.9') # FIXME
|
32
61
|
output = "{\n \"foo\": {\n \"id\": 1234,\n \"name\": \"bar\"\n }\n}\n"
|
@@ -39,7 +68,18 @@ describe "Yajl JSON encoder" do
|
|
39
68
|
io.read.should == output
|
40
69
|
end
|
41
70
|
|
42
|
-
it "should encode with :pretty turned on and a
|
71
|
+
it "should encode with :pretty turned on and a single space indent, and return a String" do
|
72
|
+
output = "{\n \"foo\": {\n \"name\": \"bar\",\n \"id\": 1234\n }\n}\n"
|
73
|
+
if RUBY_VERSION.include?('1.9') # FIXME
|
74
|
+
output = "{\n \"foo\": {\n \"id\": 1234,\n \"name\": \"bar\"\n }\n}\n"
|
75
|
+
end
|
76
|
+
obj = {:foo => {:id => 1234, :name => "bar"}}
|
77
|
+
encoder = Yajl::Encoder.new(:pretty => true, :indent => ' ')
|
78
|
+
output = encoder.encode(obj)
|
79
|
+
output.should == output
|
80
|
+
end
|
81
|
+
|
82
|
+
it "should encode with :pretty turned on and a tab character indent, to an IO" do
|
43
83
|
output = "{\n\t\"foo\": {\n\t\t\"name\": \"bar\",\n\t\t\"id\": 1234\n\t}\n}\n"
|
44
84
|
if RUBY_VERSION.include?('1.9') # FIXME
|
45
85
|
output = "{\n\t\"foo\": {\n\t\t\"id\": 1234,\n\t\t\"name\": \"bar\"\n\t}\n}\n"
|
@@ -52,7 +92,18 @@ describe "Yajl JSON encoder" do
|
|
52
92
|
io.read.should == output
|
53
93
|
end
|
54
94
|
|
55
|
-
it "should encode with
|
95
|
+
it "should encode with :pretty turned on and a tab character indent, and return a String" do
|
96
|
+
output = "{\n\t\"foo\": {\n\t\t\"name\": \"bar\",\n\t\t\"id\": 1234\n\t}\n}\n"
|
97
|
+
if RUBY_VERSION.include?('1.9') # FIXME
|
98
|
+
output = "{\n\t\"foo\": {\n\t\t\"id\": 1234,\n\t\t\"name\": \"bar\"\n\t}\n}\n"
|
99
|
+
end
|
100
|
+
obj = {:foo => {:id => 1234, :name => "bar"}}
|
101
|
+
encoder = Yajl::Encoder.new(:pretty => true, :indent => "\t")
|
102
|
+
output = encoder.encode(obj)
|
103
|
+
output.should == output
|
104
|
+
end
|
105
|
+
|
106
|
+
it "should encode with it's class method with :pretty and a tab character indent options set, to an IO" do
|
56
107
|
output = "{\n\t\"foo\": {\n\t\t\"name\": \"bar\",\n\t\t\"id\": 1234\n\t}\n}\n"
|
57
108
|
if RUBY_VERSION.include?('1.9') # FIXME
|
58
109
|
output = "{\n\t\"foo\": {\n\t\t\"id\": 1234,\n\t\t\"name\": \"bar\"\n\t}\n}\n"
|
@@ -64,7 +115,31 @@ describe "Yajl JSON encoder" do
|
|
64
115
|
io.read.should == output
|
65
116
|
end
|
66
117
|
|
67
|
-
it "should encode
|
118
|
+
it "should encode with it's class method with :pretty and a tab character indent options set, and return a String" do
|
119
|
+
output = "{\n\t\"foo\": {\n\t\t\"name\": \"bar\",\n\t\t\"id\": 1234\n\t}\n}\n"
|
120
|
+
if RUBY_VERSION.include?('1.9') # FIXME
|
121
|
+
output = "{\n\t\"foo\": {\n\t\t\"id\": 1234,\n\t\t\"name\": \"bar\"\n\t}\n}\n"
|
122
|
+
end
|
123
|
+
obj = {:foo => {:id => 1234, :name => "bar"}}
|
124
|
+
output = Yajl::Encoder.encode(obj, :pretty => true, :indent => "\t")
|
125
|
+
output.should == output
|
126
|
+
end
|
127
|
+
|
128
|
+
it "should encode with it's class method with :pretty and a tab character indent options set, to a block" do
|
129
|
+
output = "{\n\t\"foo\": {\n\t\t\"name\": \"bar\",\n\t\t\"id\": 1234\n\t}\n}\n"
|
130
|
+
if RUBY_VERSION.include?('1.9') # FIXME
|
131
|
+
output = "{\n\t\"foo\": {\n\t\t\"id\": 1234,\n\t\t\"name\": \"bar\"\n\t}\n}\n"
|
132
|
+
end
|
133
|
+
obj = {:foo => {:id => 1234, :name => "bar"}}
|
134
|
+
output = ''
|
135
|
+
Yajl::Encoder.encode(obj, :pretty => true, :indent => "\t") do |json_str|
|
136
|
+
output = json_str
|
137
|
+
end
|
138
|
+
output.should == output
|
139
|
+
end
|
140
|
+
|
141
|
+
it "should encode multiple objects into a single stream, to an IO" do
|
142
|
+
pending "Find a better way to compare order of hash keys in resulting string"
|
68
143
|
io = StringIO.new
|
69
144
|
obj = {:foo => "bar", :baz => 1234}
|
70
145
|
encoder = Yajl::Encoder.new
|
@@ -72,6 +147,25 @@ describe "Yajl JSON encoder" do
|
|
72
147
|
encoder.encode(obj, io)
|
73
148
|
end
|
74
149
|
io.rewind
|
75
|
-
|
150
|
+
output = "{\"baz\":1234,\"foo\":\"bar\"}\n{\"baz\":1234,\"foo\":\"bar\"}\n{\"baz\":1234,\"foo\":\"bar\"}\n{\"baz\":1234,\"foo\":\"bar\"}\n{\"baz\":1234,\"foo\":\"bar\"}\n"
|
151
|
+
if RUBY_VERSION.include?('1.9') # FIXME
|
152
|
+
output = "{\"foo\":\"bar\",\"baz\":1234}\n{\"foo\":\"bar\",\"baz\":1234}\n{\"foo\":\"bar\",\"baz\":1234}\n{\"foo\":\"bar\",\"baz\":1234}\n{\"foo\":\"bar\",\"baz\":1234}\n"
|
153
|
+
end
|
154
|
+
io.read.should == output
|
155
|
+
end
|
156
|
+
|
157
|
+
it "should encode multiple objects into a single stream, and return a String" do
|
158
|
+
pending "Find a better way to compare order of hash keys in resulting string"
|
159
|
+
obj = {:foo => "bar", :baz => 1234}
|
160
|
+
encoder = Yajl::Encoder.new
|
161
|
+
json_output = ''
|
162
|
+
5.times do
|
163
|
+
json_output << encoder.encode(obj)
|
164
|
+
end
|
165
|
+
output = "{\"baz\":1234,\"foo\":\"bar\"}\n{\"baz\":1234,\"foo\":\"bar\"}\n{\"baz\":1234,\"foo\":\"bar\"}\n{\"baz\":1234,\"foo\":\"bar\"}\n{\"baz\":1234,\"foo\":\"bar\"}\n"
|
166
|
+
if RUBY_VERSION.include?('1.9') # FIXME
|
167
|
+
output = "{\"foo\":\"bar\",\"baz\":1234}\n{\"foo\":\"bar\",\"baz\":1234}\n{\"foo\":\"bar\",\"baz\":1234}\n{\"foo\":\"bar\",\"baz\":1234}\n{\"foo\":\"bar\",\"baz\":1234}\n"
|
168
|
+
end
|
169
|
+
json_output.should == output
|
76
170
|
end
|
77
171
|
end
|
@@ -35,18 +35,34 @@ describe "ActiveSupport test cases" do
|
|
35
35
|
}
|
36
36
|
|
37
37
|
TESTS.each do |json, expected|
|
38
|
-
it "should be able to parse #{json}" do
|
38
|
+
it "should be able to parse #{json} as an IO" do
|
39
39
|
lambda {
|
40
40
|
parser = Yajl::Parser.new
|
41
41
|
parser.parse(StringIO.new(json)).should == expected
|
42
42
|
}.should_not raise_error(Yajl::ParseError)
|
43
43
|
end
|
44
44
|
end
|
45
|
+
|
46
|
+
TESTS.each do |json, expected|
|
47
|
+
it "should be able to parse #{json} as a string" do
|
48
|
+
lambda {
|
49
|
+
parser = Yajl::Parser.new
|
50
|
+
parser.parse(json).should == expected
|
51
|
+
}.should_not raise_error(Yajl::ParseError)
|
52
|
+
end
|
53
|
+
end
|
45
54
|
|
46
|
-
it "should fail parsing {: 1}" do
|
55
|
+
it "should fail parsing {: 1} as an IO" do
|
47
56
|
lambda {
|
48
57
|
parser = Yajl::Parser.new
|
49
58
|
parser.parse(StringIO.new("{: 1}"))
|
50
59
|
}.should raise_error(Yajl::ParseError)
|
51
60
|
end
|
61
|
+
|
62
|
+
it "should fail parsing {: 1} as a string" do
|
63
|
+
lambda {
|
64
|
+
parser = Yajl::Parser.new
|
65
|
+
parser.parse("{: 1}")
|
66
|
+
}.should raise_error(Yajl::ParseError)
|
67
|
+
end
|
52
68
|
end
|
@@ -4,7 +4,7 @@ require 'stringio'
|
|
4
4
|
|
5
5
|
describe "Chunked parser" do
|
6
6
|
before(:all) do
|
7
|
-
@final = [{
|
7
|
+
@final = [{:abc => 123}, {:def => 456}]
|
8
8
|
end
|
9
9
|
|
10
10
|
before(:each) do
|
@@ -67,6 +67,32 @@ describe "Chunked parser" do
|
|
67
67
|
path = File.expand_path(File.dirname(__FILE__) + '/../../benchmark/subjects/twitter_stream.json')
|
68
68
|
json = File.new(path, 'r')
|
69
69
|
@callback.should_receive(:call).exactly(430).times
|
70
|
-
|
70
|
+
lambda {
|
71
|
+
@parser.parse(json)
|
72
|
+
}.should_not raise_error(Yajl::ParseError)
|
73
|
+
end
|
74
|
+
|
75
|
+
it "should parse twitter_stream.json and fire callback 430 times, with a block as the callback" do
|
76
|
+
path = File.expand_path(File.dirname(__FILE__) + '/../../benchmark/subjects/twitter_stream.json')
|
77
|
+
json = File.new(path, 'r')
|
78
|
+
@callback.should_receive(:call).exactly(0).times
|
79
|
+
@parser.on_parse_complete = nil
|
80
|
+
lambda {
|
81
|
+
times = 0
|
82
|
+
@parser.parse(json) do |hsh|
|
83
|
+
times += 1
|
84
|
+
end
|
85
|
+
times.should eql(430)
|
86
|
+
}.should_not raise_error(Yajl::ParseError)
|
87
|
+
end
|
88
|
+
|
89
|
+
it "should raise a Yajl::ParseError error if multiple JSON strings were found when no on_parse_complete callback assigned" do
|
90
|
+
path = File.expand_path(File.dirname(__FILE__) + '/../../benchmark/subjects/twitter_stream.json')
|
91
|
+
json = File.new(path, 'r')
|
92
|
+
@parser.on_parse_complete = nil
|
93
|
+
@callback.should_receive(:call).exactly(0).times
|
94
|
+
lambda {
|
95
|
+
@parser.parse(json)
|
96
|
+
}.should raise_error(Yajl::ParseError)
|
71
97
|
end
|
72
98
|
end
|
@@ -8,7 +8,7 @@ describe "Parsing JSON Fixtures" do
|
|
8
8
|
FAILED = failed.inject([]) { |a, f| a << [ f, File.read(f) ] }.sort
|
9
9
|
|
10
10
|
FAILED.each do |name, source|
|
11
|
-
it "should not be able to parse #{File.basename(name)}" do
|
11
|
+
it "should not be able to parse #{File.basename(name)} as an IO" do
|
12
12
|
lambda {
|
13
13
|
parser = Yajl::Parser.new
|
14
14
|
parser.parse(StringIO.new(source))
|
@@ -16,12 +16,30 @@ describe "Parsing JSON Fixtures" do
|
|
16
16
|
end
|
17
17
|
end
|
18
18
|
|
19
|
+
FAILED.each do |name, source|
|
20
|
+
it "should not be able to parse #{File.basename(name)} as a string" do
|
21
|
+
lambda {
|
22
|
+
parser = Yajl::Parser.new
|
23
|
+
parser.parse(source)
|
24
|
+
}.should raise_error(Yajl::ParseError)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
19
28
|
PASSED.each do |name, source|
|
20
|
-
it "should be able to parse #{File.basename(name)}" do
|
29
|
+
it "should be able to parse #{File.basename(name)} as an IO" do
|
21
30
|
lambda {
|
22
31
|
parser = Yajl::Parser.new
|
23
32
|
parser.parse(StringIO.new(source))
|
24
33
|
}.should_not raise_error(Yajl::ParseError)
|
25
34
|
end
|
26
35
|
end
|
36
|
+
|
37
|
+
PASSED.each do |name, source|
|
38
|
+
it "should be able to parse #{File.basename(name)} as a string" do
|
39
|
+
lambda {
|
40
|
+
parser = Yajl::Parser.new
|
41
|
+
parser.parse(source)
|
42
|
+
}.should_not raise_error(Yajl::ParseError)
|
43
|
+
end
|
44
|
+
end
|
27
45
|
end
|
@@ -6,7 +6,7 @@ describe "One-off JSON examples" do
|
|
6
6
|
infinity = (1.0/0)
|
7
7
|
silence_warnings do
|
8
8
|
parser = Yajl::Parser.new
|
9
|
-
parser.parse(StringIO.new('{"key": 23456789012E666}')).should == {
|
9
|
+
parser.parse(StringIO.new('{"key": 23456789012E666}')).should == {:key => infinity}
|
10
10
|
end
|
11
11
|
end
|
12
12
|
|
@@ -27,17 +27,32 @@ describe "One-off JSON examples" do
|
|
27
27
|
end
|
28
28
|
|
29
29
|
it "should not parse invalid UTF8 with :check_utf8 set to true" do
|
30
|
-
pending
|
31
|
-
# not sure how to write this test yet
|
30
|
+
pending "not sure how to write this test yet"
|
32
31
|
end
|
33
32
|
|
34
33
|
it "should parse invalid UTF8 with :check_utf8 set to false" do
|
35
|
-
pending
|
36
|
-
# not sure how to write this test yet
|
34
|
+
pending "not sure how to write this test yet"
|
37
35
|
end
|
38
36
|
|
39
|
-
it "should parse using it's class method" do
|
37
|
+
it "should parse using it's class method, from an IO" do
|
40
38
|
io = StringIO.new('{"key": 1234}')
|
41
|
-
Yajl::Parser.parse(io).should == {
|
39
|
+
Yajl::Parser.parse(io).should == {:key => 1234}
|
40
|
+
end
|
41
|
+
|
42
|
+
it "should parse using it's class method, from an IO with string keys" do
|
43
|
+
parser = Yajl::Parser.new(:symbolize_keys => true)
|
44
|
+
parser.parse('{"key": 1234}').should == {:key => 1234}
|
45
|
+
end
|
46
|
+
|
47
|
+
it "should parse using it's class method, from a string" do
|
48
|
+
Yajl::Parser.parse('{"key": 1234}').should == {:key => 1234}
|
49
|
+
end
|
50
|
+
|
51
|
+
it "should parse using it's class method, from a string with a block" do
|
52
|
+
output = nil
|
53
|
+
Yajl::Parser.parse('{"key": 1234}') do |obj|
|
54
|
+
output = obj
|
55
|
+
end
|
56
|
+
output.should == {:key => 1234}
|
42
57
|
end
|
43
58
|
end
|
data/yajl-ruby.gemspec
CHANGED
@@ -2,11 +2,11 @@
|
|
2
2
|
|
3
3
|
Gem::Specification.new do |s|
|
4
4
|
s.name = %q{yajl-ruby}
|
5
|
-
s.version = "0.5.
|
5
|
+
s.version = "0.5.3"
|
6
6
|
|
7
7
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
8
8
|
s.authors = ["Brian Lopez", "Lloyd Hilaiel"]
|
9
|
-
s.date = %q{2009-
|
9
|
+
s.date = %q{2009-06-07}
|
10
10
|
s.email = %q{seniorlopez@gmail.com}
|
11
11
|
s.extensions = ["ext/extconf.rb"]
|
12
12
|
s.extra_rdoc_files = [
|
@@ -142,7 +142,7 @@ Gem::Specification.new do |s|
|
|
142
142
|
s.homepage = %q{http://github.com/brianmario/yajl-ruby}
|
143
143
|
s.rdoc_options = ["--charset=UTF-8"]
|
144
144
|
s.require_paths = ["lib", "ext"]
|
145
|
-
s.rubygems_version = %q{1.3.
|
145
|
+
s.rubygems_version = %q{1.3.4}
|
146
146
|
s.summary = %q{Ruby C bindings to the excellent Yajl JSON stream-based parser library.}
|
147
147
|
s.test_files = [
|
148
148
|
"spec/encoding/encoding_spec.rb",
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: brianmario-yajl-ruby
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.5.
|
4
|
+
version: 0.5.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Brian Lopez
|
@@ -10,7 +10,7 @@ autorequire:
|
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
12
|
|
13
|
-
date: 2009-
|
13
|
+
date: 2009-06-07 00:00:00 -07:00
|
14
14
|
default_executable:
|
15
15
|
dependencies: []
|
16
16
|
|