yajl-ruby 1.3.1 → 1.4.0
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/ext/yajl/extconf.rb +1 -1
- data/ext/yajl/yajl_ext.c +437 -12
- data/ext/yajl/yajl_ext.h +1 -1
- 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/version.rb +1 -1
- data/spec/projection/project_file.rb +41 -0
- data/spec/projection/projection.rb +498 -0
- data/yajl-ruby.gemspec +1 -0
- metadata +20 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5006e05689ab5c95a3e4dd98b03c66a7bb46394c
|
4
|
+
data.tar.gz: ecfd90a4a8993aa166c0f2b0b983dc05bfa96e51
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9e21e2069c984380ce506562aa837b4e02207e3368085b7cbf651dd2075de0c4d0ec43e1e1006abcaa947c797b2ec6a59bba7c73bea17ae9b47e1f01b3552dbb
|
7
|
+
data.tar.gz: 01df11ba7ab076382eb270e692bb316f2d5ab06034275661f65ff58888a89d1ffed25d2a4081bd9cad9c8a0275eaa8c23411528d2ca9a3423a83e9bba59b2d57
|
data/ext/yajl/extconf.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
require 'mkmf'
|
2
2
|
require 'rbconfig'
|
3
3
|
|
4
|
-
$CFLAGS << ' -Wall -funroll-loops'
|
4
|
+
$CFLAGS << ' -Wall -funroll-loops -Wno-declaration-after-statement'
|
5
5
|
$CFLAGS << ' -Werror-implicit-function-declaration -Wextra -O0 -ggdb3' if ENV['DEBUG']
|
6
6
|
|
7
7
|
create_makefile('yajl/yajl')
|
data/ext/yajl/yajl_ext.c
CHANGED
@@ -22,6 +22,12 @@
|
|
22
22
|
*/
|
23
23
|
|
24
24
|
#include "yajl_ext.h"
|
25
|
+
#include "yajl_lex.h"
|
26
|
+
#include "yajl_alloc.h"
|
27
|
+
#include "yajl_buf.h"
|
28
|
+
#include "yajl_encode.h"
|
29
|
+
#include "api/yajl_common.h"
|
30
|
+
#include "assert.h"
|
25
31
|
|
26
32
|
#define YAJL_RB_TO_JSON \
|
27
33
|
VALUE rb_encoder, cls; \
|
@@ -32,6 +38,25 @@
|
|
32
38
|
} \
|
33
39
|
return rb_yajl_encoder_encode(1, &self, rb_encoder); \
|
34
40
|
|
41
|
+
static void *rb_internal_malloc(void *ctx, unsigned int sz) {
|
42
|
+
return xmalloc(sz);
|
43
|
+
}
|
44
|
+
|
45
|
+
static void *rb_internal_realloc(void *ctx, void *previous, unsigned int sz) {
|
46
|
+
return xrealloc(previous, sz);
|
47
|
+
}
|
48
|
+
|
49
|
+
static void rb_internal_free(void *ctx, void *ptr) {
|
50
|
+
xfree(ptr);
|
51
|
+
}
|
52
|
+
|
53
|
+
static yajl_alloc_funcs rb_alloc_funcs = {
|
54
|
+
rb_internal_malloc,
|
55
|
+
rb_internal_realloc,
|
56
|
+
rb_internal_free,
|
57
|
+
NULL
|
58
|
+
};
|
59
|
+
|
35
60
|
/* Helpers for building objects */
|
36
61
|
static void yajl_check_and_fire_callback(void * ctx) {
|
37
62
|
yajl_parser_wrapper * wrapper;
|
@@ -39,12 +64,12 @@ static void yajl_check_and_fire_callback(void * ctx) {
|
|
39
64
|
|
40
65
|
/* No need to do any of this if the callback isn't even setup */
|
41
66
|
if (wrapper->parse_complete_callback != Qnil) {
|
42
|
-
|
67
|
+
long len = RARRAY_LEN(wrapper->builderStack);
|
43
68
|
if (len == 1 && wrapper->nestedArrayLevel == 0 && wrapper->nestedHashLevel == 0) {
|
44
69
|
rb_funcall(wrapper->parse_complete_callback, intern_call, 1, rb_ary_pop(wrapper->builderStack));
|
45
70
|
}
|
46
71
|
} else {
|
47
|
-
|
72
|
+
long len = RARRAY_LEN(wrapper->builderStack);
|
48
73
|
if (len == 1 && wrapper->nestedArrayLevel == 0 && wrapper->nestedHashLevel == 0) {
|
49
74
|
wrapper->objectsFound++;
|
50
75
|
if (wrapper->objectsFound > 1) {
|
@@ -76,7 +101,7 @@ static char *yajl_raise_encode_error_for_status(yajl_gen_status status, VALUE ob
|
|
76
101
|
static void yajl_set_static_value(void * ctx, VALUE val) {
|
77
102
|
yajl_parser_wrapper * wrapper;
|
78
103
|
VALUE lastEntry, hash;
|
79
|
-
|
104
|
+
long len;
|
80
105
|
|
81
106
|
GetParser((VALUE)ctx, wrapper);
|
82
107
|
|
@@ -198,7 +223,7 @@ void yajl_encode_part(void * wrapper, VALUE obj, VALUE io) {
|
|
198
223
|
case T_BIGNUM:
|
199
224
|
str = rb_funcall(obj, intern_to_s, 0);
|
200
225
|
cptr = RSTRING_PTR(str);
|
201
|
-
len = RSTRING_LEN(str);
|
226
|
+
len = (unsigned int)RSTRING_LEN(str);
|
202
227
|
if (memcmp(cptr, "NaN", 3) == 0 || memcmp(cptr, "Infinity", 8) == 0 || memcmp(cptr, "-Infinity", 9) == 0) {
|
203
228
|
rb_raise(cEncodeError, "'%s' is an invalid number", cptr);
|
204
229
|
}
|
@@ -206,7 +231,7 @@ void yajl_encode_part(void * wrapper, VALUE obj, VALUE io) {
|
|
206
231
|
break;
|
207
232
|
case T_STRING:
|
208
233
|
cptr = RSTRING_PTR(obj);
|
209
|
-
len = RSTRING_LEN(obj);
|
234
|
+
len = (unsigned int)RSTRING_LEN(obj);
|
210
235
|
CHECK_STATUS(yajl_gen_string(w->encoder, (const unsigned char *)cptr, len));
|
211
236
|
break;
|
212
237
|
default:
|
@@ -214,13 +239,13 @@ void yajl_encode_part(void * wrapper, VALUE obj, VALUE io) {
|
|
214
239
|
str = rb_funcall(obj, intern_to_json, 0);
|
215
240
|
Check_Type(str, T_STRING);
|
216
241
|
cptr = RSTRING_PTR(str);
|
217
|
-
len = RSTRING_LEN(str);
|
242
|
+
len = (unsigned int)RSTRING_LEN(str);
|
218
243
|
CHECK_STATUS(yajl_gen_number(w->encoder, cptr, len));
|
219
244
|
} else {
|
220
245
|
str = rb_funcall(obj, intern_to_s, 0);
|
221
246
|
Check_Type(str, T_STRING);
|
222
247
|
cptr = RSTRING_PTR(str);
|
223
|
-
len = RSTRING_LEN(str);
|
248
|
+
len = (unsigned int)RSTRING_LEN(str);
|
224
249
|
CHECK_STATUS(yajl_gen_string(w->encoder, (const unsigned char *)cptr, len));
|
225
250
|
}
|
226
251
|
break;
|
@@ -420,7 +445,7 @@ static VALUE rb_yajl_parser_new(int argc, VALUE * argv, VALUE klass) {
|
|
420
445
|
cfg = (yajl_parser_config){allowComments, checkUTF8};
|
421
446
|
|
422
447
|
obj = Data_Make_Struct(klass, yajl_parser_wrapper, yajl_parser_wrapper_mark, yajl_parser_wrapper_free, wrapper);
|
423
|
-
wrapper->parser = yajl_alloc(&callbacks, &cfg,
|
448
|
+
wrapper->parser = yajl_alloc(&callbacks, &cfg, &rb_alloc_funcs, (void *)obj);
|
424
449
|
wrapper->nestedArrayLevel = 0;
|
425
450
|
wrapper->nestedHashLevel = 0;
|
426
451
|
wrapper->objectsFound = 0;
|
@@ -489,13 +514,13 @@ static VALUE rb_yajl_parser_parse(int argc, VALUE * argv, VALUE self) {
|
|
489
514
|
|
490
515
|
if (TYPE(input) == T_STRING) {
|
491
516
|
cptr = RSTRING_PTR(input);
|
492
|
-
len = RSTRING_LEN(input);
|
517
|
+
len = (unsigned int)RSTRING_LEN(input);
|
493
518
|
yajl_parse_chunk((const unsigned char*)cptr, len, wrapper->parser);
|
494
519
|
} else if (rb_respond_to(input, intern_io_read)) {
|
495
520
|
VALUE parsed = rb_str_new(0, FIX2LONG(rbufsize));
|
496
521
|
while (rb_funcall(input, intern_io_read, 2, rbufsize, parsed) != Qnil) {
|
497
522
|
cptr = RSTRING_PTR(parsed);
|
498
|
-
len = RSTRING_LEN(parsed);
|
523
|
+
len = (unsigned int)RSTRING_LEN(parsed);
|
499
524
|
yajl_parse_chunk((const unsigned char*)cptr, len, wrapper->parser);
|
500
525
|
}
|
501
526
|
} else {
|
@@ -535,7 +560,7 @@ static VALUE rb_yajl_parser_parse_chunk(VALUE self, VALUE chunk) {
|
|
535
560
|
|
536
561
|
if (wrapper->parse_complete_callback != Qnil) {
|
537
562
|
const char * cptr = RSTRING_PTR(chunk);
|
538
|
-
len = RSTRING_LEN(chunk);
|
563
|
+
len = (unsigned int)RSTRING_LEN(chunk);
|
539
564
|
yajl_parse_chunk((const unsigned char*)cptr, len, wrapper->parser);
|
540
565
|
} else {
|
541
566
|
rb_raise(cParseError, "The on_parse_complete callback isn't setup, parsing useless.");
|
@@ -560,6 +585,402 @@ static VALUE rb_yajl_parser_set_complete_cb(VALUE self, VALUE callback) {
|
|
560
585
|
return Qnil;
|
561
586
|
}
|
562
587
|
|
588
|
+
/*
|
589
|
+
* An event stream pulls data off the IO source into the buffer,
|
590
|
+
* then runs the lexer over that stream.
|
591
|
+
*/
|
592
|
+
struct yajl_event_stream_s {
|
593
|
+
yajl_alloc_funcs *funcs;
|
594
|
+
|
595
|
+
VALUE stream; // source
|
596
|
+
|
597
|
+
VALUE buffer;
|
598
|
+
unsigned int offset;
|
599
|
+
|
600
|
+
yajl_lexer lexer; // event source
|
601
|
+
};
|
602
|
+
|
603
|
+
typedef struct yajl_event_stream_s *yajl_event_stream_t;
|
604
|
+
|
605
|
+
struct yajl_event_s {
|
606
|
+
yajl_tok token;
|
607
|
+
const char *buf;
|
608
|
+
unsigned int len;
|
609
|
+
};
|
610
|
+
typedef struct yajl_event_s yajl_event_t;
|
611
|
+
|
612
|
+
static yajl_event_t yajl_event_stream_next(yajl_event_stream_t parser, int pop) {
|
613
|
+
assert(parser->stream);
|
614
|
+
assert(parser->buffer);
|
615
|
+
|
616
|
+
while (1) {
|
617
|
+
if (parser->offset >= RSTRING_LEN(parser->buffer)) {
|
618
|
+
//printf("reading offset %d size %ld\n", parser->offset, RSTRING_LEN(parser->buffer));
|
619
|
+
|
620
|
+
// Refill the buffer
|
621
|
+
rb_funcall(parser->stream, intern_io_read, 2, INT2FIX(RSTRING_LEN(parser->buffer)), parser->buffer);
|
622
|
+
if (RSTRING_LEN(parser->buffer) == 0) {
|
623
|
+
yajl_event_t event = {
|
624
|
+
.token = yajl_tok_eof,
|
625
|
+
};
|
626
|
+
return event;
|
627
|
+
}
|
628
|
+
|
629
|
+
parser->offset = 0;
|
630
|
+
}
|
631
|
+
|
632
|
+
// Try to pull an event off the lexer
|
633
|
+
yajl_event_t event;
|
634
|
+
|
635
|
+
yajl_tok token;
|
636
|
+
if (pop == 0) {
|
637
|
+
//printf("peeking %p %ld %d\n", RSTRING_PTR(parser->buffer), RSTRING_LEN(parser->buffer), parser->offset);
|
638
|
+
token = yajl_lex_peek(parser->lexer, (const unsigned char *)RSTRING_PTR(parser->buffer), (unsigned int)RSTRING_LEN(parser->buffer), parser->offset);
|
639
|
+
//printf("peeked event %d\n", token);
|
640
|
+
|
641
|
+
if (token == yajl_tok_eof) {
|
642
|
+
parser->offset = (unsigned int)RSTRING_LEN(parser->buffer);
|
643
|
+
continue;
|
644
|
+
}
|
645
|
+
|
646
|
+
event.token = token;
|
647
|
+
|
648
|
+
return event;
|
649
|
+
}
|
650
|
+
|
651
|
+
//printf("popping\n");
|
652
|
+
token = yajl_lex_lex(parser->lexer, (const unsigned char *)RSTRING_PTR(parser->buffer), (unsigned int)RSTRING_LEN(parser->buffer), &parser->offset, (const unsigned char **)&event.buf, &event.len);
|
653
|
+
//printf("popped event %d\n", token);
|
654
|
+
|
655
|
+
if (token == yajl_tok_eof) {
|
656
|
+
continue;
|
657
|
+
}
|
658
|
+
|
659
|
+
event.token = token;
|
660
|
+
|
661
|
+
return event;
|
662
|
+
}
|
663
|
+
|
664
|
+
return (yajl_event_t){};
|
665
|
+
}
|
666
|
+
|
667
|
+
static VALUE rb_yajl_projector_filter_array_subtree(yajl_event_stream_t parser, VALUE schema, yajl_event_t event);
|
668
|
+
static VALUE rb_yajl_projector_filter_object_subtree(yajl_event_stream_t parser, VALUE schema, yajl_event_t event);
|
669
|
+
static void rb_yajl_projector_ignore_value(yajl_event_stream_t parser);
|
670
|
+
static void rb_yajl_projector_ignore_container(yajl_event_stream_t parser);
|
671
|
+
static VALUE rb_yajl_projector_build_simple_value(yajl_event_stream_t parser, yajl_event_t event);
|
672
|
+
static VALUE rb_yajl_projector_build_string(yajl_event_stream_t parser, yajl_event_t event);
|
673
|
+
|
674
|
+
static VALUE rb_yajl_projector_filter(yajl_event_stream_t parser, VALUE schema, yajl_event_t event) {
|
675
|
+
assert(parser->stream);
|
676
|
+
|
677
|
+
switch(event.token) {
|
678
|
+
case yajl_tok_left_brace:
|
679
|
+
return rb_yajl_projector_filter_array_subtree(parser, schema, event);
|
680
|
+
break;
|
681
|
+
case yajl_tok_left_bracket:
|
682
|
+
return rb_yajl_projector_filter_object_subtree(parser, schema, event);
|
683
|
+
break;
|
684
|
+
default:
|
685
|
+
return rb_yajl_projector_build_simple_value(parser, event);
|
686
|
+
}
|
687
|
+
}
|
688
|
+
|
689
|
+
static VALUE rb_yajl_projector_filter_array_subtree(yajl_event_stream_t parser, VALUE schema, yajl_event_t event) {
|
690
|
+
assert(event.token == yajl_tok_left_brace);
|
691
|
+
|
692
|
+
VALUE ary = rb_ary_new();
|
693
|
+
|
694
|
+
while (1) {
|
695
|
+
event = yajl_event_stream_next(parser, 1);
|
696
|
+
|
697
|
+
if (event.token == yajl_tok_right_brace) {
|
698
|
+
break;
|
699
|
+
}
|
700
|
+
|
701
|
+
VALUE val = rb_yajl_projector_filter(parser, schema, event);
|
702
|
+
rb_ary_push(ary, val);
|
703
|
+
|
704
|
+
event = yajl_event_stream_next(parser, 0);
|
705
|
+
if (event.token == yajl_tok_comma) {
|
706
|
+
event = yajl_event_stream_next(parser, 1);
|
707
|
+
assert(event.token == yajl_tok_comma);
|
708
|
+
|
709
|
+
event = yajl_event_stream_next(parser, 0);
|
710
|
+
if (!(event.token == yajl_tok_string || event.token == yajl_tok_integer || event.token == yajl_tok_double || event.token == yajl_tok_null || event.token == yajl_tok_bool || event.token == yajl_tok_left_bracket || event.token == yajl_tok_left_brace)) {
|
711
|
+
rb_raise(cParseError, "read a comma, expected a value to follow, actually read %s", yajl_tok_name(event.token));
|
712
|
+
}
|
713
|
+
} else if (event.token != yajl_tok_right_brace) {
|
714
|
+
rb_raise(cParseError, "didn't read a comma, expected closing array, actually read %s", yajl_tok_name(event.token));
|
715
|
+
}
|
716
|
+
}
|
717
|
+
|
718
|
+
return ary;
|
719
|
+
}
|
720
|
+
|
721
|
+
static VALUE rb_yajl_projector_filter_object_subtree(yajl_event_stream_t parser, VALUE schema, yajl_event_t event) {
|
722
|
+
assert(event.token == yajl_tok_left_bracket);
|
723
|
+
|
724
|
+
VALUE hsh = rb_hash_new();
|
725
|
+
|
726
|
+
while (1) {
|
727
|
+
event = yajl_event_stream_next(parser, 1);
|
728
|
+
|
729
|
+
if (event.token == yajl_tok_right_bracket) {
|
730
|
+
break;
|
731
|
+
}
|
732
|
+
|
733
|
+
if (!(event.token == yajl_tok_string || event.token == yajl_tok_string_with_escapes)) {
|
734
|
+
rb_raise(cParseError, "Expected string, unexpected stream event %s", yajl_tok_name(event.token));
|
735
|
+
}
|
736
|
+
|
737
|
+
VALUE key = rb_yajl_projector_build_string(parser, event);
|
738
|
+
|
739
|
+
event = yajl_event_stream_next(parser, 1);
|
740
|
+
if (!(event.token == yajl_tok_colon)) {
|
741
|
+
rb_raise(cParseError, "Expected colon, unexpected stream event %s", yajl_tok_name(event.token));
|
742
|
+
}
|
743
|
+
|
744
|
+
// nil schema means reify the subtree from here on
|
745
|
+
// otherwise if the schema has a key for this we want it
|
746
|
+
int interesting = (schema == Qnil || rb_funcall(schema, rb_intern("key?"), 1, key) == Qtrue);
|
747
|
+
if (!interesting) {
|
748
|
+
rb_yajl_projector_ignore_value(parser);
|
749
|
+
goto peek_comma;
|
750
|
+
}
|
751
|
+
|
752
|
+
yajl_event_t value_event = yajl_event_stream_next(parser, 1);
|
753
|
+
|
754
|
+
VALUE key_schema;
|
755
|
+
if (schema == Qnil) {
|
756
|
+
key_schema = Qnil;
|
757
|
+
} else {
|
758
|
+
key_schema = rb_hash_aref(schema, key);
|
759
|
+
}
|
760
|
+
|
761
|
+
VALUE val = rb_yajl_projector_filter(parser, key_schema, value_event);
|
762
|
+
|
763
|
+
rb_str_freeze(key);
|
764
|
+
rb_hash_aset(hsh, key, val);
|
765
|
+
|
766
|
+
peek_comma:
|
767
|
+
|
768
|
+
event = yajl_event_stream_next(parser, 0);
|
769
|
+
if (event.token == yajl_tok_comma) {
|
770
|
+
event = yajl_event_stream_next(parser, 1);
|
771
|
+
assert(event.token == yajl_tok_comma);
|
772
|
+
|
773
|
+
event = yajl_event_stream_next(parser, 0);
|
774
|
+
if (!(event.token == yajl_tok_string || event.token == yajl_tok_string_with_escapes)) {
|
775
|
+
rb_raise(cParseError, "read a comma, expected a key to follow, actually read %s", yajl_tok_name(event.token));
|
776
|
+
}
|
777
|
+
} else if (event.token != yajl_tok_right_bracket) {
|
778
|
+
rb_raise(cParseError, "read a value without tailing comma, expected closing bracket, actually read %s", yajl_tok_name(event.token));
|
779
|
+
}
|
780
|
+
}
|
781
|
+
|
782
|
+
return hsh;
|
783
|
+
}
|
784
|
+
|
785
|
+
/*
|
786
|
+
# After reading a key if we know we are not interested in the next value,
|
787
|
+
# read and discard all its stream events.
|
788
|
+
#
|
789
|
+
# Values can be simple (string, numeric, boolean, null) or compound (object
|
790
|
+
# or array).
|
791
|
+
#
|
792
|
+
# Returns nothing.
|
793
|
+
*/
|
794
|
+
static void rb_yajl_projector_ignore_value(yajl_event_stream_t parser) {
|
795
|
+
yajl_event_t value_event = yajl_event_stream_next(parser, 1);
|
796
|
+
|
797
|
+
switch (value_event.token) {
|
798
|
+
case yajl_tok_null:
|
799
|
+
case yajl_tok_bool:
|
800
|
+
case yajl_tok_integer:
|
801
|
+
case yajl_tok_double:
|
802
|
+
case yajl_tok_string:
|
803
|
+
case yajl_tok_string_with_escapes:
|
804
|
+
return;
|
805
|
+
default:
|
806
|
+
break;
|
807
|
+
}
|
808
|
+
|
809
|
+
if (value_event.token == yajl_tok_left_brace || value_event.token == yajl_tok_left_bracket) {
|
810
|
+
rb_yajl_projector_ignore_container(parser);
|
811
|
+
return;
|
812
|
+
}
|
813
|
+
|
814
|
+
rb_raise(cParseError, "unknown value type to ignore %s", yajl_tok_name(value_event.token));
|
815
|
+
}
|
816
|
+
|
817
|
+
/*
|
818
|
+
# Given the start of an array or object, read until the closing event.
|
819
|
+
# Object structures can nest and this is considered.
|
820
|
+
#
|
821
|
+
# Returns nothing.
|
822
|
+
*/
|
823
|
+
static void rb_yajl_projector_ignore_container(yajl_event_stream_t parser) {
|
824
|
+
int depth = 1;
|
825
|
+
|
826
|
+
while (depth > 0) {
|
827
|
+
yajl_event_t event = yajl_event_stream_next(parser, 1);
|
828
|
+
|
829
|
+
if (event.token == yajl_tok_eof) {
|
830
|
+
return;
|
831
|
+
}
|
832
|
+
|
833
|
+
if (event.token == yajl_tok_left_bracket || event.token == yajl_tok_left_brace) {
|
834
|
+
depth += 1;
|
835
|
+
} else if (event.token == yajl_tok_right_bracket || event.token == yajl_tok_right_brace) {
|
836
|
+
depth -= 1;
|
837
|
+
}
|
838
|
+
}
|
839
|
+
}
|
840
|
+
|
841
|
+
static VALUE rb_yajl_projector_build_simple_value(yajl_event_stream_t parser, yajl_event_t event) {
|
842
|
+
assert(parser->stream);
|
843
|
+
|
844
|
+
switch (event.token) {
|
845
|
+
case yajl_tok_null:;
|
846
|
+
return Qnil;
|
847
|
+
case yajl_tok_bool:;
|
848
|
+
if (memcmp(event.buf, "true", 4) == 0) {
|
849
|
+
return Qtrue;
|
850
|
+
} else if (memcmp(event.buf, "false", 4) == 0) {
|
851
|
+
return Qfalse;
|
852
|
+
} else {
|
853
|
+
rb_raise(cStandardError, "unknown boolean token %s", event.buf);
|
854
|
+
}
|
855
|
+
case yajl_tok_integer:;
|
856
|
+
case yajl_tok_double:;
|
857
|
+
char *buf = (char *)malloc(event.len + 1);
|
858
|
+
buf[event.len] = 0;
|
859
|
+
memcpy(buf, event.buf, event.len);
|
860
|
+
|
861
|
+
VALUE val;
|
862
|
+
if (memchr(buf, '.', event.len) ||
|
863
|
+
memchr(buf, 'e', event.len) ||
|
864
|
+
memchr(buf, 'E', event.len)) {
|
865
|
+
val = rb_float_new(strtod(buf, NULL));
|
866
|
+
} else {
|
867
|
+
val = rb_cstr2inum(buf, 10);
|
868
|
+
}
|
869
|
+
free(buf);
|
870
|
+
|
871
|
+
return val;
|
872
|
+
|
873
|
+
case yajl_tok_string:;
|
874
|
+
case yajl_tok_string_with_escapes:;
|
875
|
+
return rb_yajl_projector_build_string(parser, event);
|
876
|
+
|
877
|
+
case yajl_tok_eof:;
|
878
|
+
rb_raise(cParseError, "unexpected eof while constructing value");
|
879
|
+
|
880
|
+
case yajl_tok_comma:
|
881
|
+
rb_raise(cParseError, "unexpected comma while constructing value");
|
882
|
+
|
883
|
+
case yajl_tok_colon:
|
884
|
+
rb_raise(cParseError, "unexpected colon while constructing value");
|
885
|
+
|
886
|
+
default:;
|
887
|
+
assert(0);
|
888
|
+
}
|
889
|
+
}
|
890
|
+
|
891
|
+
static VALUE rb_yajl_projector_build_string(yajl_event_stream_t parser, yajl_event_t event) {
|
892
|
+
switch (event.token) {
|
893
|
+
case yajl_tok_string:; {
|
894
|
+
VALUE str = rb_str_new(event.buf, event.len);
|
895
|
+
rb_enc_associate(str, utf8Encoding);
|
896
|
+
|
897
|
+
rb_encoding *default_internal_enc = rb_default_internal_encoding();
|
898
|
+
if (default_internal_enc) {
|
899
|
+
str = rb_str_export_to_enc(str, default_internal_enc);
|
900
|
+
}
|
901
|
+
|
902
|
+
return str;
|
903
|
+
}
|
904
|
+
|
905
|
+
case yajl_tok_string_with_escapes:; {
|
906
|
+
//printf("decoding string with escapes\n");
|
907
|
+
|
908
|
+
yajl_buf strBuf = yajl_buf_alloc(parser->funcs);
|
909
|
+
yajl_string_decode(strBuf, (const unsigned char *)event.buf, event.len);
|
910
|
+
|
911
|
+
VALUE str = rb_str_new((const char *)yajl_buf_data(strBuf), yajl_buf_len(strBuf));
|
912
|
+
rb_enc_associate(str, utf8Encoding);
|
913
|
+
|
914
|
+
yajl_buf_free(strBuf);
|
915
|
+
|
916
|
+
rb_encoding *default_internal_enc = rb_default_internal_encoding();
|
917
|
+
if (default_internal_enc) {
|
918
|
+
str = rb_str_export_to_enc(str, default_internal_enc);
|
919
|
+
}
|
920
|
+
|
921
|
+
return str;
|
922
|
+
}
|
923
|
+
|
924
|
+
default:; {
|
925
|
+
assert(0);
|
926
|
+
}
|
927
|
+
}
|
928
|
+
}
|
929
|
+
|
930
|
+
static VALUE rb_protected_yajl_projector_filter(VALUE pointer) {
|
931
|
+
VALUE *args = (VALUE *)pointer;
|
932
|
+
return rb_yajl_projector_filter((struct yajl_event_stream_s *)args[0],
|
933
|
+
args[1],
|
934
|
+
*(yajl_event_t *)args[2]);
|
935
|
+
}
|
936
|
+
|
937
|
+
/*
|
938
|
+
* Document-method: project
|
939
|
+
*/
|
940
|
+
static VALUE rb_yajl_projector_project(VALUE self, VALUE schema) {
|
941
|
+
VALUE stream = rb_iv_get(self, "@stream");
|
942
|
+
|
943
|
+
long buffer_size = FIX2LONG(rb_iv_get(self, "@buffer_size"));
|
944
|
+
VALUE buffer = rb_str_new(0, buffer_size);
|
945
|
+
|
946
|
+
struct yajl_event_stream_s parser = {
|
947
|
+
.funcs = &rb_alloc_funcs,
|
948
|
+
|
949
|
+
.stream = stream,
|
950
|
+
|
951
|
+
.buffer = buffer,
|
952
|
+
.offset = (unsigned int)buffer_size,
|
953
|
+
|
954
|
+
.lexer = yajl_lex_alloc(&rb_alloc_funcs, 0, 1),
|
955
|
+
};
|
956
|
+
|
957
|
+
yajl_event_t event = yajl_event_stream_next(&parser, 1);
|
958
|
+
|
959
|
+
RB_GC_GUARD(stream);
|
960
|
+
RB_GC_GUARD(buffer);
|
961
|
+
|
962
|
+
VALUE result;
|
963
|
+
int state = 0;
|
964
|
+
|
965
|
+
if (event.token == yajl_tok_left_brace || event.token == yajl_tok_left_bracket) {
|
966
|
+
VALUE args[3];
|
967
|
+
args[0] = (VALUE)&parser;
|
968
|
+
args[1] = schema;
|
969
|
+
args[2] = (VALUE)&event;
|
970
|
+
result = rb_protect(rb_protected_yajl_projector_filter,
|
971
|
+
(VALUE)args,
|
972
|
+
&state);
|
973
|
+
} else {
|
974
|
+
yajl_lex_free(parser.lexer);
|
975
|
+
rb_raise(cParseError, "expected left bracket or brace, actually read %s", yajl_tok_name(event.token));
|
976
|
+
}
|
977
|
+
|
978
|
+
yajl_lex_free(parser.lexer);
|
979
|
+
if (state) rb_jump_tag(state);
|
980
|
+
|
981
|
+
return result;
|
982
|
+
}
|
983
|
+
|
563
984
|
/*
|
564
985
|
* Document-class: Yajl::Encoder
|
565
986
|
*
|
@@ -620,7 +1041,7 @@ static VALUE rb_yajl_encoder_new(int argc, VALUE * argv, VALUE klass) {
|
|
620
1041
|
|
621
1042
|
obj = Data_Make_Struct(klass, yajl_encoder_wrapper, yajl_encoder_wrapper_mark, yajl_encoder_wrapper_free, wrapper);
|
622
1043
|
wrapper->indentString = actualIndent;
|
623
|
-
wrapper->encoder = yajl_gen_alloc(&cfg,
|
1044
|
+
wrapper->encoder = yajl_gen_alloc(&cfg, &rb_alloc_funcs);
|
624
1045
|
wrapper->on_progress_callback = Qnil;
|
625
1046
|
if (opts != Qnil && rb_funcall(opts, intern_has_key, 1, sym_terminator) == Qtrue) {
|
626
1047
|
wrapper->terminator = rb_hash_aref(opts, sym_terminator);
|
@@ -900,6 +1321,7 @@ void Init_yajl() {
|
|
900
1321
|
|
901
1322
|
cParseError = rb_define_class_under(mYajl, "ParseError", rb_eStandardError);
|
902
1323
|
cEncodeError = rb_define_class_under(mYajl, "EncodeError", rb_eStandardError);
|
1324
|
+
cStandardError = rb_const_get(rb_cObject, rb_intern("StandardError"));
|
903
1325
|
|
904
1326
|
cParser = rb_define_class_under(mYajl, "Parser", rb_cObject);
|
905
1327
|
rb_define_singleton_method(cParser, "new", rb_yajl_parser_new, -1);
|
@@ -909,6 +1331,9 @@ void Init_yajl() {
|
|
909
1331
|
rb_define_method(cParser, "<<", rb_yajl_parser_parse_chunk, 1);
|
910
1332
|
rb_define_method(cParser, "on_parse_complete=", rb_yajl_parser_set_complete_cb, 1);
|
911
1333
|
|
1334
|
+
cProjector = rb_define_class_under(mYajl, "Projector", rb_cObject);
|
1335
|
+
rb_define_method(cProjector, "project", rb_yajl_projector_project, 1);
|
1336
|
+
|
912
1337
|
cEncoder = rb_define_class_under(mYajl, "Encoder", rb_cObject);
|
913
1338
|
rb_define_singleton_method(cEncoder, "new", rb_yajl_encoder_new, -1);
|
914
1339
|
rb_define_method(cEncoder, "initialize", rb_yajl_encoder_init, -1);
|
data/ext/yajl/yajl_ext.h
CHANGED
@@ -53,7 +53,7 @@ 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
59
|
static ID sym_allow_comments, sym_check_utf8, sym_pretty, sym_indent, sym_terminator, sym_symbolize_keys, sym_symbolize_names, sym_html_safe;
|
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/version.rb
CHANGED
@@ -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
|
data/yajl-ruby.gemspec
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: yajl-ruby
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Brian Lopez
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2018-04-27 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rake-compiler
|
@@ -67,6 +67,20 @@ dependencies:
|
|
67
67
|
- - ">="
|
68
68
|
- !ruby/object:Gem::Version
|
69
69
|
version: '0'
|
70
|
+
- !ruby/object:Gem::Dependency
|
71
|
+
name: benchmark-memory
|
72
|
+
requirement: !ruby/object:Gem::Requirement
|
73
|
+
requirements:
|
74
|
+
- - "~>"
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '0.1'
|
77
|
+
type: :development
|
78
|
+
prerelease: false
|
79
|
+
version_requirements: !ruby/object:Gem::Requirement
|
80
|
+
requirements:
|
81
|
+
- - "~>"
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: '0.1'
|
70
84
|
description:
|
71
85
|
email: seniorlopez@gmail.com
|
72
86
|
executables: []
|
@@ -221,6 +235,8 @@ files:
|
|
221
235
|
- spec/parsing/fixtures_spec.rb
|
222
236
|
- spec/parsing/large_number_spec.rb
|
223
237
|
- spec/parsing/one_off_spec.rb
|
238
|
+
- spec/projection/project_file.rb
|
239
|
+
- spec/projection/projection.rb
|
224
240
|
- spec/rcov.opts
|
225
241
|
- spec/spec_helper.rb
|
226
242
|
- tasks/compile.rake
|
@@ -337,5 +353,7 @@ test_files:
|
|
337
353
|
- spec/parsing/fixtures_spec.rb
|
338
354
|
- spec/parsing/large_number_spec.rb
|
339
355
|
- spec/parsing/one_off_spec.rb
|
356
|
+
- spec/projection/project_file.rb
|
357
|
+
- spec/projection/projection.rb
|
340
358
|
- spec/rcov.opts
|
341
359
|
- spec/spec_helper.rb
|