oj 2.2.3 → 2.3.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of oj might be problematic. Click here for more details.

checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: bf666d35187060e360daf53c2d0bedd178d3c686
4
- data.tar.gz: 3e855525649cf4cec80eaa2e49b1d895f2db9eee
3
+ metadata.gz: 0ac1090fe61f4056ff0fb5c838297e76f356fe8c
4
+ data.tar.gz: e75c5ddbce19b8846c1cac9f5dca6994194aa9e7
5
5
  SHA512:
6
- metadata.gz: 7b6c183e662e435f8eaf00079f8caa2e306d20f8fbfafcd30b7d86e8e5f715d8fe3ebf6ceaadc4e217ce140df7b572807cb5a980af22836b76a8cd971a540440
7
- data.tar.gz: fa4fe7655132ab085ce93ca6604dcfba71cfa925480c279db93dc2baf2ede9fe5d8592c101af7045587656d6e418d2ee7e7049ef49ef681a066e95a80ed9f372
6
+ metadata.gz: f509d7b7dfbd8b848a0ce4b388e962853e3db6b7e2c050639a559a837964f767e1e279b53ef3cd29c7a6ceb569ac10522f9f3726f892f7a0c13e586688a28540
7
+ data.tar.gz: f7bdb0ae179ac0e796625445447f1f0dbce85ff3b2d3af1181e16fea2128077112056c9e104b15a06e4d13b48ff630bec7fe675e316ea16500b4869edbf69287
data/README.md CHANGED
@@ -20,31 +20,18 @@ Follow [@peterohler on Twitter](http://twitter.com/#!/peterohler) for announceme
20
20
 
21
21
  [![Build Status](https://secure.travis-ci.org/ohler55/oj.png?branch=master)](http://travis-ci.org/ohler55/oj)
22
22
 
23
- ### Current Release 2.2.3
24
23
 
25
- - Fixed struct segfault on load.
24
+ ### Current Release 2.3.0
26
25
 
27
- - Added option to force float on load if a decimal number.
26
+ - JRuby is no longer supported.
28
27
 
29
- ### Current Release 2.2.2
28
+ - Thanks to Stefan Kaes the support for structs has been optimized.
30
29
 
31
- - Added mutex support for Windows.
30
+ - Better support for Rubinous
32
31
 
33
- - Protected SCP parser for GC.
32
+ - Added option to disable GG during parsing.
34
33
 
35
- ### Current Release 2.2.1
36
-
37
- - Made all VALUEs in parse volatile to avoid garbage collection while in use.
38
-
39
- ### Current Release 2.2.0
40
-
41
- - All 1.8.x versions of Ruby now have require 'rational' called.
42
-
43
- - Removed the temporary GC disable and implemented mark strategy instead.
44
-
45
- - Added new character encoding mode to support the Rails 4 escape characters of &, <, and > as xss_safe mode. The :encoding option replaces the :ascii_only option.
46
-
47
- - Change parsing of NaN to not use math.h which on older systems does not define NAN.
34
+ - Added StringWriter that allows building a JSON document one element at a time.
48
35
 
49
36
  [Older release notes](http://www.ohler.com/dev/oj_misc/release_notes.html).
50
37
 
@@ -76,10 +63,9 @@ is the :object mode.
76
63
  preference over the to_json() method. If neither the to_json() or to_hash()
77
64
  methods exist then the Oj internal Object variable encoding is used.
78
65
 
79
- Oj is compatible with Ruby 1.8.7, 1.9.2, 1.9.3, 2.0.0, JRuby, and RBX. Note that JRuby now disables support
80
- for extentions by default and is no longer supporting C extensions. Bugs reported in the JRuby MRI are no longer being
81
- fixed and there is at least one that has caused a test to be commented out for JRuby in the Oj test suite. JRuby can be
82
- build with extensions enabled. Check the documenation for JRuby installs in your environment.
66
+ Oj is compatible with Ruby 1.8.7, 1.9.2, 1.9.3, 2.0.0, and RBX. Support for
67
+ JRuby has been removed as JRuby no longer supports C extensions and there are
68
+ bugs in the older versions that are not being fixed.
83
69
 
84
70
  Oj is also compatible with Rails. Just make sure the Oj gem is installed and
85
71
  [multi_json](https://github.com/intridea/multi_json) will pick it up and use it.
@@ -84,10 +84,8 @@ static void dump_data_comp(VALUE obj, int depth, Out out);
84
84
  static void dump_data_obj(VALUE obj, int depth, Out out);
85
85
  static void dump_obj_comp(VALUE obj, int depth, Out out);
86
86
  static void dump_obj_obj(VALUE obj, int depth, Out out);
87
- #if HAS_RSTRUCT
88
87
  static void dump_struct_comp(VALUE obj, int depth, Out out);
89
88
  static void dump_struct_obj(VALUE obj, int depth, Out out);
90
- #endif
91
89
  #if HAS_IVAR_HELPERS
92
90
  static int dump_attr_cb(ID key, VALUE value, Out out);
93
91
  #endif
@@ -1484,7 +1482,6 @@ dump_obj_attrs(VALUE obj, VALUE clas, slot_t id, int depth, Out out) {
1484
1482
  *out->cur = '\0';
1485
1483
  }
1486
1484
 
1487
- #if HAS_RSTRUCT
1488
1485
  static void
1489
1486
  dump_struct_comp(VALUE obj, int depth, Out out) {
1490
1487
  if (rb_respond_to(obj, oj_to_hash_id)) {
@@ -1515,13 +1512,12 @@ static void
1515
1512
  dump_struct_obj(VALUE obj, int depth, Out out) {
1516
1513
  VALUE clas = rb_obj_class(obj);
1517
1514
  const char *class_name = rb_class2name(clas);
1518
- VALUE *vp;
1519
1515
  int i;
1520
1516
  int d2 = depth + 1;
1521
1517
  int d3 = d2 + 1;
1522
1518
  size_t len = strlen(class_name);
1523
1519
  size_t size = d2 * out->indent + d3 * out->indent + 10 + len;
1524
-
1520
+
1525
1521
  if (out->end - out->cur <= (long)size) {
1526
1522
  grow(out, size);
1527
1523
  }
@@ -1540,20 +1536,40 @@ dump_struct_obj(VALUE obj, int depth, Out out) {
1540
1536
  *out->cur++ = '"';
1541
1537
  *out->cur++ = ',';
1542
1538
  size = d3 * out->indent + 2;
1543
- for (i = (int)RSTRUCT_LEN(obj), vp = RSTRUCT_PTR(obj); 0 < i; i--, vp++) {
1544
- if (out->end - out->cur <= (long)size) {
1545
- grow(out, size);
1539
+ #ifdef RSTRUCT_LEN
1540
+ {
1541
+ VALUE *vp;
1542
+
1543
+ for (i = (int)RSTRUCT_LEN(obj), vp = RSTRUCT_PTR(obj); 0 < i; i--, vp++) {
1544
+ if (out->end - out->cur <= (long)size) {
1545
+ grow(out, size);
1546
+ }
1547
+ fill_indent(out, d3);
1548
+ dump_val(*vp, d3, out);
1549
+ *out->cur++ = ',';
1550
+ }
1551
+ }
1552
+ #else
1553
+ {
1554
+ // This is a bit risky as a struct in C ruby is not the same as a Struct
1555
+ // class in interpreted Ruby so length() may not be defined.
1556
+ int slen = FIX2INT(rb_funcall2(obj, oj_length_id, 0, 0));
1557
+
1558
+ for (i = 0; i < slen; i++) {
1559
+ if (out->end - out->cur <= (long)size) {
1560
+ grow(out, size);
1561
+ }
1562
+ fill_indent(out, d3);
1563
+ dump_val(rb_struct_aref(obj, INT2FIX(i)), d3, out);
1564
+ *out->cur++ = ',';
1546
1565
  }
1547
- fill_indent(out, d3);
1548
- dump_val(*vp, d3, out);
1549
- *out->cur++ = ',';
1550
1566
  }
1567
+ #endif
1551
1568
  out->cur--;
1552
1569
  *out->cur++ = ']';
1553
1570
  *out->cur++ = '}';
1554
1571
  *out->cur = '\0';
1555
1572
  }
1556
- #endif
1557
1573
 
1558
1574
  static void
1559
1575
  dump_odd(VALUE obj, Odd odd, VALUE clas, int depth, Out out) {
@@ -1669,7 +1685,7 @@ dump_val(VALUE obj, int depth, Out out) {
1669
1685
  default: dump_data_obj(obj, depth, out); break;
1670
1686
  }
1671
1687
  break;
1672
- #if HAS_RSTRUCT
1688
+
1673
1689
  case T_STRUCT: // for Range
1674
1690
  switch (out->opts->mode) {
1675
1691
  case StrictMode: raise_strict(obj); break;
@@ -1679,7 +1695,7 @@ dump_val(VALUE obj, int depth, Out out) {
1679
1695
  default: dump_struct_obj(obj, depth, out); break;
1680
1696
  }
1681
1697
  break;
1682
- #endif
1698
+
1683
1699
  #if (defined T_COMPLEX && defined RCOMPLEX)
1684
1700
  case T_COMPLEX:
1685
1701
  #endif
@@ -2010,3 +2026,133 @@ oj_write_leaf_to_file(Leaf leaf, const char *path, Options copts) {
2010
2026
  }
2011
2027
  fclose(f);
2012
2028
  }
2029
+
2030
+ // string writer functions
2031
+
2032
+ static void
2033
+ key_check(StrWriter sw, const char *key) {
2034
+ DumpType type = sw->types[sw->depth];
2035
+
2036
+ if (0 == key && (ObjectNew == type || ObjectType == type)) {
2037
+ rb_raise(rb_eStandardError, "Can not push onto an Object without a key.");
2038
+ } else if (0 != key && (ArrayNew == type || ArrayType == type)) {
2039
+ rb_raise(rb_eStandardError, "No key is needed to push into an array.");
2040
+ }
2041
+ }
2042
+
2043
+ static void
2044
+ push_type(StrWriter sw, DumpType type) {
2045
+ if (sw->types_end <= sw->types + sw->depth + 1) {
2046
+ size_t size = (sw->types_end - sw->types) * 2;
2047
+
2048
+ REALLOC_N(sw->types, char, size);
2049
+ sw->types_end = sw->types + size;
2050
+ }
2051
+ sw->depth++;
2052
+ sw->types[sw->depth] = type;
2053
+ }
2054
+
2055
+ static void
2056
+ maybe_comma(StrWriter sw) {
2057
+ switch (sw->types[sw->depth]) {
2058
+ case ObjectNew:
2059
+ sw->types[sw->depth] = ObjectType;
2060
+ break;
2061
+ case ArrayNew:
2062
+ sw->types[sw->depth] = ArrayType;
2063
+ break;
2064
+ case ObjectType:
2065
+ case ArrayType:
2066
+ // Always have a few characters available in the out.buf.
2067
+ *sw->out.cur++ = ',';
2068
+ break;
2069
+ }
2070
+ }
2071
+
2072
+ void
2073
+ oj_str_writer_push_object(StrWriter sw, const char *key) {
2074
+ size_t size;
2075
+
2076
+ key_check(sw, key);
2077
+ size = sw->depth * sw->out.indent + 3;
2078
+ if (sw->out.end - sw->out.cur <= size) {
2079
+ grow(&sw->out, size);
2080
+ }
2081
+ maybe_comma(sw);
2082
+ fill_indent(&sw->out, sw->depth);
2083
+ if (0 != key) {
2084
+ dump_cstr(key, strlen(key), 0, 0, &sw->out);
2085
+ *sw->out.cur++ = ':';
2086
+ }
2087
+ *sw->out.cur++ = '{';
2088
+ push_type(sw, ObjectNew);
2089
+ }
2090
+
2091
+ void
2092
+ oj_str_writer_push_array(StrWriter sw, const char *key) {
2093
+ size_t size;
2094
+
2095
+ key_check(sw, key);
2096
+ size = sw->depth * sw->out.indent + 3;
2097
+ if (sw->out.end - sw->out.cur <= size) {
2098
+ grow(&sw->out, size);
2099
+ }
2100
+ maybe_comma(sw);
2101
+ if (0 != key) {
2102
+ dump_cstr(key, strlen(key), 0, 0, &sw->out);
2103
+ *sw->out.cur++ = ':';
2104
+ }
2105
+ *sw->out.cur++ = '[';
2106
+ push_type(sw, ArrayNew);
2107
+ }
2108
+
2109
+ void
2110
+ oj_str_writer_push_value(StrWriter sw, VALUE val, const char *key) {
2111
+ size_t size;
2112
+
2113
+ key_check(sw, key);
2114
+ size = sw->depth * sw->out.indent + 3;
2115
+ if (sw->out.end - sw->out.cur <= size) {
2116
+ grow(&sw->out, size);
2117
+ }
2118
+ maybe_comma(sw);
2119
+ if (0 != key) {
2120
+ dump_cstr(key, strlen(key), 0, 0, &sw->out);
2121
+ *sw->out.cur++ = ':';
2122
+ }
2123
+ dump_val(val, sw->depth, &sw->out);
2124
+ }
2125
+
2126
+ void
2127
+ oj_str_writer_pop(StrWriter sw) {
2128
+ size_t size;
2129
+ DumpType type = sw->types[sw->depth];
2130
+
2131
+ sw->depth--;
2132
+ if (0 > sw->depth) {
2133
+ rb_raise(rb_eStandardError, "Can not pop with no open array or object.");
2134
+ }
2135
+ size = sw->depth * sw->out.indent + 2;
2136
+ if (sw->out.end - sw->out.cur <= size) {
2137
+ grow(&sw->out, size);
2138
+ }
2139
+ fill_indent(&sw->out, sw->depth);
2140
+ switch (type) {
2141
+ case ObjectNew:
2142
+ case ObjectType:
2143
+ *sw->out.cur++ = '}';
2144
+ break;
2145
+ case ArrayNew:
2146
+ case ArrayType:
2147
+ *sw->out.cur++ = ']';
2148
+ break;
2149
+ }
2150
+ }
2151
+
2152
+ void
2153
+ oj_str_writer_pop_all(StrWriter sw) {
2154
+ while (0 < sw->depth) {
2155
+ oj_str_writer_pop(sw);
2156
+ }
2157
+ }
2158
+
@@ -24,7 +24,6 @@ dflags = {
24
24
  'HAS_ENCODING_SUPPORT' => (('ruby' == type || 'rubinius' == type) &&
25
25
  (('1' == version[0] && '9' == version[1]) || '2' <= version[0])) ? 1 : 0,
26
26
  'HAS_NANO_TIME' => ('ruby' == type && ('1' == version[0] && '9' == version[1]) || '2' <= version[0]) ? 1 : 0,
27
- 'HAS_RSTRUCT' => ('ruby' == type || 'ree' == type || 'tcs-ruby' == type) ? 1 : 0,
28
27
  'HAS_IVAR_HELPERS' => ('ruby' == type && !is_windows && (('1' == version[0] && '9' == version[1]) || '2' <= version[0])) ? 1 : 0,
29
28
  'HAS_EXCEPTION_MAGIC' => ('ruby' == type && ('1' == version[0] && '9' == version[1])) ? 0 : 1,
30
29
  'HAS_PROC_WITH_BLOCK' => ('ruby' == type && (('1' == version[0] && '9' == version[1]) || '2' <= version[0])) ? 1 : 0,
@@ -185,17 +185,39 @@ hat_value(ParseInfo pi, Val parent, const char *key, size_t klen, volatile VALUE
185
185
  int len = (int)RARRAY_LEN(value);
186
186
 
187
187
  if (2 == klen && 'u' == key[1]) {
188
- volatile VALUE sc;
188
+ VALUE sc;
189
189
 
190
190
  if (0 == len) {
191
191
  oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "Invalid struct data");
192
192
  return 1;
193
193
  }
194
- // If struct is not defined of new is not supported on the class then
195
- // let this fail and raise an exception.
194
+ // If struct is not defined then we let this fail and raise an exception.
196
195
  sc = rb_const_get(oj_struct_class, rb_to_id(*RARRAY_PTR(value)));
197
- parent->val = rb_funcall2(sc, oj_new_id, len - 1, RARRAY_PTR(value) + 1);
198
-
196
+ // Create a properly initialized struct instance without calling the initialize method.
197
+ parent->val = rb_obj_alloc(sc);
198
+ // If the JSON array has more entries than the struct class allows, we record an error.
199
+ #ifdef RSTRUCT_LEN
200
+ // MRI >= 1.9
201
+ if (len - 1 > RSTRUCT_LEN(parent->val)) {
202
+ oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "Invalid struct data");
203
+ } else {
204
+ MEMCPY(RSTRUCT_PTR(parent->val), RARRAY_PTR(value) + 1, VALUE, len - 1);
205
+ }
206
+ #else
207
+ {
208
+ // MRI < 1.9 or Rubinius
209
+ int slen = FIX2INT(rb_funcall2(parent->val, oj_length_id, 0, 0));
210
+ int i;
211
+
212
+ if (len - 1 > slen) {
213
+ oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "Invalid struct data");
214
+ } else {
215
+ for (i = 0; i < slen; i++) {
216
+ rb_struct_aset(parent->val, INT2FIX(i), RARRAY_PTR(value)[i + 1]);
217
+ }
218
+ }
219
+ }
220
+ #endif
199
221
  return 1;
200
222
  } else if (3 <= klen && '#' == key[1]) {
201
223
  volatile VALUE *a;
@@ -64,6 +64,7 @@ ID oj_hash_start_id;
64
64
  ID oj_iconv_id;
65
65
  ID oj_instance_variables_id;
66
66
  ID oj_json_create_id;
67
+ ID oj_length_id;
67
68
  ID oj_new_id;
68
69
  ID oj_read_id;
69
70
  ID oj_string_id;
@@ -84,12 +85,15 @@ VALUE oj_cstack_class;
84
85
  VALUE oj_date_class;
85
86
  VALUE oj_datetime_class;
86
87
  VALUE oj_parse_error_class;
88
+ VALUE oj_stream_writer_class;
89
+ VALUE oj_string_writer_class;
87
90
  VALUE oj_stringio_class;
88
91
  VALUE oj_struct_class;
89
92
  VALUE oj_time_class;
90
93
 
91
94
  VALUE oj_slash_string;
92
95
 
96
+ static VALUE allow_gc_sym;
93
97
  static VALUE ascii_only_sym;
94
98
  static VALUE ascii_sym;
95
99
  static VALUE auto_define_sym;
@@ -153,6 +157,7 @@ struct _Options oj_default_options = {
153
157
  json_class, // create_id
154
158
  10, // create_id_len
155
159
  9, // sec_prec
160
+ Yes, // allow_gc
156
161
  0, // dump_opts
157
162
  };
158
163
 
@@ -173,6 +178,7 @@ static VALUE define_mimic_json(int argc, VALUE *argv, VALUE self);
173
178
  * - bigdecimal_load: [:bigdecimal|:float|:auto] load decimals as BigDecimal instead of as a Float. :auto pick the most precise for the number of digits.
174
179
  * - create_id: [String|nil] create id for json compatible object encoding, default is 'json_create'
175
180
  * - second_precision: [Fixnum|nil] number of digits after the decimal when dumping the seconds portion of time
181
+ * - allow_gc: [true|false|nil] allow or prohibit GC during parsing, default is true (allow)
176
182
  * @return [Hash] all current option settings.
177
183
  */
178
184
  static VALUE
@@ -186,6 +192,7 @@ get_def_opts(VALUE self) {
186
192
  rb_hash_aset(opts, auto_define_sym, (Yes == oj_default_options.auto_define) ? Qtrue : ((No == oj_default_options.auto_define) ? Qfalse : Qnil));
187
193
  rb_hash_aset(opts, symbol_keys_sym, (Yes == oj_default_options.sym_key) ? Qtrue : ((No == oj_default_options.sym_key) ? Qfalse : Qnil));
188
194
  rb_hash_aset(opts, bigdecimal_as_decimal_sym, (Yes == oj_default_options.bigdec_as_num) ? Qtrue : ((No == oj_default_options.bigdec_as_num) ? Qfalse : Qnil));
195
+ rb_hash_aset(opts, allow_gc_sym, (Yes == oj_default_options.allow_gc) ? Qtrue : ((No == oj_default_options.allow_gc) ? Qfalse : Qnil));
189
196
  switch (oj_default_options.mode) {
190
197
  case StrictMode: rb_hash_aset(opts, mode_sym, strict_sym); break;
191
198
  case CompatMode: rb_hash_aset(opts, mode_sym, compat_sym); break;
@@ -244,6 +251,7 @@ get_def_opts(VALUE self) {
244
251
  * :ruby Time.to_s formatted String
245
252
  * @param [String|nil] :create_id create id for json compatible object encoding
246
253
  * @param [Fixnum|nil] :second_precision number of digits after the decimal when dumping the seconds portion of time
254
+ * @param [true|false|nil] :allow_gc allow or prohibit GC during parsing, default is true (allow)
247
255
  * @return [nil]
248
256
  */
249
257
  static VALUE
@@ -254,6 +262,7 @@ set_def_opts(VALUE self, VALUE opts) {
254
262
  { symbol_keys_sym, &oj_default_options.sym_key },
255
263
  { class_cache_sym, &oj_default_options.class_cache },
256
264
  { bigdecimal_as_decimal_sym, &oj_default_options.bigdec_as_num },
265
+ { allow_gc_sym, &oj_default_options.allow_gc },
257
266
  { Qnil, 0 }
258
267
  };
259
268
  YesNoOpt o;
@@ -383,6 +392,7 @@ oj_parse_options(VALUE ropts, Options copts) {
383
392
  { symbol_keys_sym, &copts->sym_key },
384
393
  { class_cache_sym, &copts->class_cache },
385
394
  { bigdecimal_as_decimal_sym, &copts->bigdec_as_num },
395
+ { allow_gc_sym, &copts->allow_gc },
386
396
  { Qnil, 0 }
387
397
  };
388
398
  YesNoOpt o;
@@ -817,6 +827,179 @@ to_stream(int argc, VALUE *argv, VALUE self) {
817
827
  return Qnil;
818
828
  }
819
829
 
830
+ static void
831
+ str_writer_free(void *ptr) {
832
+ StrWriter sw;
833
+
834
+ if (0 == ptr) {
835
+ return;
836
+ }
837
+ sw = (StrWriter)ptr;
838
+ xfree(sw->out.buf);
839
+ xfree(sw->types);
840
+ xfree(ptr);
841
+ }
842
+
843
+ /* Document-class: Oj::StringWriter
844
+ *
845
+ * Supports building a JSON document one element at a time. Build the document
846
+ * by pushing values into the document. Pushing an array or an object will
847
+ * create that element in the JSON document and subsequent pushes will add the
848
+ * elements to that array or object until a pop() is called. When complete
849
+ * calling to_s() will return the JSON document. Note tha calling to_s() before
850
+ * construction is complete will return the document in it's current state.
851
+ */
852
+
853
+ /* call-seq: new(options)
854
+ *
855
+ * Creates a new StringWriter.
856
+ * @param [Hash] options formating options
857
+ */
858
+ static VALUE
859
+ str_writer_new(int argc, VALUE *argv, VALUE self) {
860
+ StrWriter sw = ALLOC(struct _StrWriter);
861
+
862
+ sw->opts = oj_default_options;
863
+ sw->depth = 0;
864
+ sw->types = ALLOC_N(char, 256);
865
+ sw->types_end = sw->types + 256;
866
+ *sw->types = '\0';
867
+ sw->out.buf = ALLOC_N(char, 4096);
868
+ sw->out.end = sw->out.buf + 4086;
869
+ sw->out.allocated = 1;
870
+ sw->out.cur = sw->out.buf;
871
+ *sw->out.cur = '\0';
872
+ sw->out.circ_cnt = 0;
873
+ sw->out.hash_cnt = 0;
874
+ if (1 == argc) {
875
+ oj_parse_options(argv[0], &sw->opts);
876
+ }
877
+ sw->out.opts = &sw->opts;
878
+ sw->out.indent = sw->opts.indent;
879
+
880
+ return Data_Wrap_Struct(oj_string_writer_class, 0, str_writer_free, sw);
881
+ }
882
+
883
+ /* call-seq: push_object(key=nil)
884
+ *
885
+ * Pushes an object onto the JSON document. Future pushes will be to this object
886
+ * until a pop() is called.
887
+ * @param [String] key the key if adding to an object in the JSON document
888
+ */
889
+ static VALUE
890
+ str_writer_push_object(int argc, VALUE *argv, VALUE self) {
891
+ switch (argc) {
892
+ case 0:
893
+ oj_str_writer_push_object((StrWriter)DATA_PTR(self), 0);
894
+ break;
895
+ case 1:
896
+ rb_check_type(argv[0], T_STRING);
897
+ oj_str_writer_push_object((StrWriter)DATA_PTR(self), StringValuePtr(argv[0]));
898
+ break;
899
+ default:
900
+ rb_raise(rb_eArgError, "Wrong number of argument to 'push_object'.");
901
+ break;
902
+ }
903
+ return Qnil;
904
+ }
905
+
906
+ /* call-seq: push_array(key=nil)
907
+ *
908
+ * Pushes an array onto the JSON document. Future pushes will be to this object
909
+ * until a pop() is called.
910
+ * @param [String] key the key if adding to an object in the JSON document
911
+ */
912
+ static VALUE
913
+ str_writer_push_array(int argc, VALUE *argv, VALUE self) {
914
+ switch (argc) {
915
+ case 0:
916
+ oj_str_writer_push_array((StrWriter)DATA_PTR(self), 0);
917
+ break;
918
+ case 1:
919
+ rb_check_type(argv[0], T_STRING);
920
+ oj_str_writer_push_array((StrWriter)DATA_PTR(self), StringValuePtr(argv[0]));
921
+ break;
922
+ default:
923
+ rb_raise(rb_eArgError, "Wrong number of argument to 'push_object'.");
924
+ break;
925
+ }
926
+ return Qnil;
927
+ }
928
+
929
+ /* call-seq: push_value(value, key=nil)
930
+ *
931
+ * Pushes a value onto the JSON document.
932
+ * @param [Object] value value to add to the JSON document
933
+ * @param [String] key the key if adding to an object in the JSON document
934
+ */
935
+ static VALUE
936
+ str_writer_push_value(int argc, VALUE *argv, VALUE self) {
937
+ switch (argc) {
938
+ case 1:
939
+ oj_str_writer_push_value((StrWriter)DATA_PTR(self), *argv, 0);
940
+ break;
941
+ case 2:
942
+ rb_check_type(argv[1], T_STRING);
943
+ oj_str_writer_push_value((StrWriter)DATA_PTR(self), *argv, StringValuePtr(argv[1]));
944
+ break;
945
+ default:
946
+ rb_raise(rb_eArgError, "Wrong number of argument to 'push_value'.");
947
+ break;
948
+ }
949
+ return Qnil;
950
+ }
951
+
952
+ /* call-seq: pop()
953
+ *
954
+ * Pops up a level in the JSON document closing the array or object that is
955
+ * currently open.
956
+ */
957
+ static VALUE
958
+ str_writer_pop(VALUE self) {
959
+ oj_str_writer_pop((StrWriter)DATA_PTR(self));
960
+ return Qnil;
961
+ }
962
+
963
+ /* call-seq: pop_all()
964
+ *
965
+ * Pops all level in the JSON document closing all the array or object that is
966
+ * currently open.
967
+ */
968
+ static VALUE
969
+ str_writer_pop_all(VALUE self) {
970
+ oj_str_writer_pop_all((StrWriter)DATA_PTR(self));
971
+
972
+ return Qnil;
973
+ }
974
+
975
+ /* call-seq: reset()
976
+ *
977
+ * Reset the writer back to the empty state.
978
+ */
979
+ static VALUE
980
+ str_writer_reset(VALUE self) {
981
+ StrWriter sw = (StrWriter)DATA_PTR(self);
982
+
983
+ sw->depth = 0;
984
+ *sw->types = '\0';
985
+ sw->out.cur = sw->out.buf;
986
+ *sw->out.cur = '\0';
987
+
988
+ return Qnil;
989
+ }
990
+
991
+ /* call-seq: to_s()
992
+ *
993
+ * Returns the JSON document string in what ever state the construction is at.
994
+ */
995
+ static VALUE
996
+ str_writer_to_s(VALUE self) {
997
+ StrWriter sw = (StrWriter)DATA_PTR(self);
998
+ VALUE rstr = rb_str_new(sw->out.buf, sw->out.cur - sw->out.buf);
999
+
1000
+ return oj_encode(rstr);
1001
+ }
1002
+
820
1003
  // Mimic JSON section
821
1004
 
822
1005
  static VALUE
@@ -1195,14 +1378,35 @@ iconv_rescue(VALUE x) {
1195
1378
  }
1196
1379
  #endif
1197
1380
 
1381
+ static VALUE
1382
+ protect_require(VALUE x) {
1383
+ rb_require("bigdecimal");
1384
+ return Qnil;
1385
+ }
1386
+
1198
1387
  void Init_oj() {
1388
+ int err = 0;
1389
+
1199
1390
  Oj = rb_define_module("Oj");
1200
1391
 
1201
1392
  oj_cstack_class = rb_define_class_under(Oj, "CStack", rb_cObject);
1202
1393
 
1394
+ oj_string_writer_class = rb_define_class_under(Oj, "StringWriter", rb_cObject);
1395
+ rb_define_module_function(oj_string_writer_class, "new", str_writer_new, -1);
1396
+ rb_define_method(oj_string_writer_class, "push_object", str_writer_push_object, -1);
1397
+ rb_define_method(oj_string_writer_class, "push_array", str_writer_push_array, -1);
1398
+ rb_define_method(oj_string_writer_class, "push_value", str_writer_push_value, -1);
1399
+ rb_define_method(oj_string_writer_class, "pop", str_writer_pop, 0);
1400
+ rb_define_method(oj_string_writer_class, "pop_all", str_writer_pop_all, 0);
1401
+ rb_define_method(oj_string_writer_class, "reset", str_writer_reset, 0);
1402
+ rb_define_method(oj_string_writer_class, "to_s", str_writer_to_s, 0);
1403
+
1404
+ //oj_stream_writer_class = rb_define_class_under(Oj, "StreamWriter", rb_cObject);
1405
+
1203
1406
  rb_require("time");
1204
1407
  rb_require("date");
1205
- rb_require("bigdecimal");
1408
+ // On Rubinius the require fails but can be done from a ruby file.
1409
+ rb_protect(protect_require, Qnil, &err);
1206
1410
  #if NEEDS_RATIONAL
1207
1411
  rb_require("rational");
1208
1412
  #endif
@@ -1248,6 +1452,7 @@ void Init_oj() {
1248
1452
  oj_iconv_id = rb_intern("iconv");
1249
1453
  oj_instance_variables_id = rb_intern("instance_variables");
1250
1454
  oj_json_create_id = rb_intern("json_create");
1455
+ oj_length_id = rb_intern("length");
1251
1456
  oj_new_id = rb_intern("new");
1252
1457
  oj_read_id = rb_intern("read");
1253
1458
  oj_string_id = rb_intern("string");
@@ -1271,6 +1476,7 @@ void Init_oj() {
1271
1476
  oj_struct_class = rb_const_get(rb_cObject, rb_intern("Struct"));
1272
1477
  oj_time_class = rb_const_get(rb_cObject, rb_intern("Time"));
1273
1478
 
1479
+ allow_gc_sym = ID2SYM(rb_intern("allow_gc")); rb_gc_register_address(&allow_gc_sym);
1274
1480
  ascii_only_sym = ID2SYM(rb_intern("ascii_only")); rb_gc_register_address(&ascii_only_sym);
1275
1481
  ascii_sym = ID2SYM(rb_intern("ascii")); rb_gc_register_address(&ascii_sym);
1276
1482
  auto_define_sym = ID2SYM(rb_intern("auto_define")); rb_gc_register_address(&auto_define_sym);
@@ -98,6 +98,13 @@ typedef enum {
98
98
  AutoDec = 'a'
99
99
  } BigLoad;
100
100
 
101
+ typedef enum {
102
+ ArrayNew = 'A',
103
+ ArrayType = 'a',
104
+ ObjectNew = 'O',
105
+ ObjectType = 'o',
106
+ } DumpType;
107
+
101
108
  typedef struct _DumpOpts {
102
109
  const char *indent;
103
110
  const char *before_sep;
@@ -125,6 +132,7 @@ typedef struct _Options {
125
132
  const char *create_id; // 0 or string
126
133
  size_t create_id_len; // length of create_id
127
134
  int sec_prec; // second precision when dumping time
135
+ char allow_gc; // allow GC during parse
128
136
  DumpOpts dump_opts;
129
137
  } *Options;
130
138
 
@@ -141,6 +149,14 @@ typedef struct _Out {
141
149
  int allocated;
142
150
  } *Out;
143
151
 
152
+ typedef struct _StrWriter {
153
+ struct _Out out;
154
+ struct _Options opts;
155
+ int depth;
156
+ char *types; // DumpType
157
+ char *types_end;
158
+ } *StrWriter;
159
+
144
160
  enum {
145
161
  STR_VAL = 0x00,
146
162
  COL_VAL = 0x01,
@@ -182,6 +198,12 @@ extern void oj_write_obj_to_stream(VALUE obj, VALUE stream, Options copts);
182
198
  extern void oj_dump_leaf_to_json(Leaf leaf, Options copts, Out out);
183
199
  extern void oj_write_leaf_to_file(Leaf leaf, const char *path, Options copts);
184
200
 
201
+ extern void oj_str_writer_push_object(StrWriter sw, const char *key);
202
+ extern void oj_str_writer_push_array(StrWriter sw, const char *key);
203
+ extern void oj_str_writer_push_value(StrWriter sw, VALUE val, const char *key);
204
+ extern void oj_str_writer_pop(StrWriter sw);
205
+ extern void oj_str_writer_pop_all(StrWriter sw);
206
+
185
207
  extern void oj_init_doc(void);
186
208
 
187
209
  extern VALUE Oj;
@@ -198,6 +220,8 @@ extern VALUE oj_cstack_class;
198
220
  extern VALUE oj_date_class;
199
221
  extern VALUE oj_datetime_class;
200
222
  extern VALUE oj_doc_class;
223
+ extern VALUE oj_stream_writer_class;
224
+ extern VALUE oj_string_writer_class;
201
225
  extern VALUE oj_stringio_class;
202
226
  extern VALUE oj_struct_class;
203
227
  extern VALUE oj_time_class;
@@ -217,6 +241,7 @@ extern ID oj_hash_start_id;
217
241
  extern ID oj_iconv_id;
218
242
  extern ID oj_instance_variables_id;
219
243
  extern ID oj_json_create_id;
244
+ extern ID oj_length_id;
220
245
  extern ID oj_new_id;
221
246
  extern ID oj_read_id;
222
247
  extern ID oj_string_id;
@@ -747,9 +747,7 @@ oj_pi_parse(int argc, VALUE *argv, ParseInfo pi, char *json) {
747
747
  if (oj_stringio_class == clas) {
748
748
  s = rb_funcall2(input, oj_string_id, 0, 0);
749
749
  pi->json = rb_string_value_cstr((VALUE*)&s);
750
- #ifndef JRUBY_RUBY
751
750
  #if !IS_WINDOWS
752
- // JRuby gets confused with what is the real fileno.
753
751
  } else if (rb_respond_to(input, oj_fileno_id) && Qnil != (s = rb_funcall(input, oj_fileno_id, 0))) {
754
752
  int fd = FIX2INT(s);
755
753
  ssize_t cnt;
@@ -769,7 +767,6 @@ oj_pi_parse(int argc, VALUE *argv, ParseInfo pi, char *json) {
769
767
  if (0xEF == (uint8_t)*pi->json && 0xBB == (uint8_t)pi->json[1] && 0xBF == (uint8_t)pi->json[2]) {
770
768
  pi->json += 3;
771
769
  }
772
- #endif
773
770
  #endif
774
771
  } else if (rb_respond_to(input, oj_read_id)) {
775
772
  s = rb_funcall2(input, oj_read_id, 0, 0);
@@ -783,6 +780,9 @@ oj_pi_parse(int argc, VALUE *argv, ParseInfo pi, char *json) {
783
780
  } else {
784
781
  pi->circ_array = 0;
785
782
  }
783
+ if (No == pi->options.allow_gc) {
784
+ rb_gc_disable();
785
+ }
786
786
  // GC can run at any time. When it runs any Object created by C will be
787
787
  // freed. We protect against this by wrapping the value stack in a ruby
788
788
  // data object and poviding a mark function for ruby objects on the
@@ -791,7 +791,9 @@ oj_pi_parse(int argc, VALUE *argv, ParseInfo pi, char *json) {
791
791
  rb_protect(protect_parse, (VALUE)pi, &line);
792
792
  result = stack_head_val(&pi->stack);
793
793
  DATA_PTR(wrapped_stack) = 0;
794
-
794
+ if (No == pi->options.allow_gc) {
795
+ rb_gc_enable();
796
+ }
795
797
  // proceed with cleanup
796
798
  if (0 != pi->circ_array) {
797
799
  oj_circ_array_free(pi->circ_array);
@@ -102,7 +102,7 @@ noop_array_append_value(ParseInfo pi, VALUE value) {
102
102
 
103
103
  static void
104
104
  add_value(ParseInfo pi, VALUE val) {
105
- rb_funcall((VALUE)pi->cbc, oj_add_value_id, 1, val);
105
+ rb_funcall((VALUE)pi->cbc, oj_add_value_id, 1, val);
106
106
  }
107
107
 
108
108
  static void
@@ -110,7 +110,7 @@ add_cstr(ParseInfo pi, const char *str, size_t len, const char *orig) {
110
110
  volatile VALUE rstr = rb_str_new(str, len);
111
111
 
112
112
  rstr = oj_encode(rstr);
113
- rb_funcall((VALUE)pi->cbc, oj_add_value_id, 1, rstr);
113
+ rb_funcall((VALUE)pi->cbc, oj_add_value_id, 1, rstr);
114
114
  }
115
115
 
116
116
  static void
@@ -1,5 +1,5 @@
1
1
 
2
2
  module Oj
3
3
  # Current version of the module.
4
- VERSION = '2.2.3'
4
+ VERSION = '2.3.0'
5
5
  end
@@ -20,6 +20,7 @@ require 'files'
20
20
 
21
21
  $circular = false
22
22
  $indent = 0
23
+ $allow_gc = true
23
24
 
24
25
  do_sample = false
25
26
  do_files = false
@@ -35,6 +36,7 @@ opts = OptionParser.new
35
36
  opts.on("-c", "circular options") { $circular = true }
36
37
 
37
38
  opts.on("-x", "use sample instead of files") { do_sample = true }
39
+ opts.on("-g", "no GC during parsing") { $allow_gc = false }
38
40
 
39
41
  opts.on("-s", "load and dump as sample Ruby object") { do_sample = true }
40
42
  opts.on("-f", "load and dump as files Ruby object") { do_files = true }
@@ -86,7 +88,7 @@ else
86
88
  end
87
89
  end
88
90
 
89
- Oj.default_options = { :mode => :object, :indent => $indent, :circular => $circular }
91
+ Oj.default_options = { :mode => :object, :indent => $indent, :circular => $circular, :allow_gc => $allow_gc }
90
92
  #puts "json: #{$json.size}"
91
93
  #puts "xml: #{$xml.size}"
92
94
  #puts "marshal: #{$mars.size}"
@@ -11,6 +11,7 @@ $: << File.join(File.dirname(__FILE__), "../lib")
11
11
  $: << File.join(File.dirname(__FILE__), "../ext")
12
12
 
13
13
  require 'test/unit'
14
+ require 'bigdecimal'
14
15
  require 'oj'
15
16
 
16
17
  $json1 = %{{
@@ -12,6 +12,7 @@ $: << File.join(File.dirname(__FILE__), "../lib")
12
12
  $: << File.join(File.dirname(__FILE__), "../ext")
13
13
 
14
14
  require 'test/unit'
15
+ require 'bigdecimal'
15
16
  require 'oj'
16
17
 
17
18
  class Goo
@@ -12,6 +12,7 @@ $: << File.join(File.dirname(__FILE__), "../ext")
12
12
 
13
13
  require 'test/unit'
14
14
  require 'stringio'
15
+ require 'bigdecimal'
15
16
  require 'oj'
16
17
 
17
18
  $ruby = RUBY_DESCRIPTION.split(' ')[0]
@@ -11,6 +11,7 @@ $: << File.join(File.dirname(__FILE__), "../lib")
11
11
  $: << File.join(File.dirname(__FILE__), "../ext")
12
12
 
13
13
  require 'test/unit'
14
+ require 'bigdecimal'
14
15
  require 'oj'
15
16
  require 'pp'
16
17
 
@@ -11,6 +11,7 @@ $: << File.join(File.dirname(__FILE__), "../lib")
11
11
  $: << File.join(File.dirname(__FILE__), "../ext")
12
12
 
13
13
  require 'test/unit'
14
+ require 'bigdecimal'
14
15
  require 'oj'
15
16
  require 'pp'
16
17
 
@@ -0,0 +1,148 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: UTF-8
3
+
4
+ # Ubuntu does not accept arguments to ruby when called using env. To get warnings to show up the -w options is
5
+ # required. That can be set in the RUBYOPT environment variable.
6
+ # export RUBYOPT=-w
7
+
8
+ $VERBOSE = true
9
+
10
+ $: << File.join(File.dirname(__FILE__), "../lib")
11
+ $: << File.join(File.dirname(__FILE__), "../ext")
12
+
13
+ require 'test/unit'
14
+ require 'stringio'
15
+ require 'date'
16
+ require 'bigdecimal'
17
+ require 'oj'
18
+
19
+ class OjWriter < ::Test::Unit::TestCase
20
+
21
+ def test_string_writer_empty_array
22
+ w = Oj::StringWriter.new(:indent => 0)
23
+ w.push_array()
24
+ w.pop()
25
+ assert_equal('[]', w.to_s)
26
+
27
+ end
28
+
29
+ def test_string_writer_nested_array
30
+ w = Oj::StringWriter.new(:indent => 0)
31
+ w.push_array()
32
+ w.push_array()
33
+ w.pop()
34
+ w.push_array()
35
+ w.push_array()
36
+ w.pop()
37
+ w.pop()
38
+ w.push_array()
39
+ w.pop()
40
+ w.pop()
41
+ assert_equal('[[],[[]],[]]', w.to_s)
42
+ end
43
+
44
+ def test_string_writer_empty_object
45
+ w = Oj::StringWriter.new(:indent => 0)
46
+ w.push_object()
47
+ w.pop()
48
+ assert_equal('{}', w.to_s)
49
+ end
50
+
51
+ def test_string_writer_nested_object
52
+ w = Oj::StringWriter.new(:indent => 0)
53
+ w.push_object()
54
+ w.push_object("a1")
55
+ w.pop()
56
+ w.push_object("a2")
57
+ w.push_object("b")
58
+ w.pop()
59
+ w.pop()
60
+ w.push_object("a3")
61
+ w.pop()
62
+ w.pop()
63
+ assert_equal('{"a1":{},"a2":{"b":{}},"a3":{}}', w.to_s)
64
+ end
65
+
66
+ def test_string_writer_value_array
67
+ w = Oj::StringWriter.new(:indent => 0)
68
+ w.push_array()
69
+ w.push_value(7)
70
+ w.push_value(7.3)
71
+ w.push_value(true)
72
+ w.push_value(nil)
73
+ w.push_value("a string")
74
+ w.push_value({'a' => 65})
75
+ w.push_value([1,2])
76
+ w.pop()
77
+ assert_equal('[7,7.3,true,null,"a string",{"a":65},[1,2]]', w.to_s)
78
+ end
79
+
80
+ def test_string_writer_pop_excess
81
+ w = Oj::StringWriter.new(:indent => 0)
82
+ begin
83
+ w.pop()
84
+ rescue Exception
85
+ assert(true)
86
+ return
87
+ end
88
+ assert(false, "*** expected an exception")
89
+ end
90
+
91
+ def test_string_writer_obj_no_key
92
+ w = Oj::StringWriter.new(:indent => 0)
93
+ w.push_object()
94
+ begin
95
+ w.push_value(59)
96
+ rescue Exception
97
+ assert(true)
98
+ return
99
+ end
100
+ assert(false, "*** expected an exception")
101
+ end
102
+
103
+ def test_string_writer_array_with_key
104
+ w = Oj::StringWriter.new(:indent => 0)
105
+ w.push_array()
106
+ begin
107
+ w.push_value(59, 'x')
108
+ rescue Exception
109
+ assert(true)
110
+ return
111
+ end
112
+ assert(false, "*** expected an exception")
113
+ end
114
+
115
+ def test_string_writer_deep
116
+ cnt = 1000
117
+ w = Oj::StringWriter.new(:indent => 0)
118
+ cnt.times do
119
+ w.push_array()
120
+ end
121
+ cnt.times do
122
+ w.pop()
123
+ end
124
+ # if no exception then passed
125
+ assert(true)
126
+ end
127
+
128
+ def test_string_writer_pop_all
129
+ w = Oj::StringWriter.new(:indent => 0)
130
+ w.push_object()
131
+ w.push_object("a1")
132
+ w.pop()
133
+ w.push_array("a2")
134
+ w.push_value(3)
135
+ w.push_array()
136
+ w.pop_all()
137
+ assert_equal('{"a1":{},"a2":[3,[]]}', w.to_s)
138
+ end
139
+
140
+ def test_string_writer_reset
141
+ w = Oj::StringWriter.new(:indent => 0)
142
+ w.push_array()
143
+ w.pop()
144
+ w.reset()
145
+ assert_equal('', w.to_s)
146
+ end
147
+
148
+ end # OjWriter
@@ -134,6 +134,7 @@ class Juice < ::Test::Unit::TestCase
134
134
  :time_format=>:unix,
135
135
  :bigdecimal_as_decimal=>true,
136
136
  :bigdecimal_load=>:auto,
137
+ :allow_gc=>true,
137
138
  :create_id=>'json_class'}, opts)
138
139
  end
139
140
 
@@ -150,6 +151,7 @@ class Juice < ::Test::Unit::TestCase
150
151
  :time_format=>:unix,
151
152
  :bigdecimal_as_decimal=>true,
152
153
  :bigdecimal_load=>:auto,
154
+ :allow_gc=>true,
153
155
  :create_id=>'json_class'}
154
156
  o2 = {
155
157
  :indent=>4,
@@ -163,6 +165,7 @@ class Juice < ::Test::Unit::TestCase
163
165
  :time_format=>:ruby,
164
166
  :bigdecimal_as_decimal=>false,
165
167
  :bigdecimal_load=>:bigdecimal,
168
+ :allow_gc=>false,
166
169
  :create_id=>nil}
167
170
  o3 = { :indent => 4 }
168
171
  Oj.default_options = o2
@@ -765,8 +768,7 @@ class Juice < ::Test::Unit::TestCase
765
768
  Oj.default_options = { :mode => :object }
766
769
  json = Oj.dump(1..7, :mode => :object, :indent => 0)
767
770
  if 'rubinius' == $ruby
768
- assert(%{{"^o":"Range","excl":false,"begin":1,"end":7}} == json ||
769
- %{{"^o":"Range","begin":1,"end":7,"excl":false}} == json)
771
+ assert(%{{"^O":"Range","begin":1,"end":7,"exclude_end?":false}} == json)
770
772
  elsif 'jruby' == $ruby
771
773
  assert(%{{"^O":"Range","begin":1,"end":7,"exclude_end?":false}} == json)
772
774
  else
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: oj
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.2.3
4
+ version: 2.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Peter Ohler
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-11-19 00:00:00.000000000 Z
11
+ date: 2013-12-01 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: 'The fastest JSON parser and object serializer. '
14
14
  email: peter@ohler.com
@@ -93,6 +93,7 @@ files:
93
93
  - test/test_saj.rb
94
94
  - test/test_scp.rb
95
95
  - test/test_strict.rb
96
+ - test/test_writer.rb
96
97
  - test/tests.rb
97
98
  - test/x.rb
98
99
  - LICENSE