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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 2211f169082b89b1385a841f79d445cd559b405c
4
- data.tar.gz: 9074ca9bab2acf80a0a7069f2a608ee958b2eea8
3
+ metadata.gz: 5006e05689ab5c95a3e4dd98b03c66a7bb46394c
4
+ data.tar.gz: ecfd90a4a8993aa166c0f2b0b983dc05bfa96e51
5
5
  SHA512:
6
- metadata.gz: 20f296b807da6097dec709b153364def6cae62187223e64027871103261800e8edda82d5d2bfe96a9dbfb3e29a6b84f56e39c09b45d3bafd423335e86aa52186
7
- data.tar.gz: 89bee3f6029488f771d15a4ebc9e910a8c1ca7d276ef5ad001bb118e7754397bfb4a4065cf5a8b8cc5edffe222bdb669742b6cbe9c6572d3656b3d683393c2b8
6
+ metadata.gz: 9e21e2069c984380ce506562aa837b4e02207e3368085b7cbf651dd2075de0c4d0ec43e1e1006abcaa947c797b2ec6a59bba7c73bea17ae9b47e1f01b3552dbb
7
+ data.tar.gz: 01df11ba7ab076382eb270e692bb316f2d5ab06034275661f65ff58888a89d1ffed25d2a4081bd9cad9c8a0275eaa8c23411528d2ca9a3423a83e9bba59b2d57
@@ -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')
@@ -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
- int len = RARRAY_LEN(wrapper->builderStack);
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
- int len = RARRAY_LEN(wrapper->builderStack);
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
- int len;
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, NULL, (void *)obj);
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, NULL);
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);
@@ -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;
@@ -38,29 +38,25 @@
38
38
  #include <assert.h>
39
39
  #include <string.h>
40
40
 
41
- #ifdef YAJL_LEXER_DEBUG
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 "brace";
52
- case yajl_tok_left_bracket: return "bracket";
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 "brace";
57
- case yajl_tok_right_bracket: return "bracket";
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);
@@ -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,
@@ -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
  #
@@ -1,3 +1,3 @@
1
1
  module Yajl
2
- VERSION = '1.3.1'
2
+ VERSION = '1.4.0'
3
3
  end
@@ -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
@@ -22,5 +22,6 @@ Gem::Specification.new do |s|
22
22
  # benchmarks
23
23
  s.add_development_dependency 'activesupport', '~> 3.1.2'
24
24
  s.add_development_dependency 'json'
25
+ s.add_development_dependency "benchmark-memory", "~> 0.1"
25
26
  end
26
27
 
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.3.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: 2017-11-07 00:00:00.000000000 Z
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