yajl-ruby 1.3.1 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of yajl-ruby might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/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
|