oj 3.13.3 → 3.13.7

Sign up to get free protection for your applications and to get access to all the features.
data/ext/oj/oj.c CHANGED
@@ -107,6 +107,7 @@ static VALUE bigdecimal_load_sym;
107
107
  static VALUE bigdecimal_sym;
108
108
  static VALUE cache_keys_sym;
109
109
  static VALUE cache_str_sym;
110
+ static VALUE cache_string_sym;
110
111
  static VALUE circular_sym;
111
112
  static VALUE class_cache_sym;
112
113
  static VALUE compat_bigdecimal_sym;
@@ -287,7 +288,7 @@ struct _options oj_default_options = {
287
288
  * - *:ignore* [_nil_|_Array_] either nil or an Array of classes to ignore when dumping
288
289
  * - *:ignore_under* [_Boolean_] if true then attributes that start with _ are ignored when dumping in
289
290
  *object or custom mode.
290
- * - *:cache_keys* [_Boolean_] if true then hash keys are cached
291
+ * - *:cache_keys* [_Boolean_] if true then hash keys are cached if less than 35 bytes.
291
292
  * - *:cache_str* [_Fixnum_] maximum string value length to cache (strings less than this are cached)
292
293
  * - *:integer_range* [_Range_] Dump integers outside range as strings.
293
294
  * - *:trace* [_true,_|_false_] Trace all load and dump calls, default is false (trace is off)
@@ -692,7 +693,7 @@ static int parse_options_cb(VALUE k, VALUE v, VALUE opts)
692
693
  sprintf(copts->float_fmt, "%%0.%dg", n);
693
694
  copts->float_prec = n;
694
695
  }
695
- } else if (cache_str_sym == k) {
696
+ } else if (cache_str_sym == k || cache_string_sym == k) {
696
697
  int n;
697
698
 
698
699
  #ifdef RUBY_INTEGER_UNIFICATION
@@ -1920,6 +1921,8 @@ void Init_oj() {
1920
1921
  rb_gc_register_address(&cache_keys_sym);
1921
1922
  cache_str_sym = ID2SYM(rb_intern("cache_str"));
1922
1923
  rb_gc_register_address(&cache_str_sym);
1924
+ cache_string_sym = ID2SYM(rb_intern("cache_string"));
1925
+ rb_gc_register_address(&cache_string_sym);
1923
1926
  circular_sym = ID2SYM(rb_intern("circular"));
1924
1927
  rb_gc_register_address(&circular_sym);
1925
1928
  class_cache_sym = ID2SYM(rb_intern("class_cache"));
data/ext/oj/parser.c CHANGED
@@ -610,6 +610,9 @@ static void parse(ojParser p, const byte *json) {
610
610
  printf("*** parse - mode: %c %s\n", p->map[256], (const char *)json);
611
611
  #endif
612
612
  for (; '\0' != *b; b++) {
613
+ #if DEBUG
614
+ printf("*** parse - mode: %c %02x %s => %c\n", p->map[256], *b, b, p->map[*b]);
615
+ #endif
613
616
  switch (p->map[*b]) {
614
617
  case SKIP_NEWLINE:
615
618
  p->line++;
@@ -887,13 +890,17 @@ static void parse(ojParser p, const byte *json) {
887
890
  buf_append_string(&p->buf, (const char *)start, b - start);
888
891
  }
889
892
  if ('"' == *b) {
893
+ p->funcs[p->stack[p->depth]].add_str(p);
890
894
  p->map = p->next_map;
891
895
  break;
892
896
  }
893
897
  b--;
894
898
  break;
895
899
  case STR_SLASH: p->map = esc_map; break;
896
- case STR_QUOTE: p->map = p->next_map; break;
900
+ case STR_QUOTE:
901
+ p->funcs[p->stack[p->depth]].add_str(p);
902
+ p->map = p->next_map;
903
+ break;
897
904
  case ESC_U:
898
905
  p->map = u_map;
899
906
  p->ri = 0;
@@ -1135,13 +1142,42 @@ extern void oj_set_parser_saj(ojParser p);
1135
1142
  extern void oj_set_parser_usual(ojParser p);
1136
1143
  extern void oj_set_parser_debug(ojParser p);
1137
1144
 
1145
+ static int opt_cb(VALUE rkey, VALUE value, VALUE ptr) {
1146
+ ojParser p = (ojParser)ptr;
1147
+ const char *key = NULL;
1148
+ char set_key[64];
1149
+ long klen;
1150
+
1151
+ switch (rb_type(rkey)) {
1152
+ case RUBY_T_SYMBOL:
1153
+ rkey = rb_sym2str(rkey);
1154
+ // fall through
1155
+ case RUBY_T_STRING:
1156
+ key = rb_string_value_ptr(&rkey);
1157
+ klen = RSTRING_LEN(rkey);
1158
+ break;
1159
+ default: rb_raise(rb_eArgError, "option keys must be a symbol or string");
1160
+ }
1161
+ if ((long)sizeof(set_key) - 1 <= klen) {
1162
+ return ST_CONTINUE;
1163
+ }
1164
+ memcpy(set_key, key, klen);
1165
+ set_key[klen] = '=';
1166
+ set_key[klen + 1] = '\0';
1167
+ p->option(p, set_key, value);
1168
+
1169
+ return ST_CONTINUE;
1170
+ }
1171
+
1138
1172
  /* Document-method: new
1139
1173
  * call-seq: new(mode=nil)
1140
1174
  *
1141
1175
  * Creates a new Parser with the specified mode. If no mode is provided
1142
- * validation is assumed.
1176
+ * validation is assumed. Optional arguments can be provided that match the
1177
+ * mode. For example with the :usual mode the call might look like
1178
+ * Oj::Parser.new(:usual, cache_keys: true).
1143
1179
  */
1144
- static VALUE parser_new(VALUE self, VALUE mode) {
1180
+ static VALUE parser_new(int argc, VALUE *argv, VALUE self) {
1145
1181
  ojParser p = ALLOC(struct _ojParser);
1146
1182
 
1147
1183
  #if HAVE_RB_EXT_RACTOR_SAFE
@@ -1151,33 +1187,45 @@ static VALUE parser_new(VALUE self, VALUE mode) {
1151
1187
  memset(p, 0, sizeof(struct _ojParser));
1152
1188
  buf_init(&p->key);
1153
1189
  buf_init(&p->buf);
1154
-
1155
1190
  p->map = value_map;
1156
- if (Qnil == mode) {
1157
- oj_set_parser_validator(p);
1191
+
1192
+ if (argc < 1) {
1193
+ oj_set_parser_validator(p);
1158
1194
  } else {
1159
- const char *ms = NULL;
1160
-
1161
- switch (rb_type(mode)) {
1162
- case RUBY_T_SYMBOL:
1163
- mode = rb_sym2str(mode);
1164
- // fall through
1165
- case RUBY_T_STRING: ms = RSTRING_PTR(mode); break;
1166
- default: rb_raise(rb_eArgError, "mode must be :validate, :usual, :saj, or :object");
1167
- }
1168
- if (0 == strcmp("usual", ms) || 0 == strcmp("standard", ms) || 0 == strcmp("strict", ms) ||
1169
- 0 == strcmp("compat", ms)) {
1170
- oj_set_parser_usual(p);
1171
- } else if (0 == strcmp("object", ms)) {
1172
- // TBD
1173
- } else if (0 == strcmp("saj", ms)) {
1174
- oj_set_parser_saj(p);
1175
- } else if (0 == strcmp("validate", ms)) {
1195
+ VALUE mode = argv[0];
1196
+
1197
+ if (Qnil == mode) {
1176
1198
  oj_set_parser_validator(p);
1177
- } else if (0 == strcmp("debug", ms)) {
1178
- oj_set_parser_debug(p);
1179
1199
  } else {
1180
- rb_raise(rb_eArgError, "mode must be :validate, :usual, :saj, or :object");
1200
+ const char *ms = NULL;
1201
+
1202
+ switch (rb_type(mode)) {
1203
+ case RUBY_T_SYMBOL:
1204
+ mode = rb_sym2str(mode);
1205
+ // fall through
1206
+ case RUBY_T_STRING: ms = RSTRING_PTR(mode); break;
1207
+ default: rb_raise(rb_eArgError, "mode must be :validate, :usual, :saj, or :object");
1208
+ }
1209
+ if (0 == strcmp("usual", ms) || 0 == strcmp("standard", ms) || 0 == strcmp("strict", ms) ||
1210
+ 0 == strcmp("compat", ms)) {
1211
+ oj_set_parser_usual(p);
1212
+ } else if (0 == strcmp("object", ms)) {
1213
+ // TBD
1214
+ } else if (0 == strcmp("saj", ms)) {
1215
+ oj_set_parser_saj(p);
1216
+ } else if (0 == strcmp("validate", ms)) {
1217
+ oj_set_parser_validator(p);
1218
+ } else if (0 == strcmp("debug", ms)) {
1219
+ oj_set_parser_debug(p);
1220
+ } else {
1221
+ rb_raise(rb_eArgError, "mode must be :validate, :usual, :saj, or :object");
1222
+ }
1223
+ }
1224
+ if (1 < argc) {
1225
+ VALUE ropts = argv[1];
1226
+
1227
+ Check_Type(ropts, T_HASH);
1228
+ rb_hash_foreach(ropts, opt_cb, (VALUE)p);
1181
1229
  }
1182
1230
  }
1183
1231
  return Data_Wrap_Struct(parser_class, parser_mark, parser_free, p);
@@ -1204,7 +1252,7 @@ static VALUE parser_new(VALUE self, VALUE mode) {
1204
1252
  * - *:usual*
1205
1253
  * - _cache_keys=_ sets the value of the _cache_keys_ flag.
1206
1254
  * - _cache_keys_ returns the value of the _cache_keys_ flag.
1207
- * - _cache_strings=_ sets the value of the _cache_strings_ to an positive integer less than 35. Strings shorter than
1255
+ * - _cache_strings=_ sets the value of the _cache_strings_ to a positive integer less than 35. Strings shorter than
1208
1256
  * that length are cached.
1209
1257
  * - _cache_strings_ returns the value of the _cache_strings_ integer value.
1210
1258
  * - _cache_expunge=_ sets the value of the _cache_expunge_ where 0 never expunges, 1 expunges slowly, 2 expunges
@@ -1469,7 +1517,7 @@ static VALUE parser_validate(VALUE self) {
1469
1517
  */
1470
1518
  void oj_parser_init() {
1471
1519
  parser_class = rb_define_class_under(Oj, "Parser", rb_cObject);
1472
- rb_define_module_function(parser_class, "new", parser_new, 1);
1520
+ rb_define_module_function(parser_class, "new", parser_new, -1);
1473
1521
  rb_define_method(parser_class, "parse", parser_parse, 1);
1474
1522
  rb_define_method(parser_class, "load", parser_load, 1);
1475
1523
  rb_define_method(parser_class, "file", parser_file, 1);
data/ext/oj/strict.c CHANGED
@@ -16,7 +16,7 @@
16
16
  VALUE oj_cstr_to_value(const char *str, size_t len, size_t cache_str) {
17
17
  volatile VALUE rstr = Qnil;
18
18
 
19
- if (len <= cache_str) {
19
+ if (len < cache_str) {
20
20
  rstr = oj_str_intern(str, len);
21
21
  } else {
22
22
  rstr = rb_str_new(str, len);
@@ -37,7 +37,7 @@ VALUE oj_calc_hash_key(ParseInfo pi, Val parent) {
37
37
  } else {
38
38
  rkey = rb_str_new(parent->key, parent->klen);
39
39
  rkey = oj_encode(rkey);
40
- OBJ_FREEZE(rkey);
40
+ OBJ_FREEZE(rkey); // frozen when used as a Hash key anyway
41
41
  }
42
42
  return rkey;
43
43
  }
data/ext/oj/usual.c CHANGED
@@ -39,7 +39,7 @@ typedef struct _col {
39
39
  typedef union _key {
40
40
  struct {
41
41
  int16_t len;
42
- char buf[22];
42
+ char buf[30];
43
43
  };
44
44
  struct {
45
45
  int16_t xlen; // should be the same as len
@@ -209,21 +209,21 @@ static void push(ojParser p, VALUE v) {
209
209
  static VALUE cache_key(ojParser p, Key kp) {
210
210
  Delegate d = (Delegate)p->ctx;
211
211
 
212
- if ((size_t)kp->len < sizeof(kp->buf) - 1) {
212
+ if ((size_t)kp->len < sizeof(kp->buf)) {
213
213
  return cache_intern(d->key_cache, kp->buf, kp->len);
214
214
  }
215
215
  return cache_intern(d->key_cache, kp->key, kp->len);
216
216
  }
217
217
 
218
218
  static VALUE str_key(ojParser p, Key kp) {
219
- if ((size_t)kp->len < sizeof(kp->buf) - 1) {
219
+ if ((size_t)kp->len < sizeof(kp->buf)) {
220
220
  return rb_str_freeze(rb_utf8_str_new(kp->buf, kp->len));
221
221
  }
222
222
  return rb_str_freeze(rb_utf8_str_new(kp->key, kp->len));
223
223
  }
224
224
 
225
225
  static VALUE sym_key(ojParser p, Key kp) {
226
- if ((size_t)kp->len < sizeof(kp->buf) - 1) {
226
+ if ((size_t)kp->len < sizeof(kp->buf)) {
227
227
  return rb_str_freeze(rb_str_intern(rb_utf8_str_new(kp->buf, kp->len)));
228
228
  }
229
229
  return rb_str_freeze(rb_str_intern(rb_utf8_str_new(kp->key, kp->len)));
@@ -232,7 +232,7 @@ static VALUE sym_key(ojParser p, Key kp) {
232
232
  static ID get_attr_id(ojParser p, Key kp) {
233
233
  Delegate d = (Delegate)p->ctx;
234
234
 
235
- if ((size_t)kp->len < sizeof(kp->buf) - 1) {
235
+ if ((size_t)kp->len < sizeof(kp->buf)) {
236
236
  return (ID)cache_intern(d->attr_cache, kp->buf, kp->len);
237
237
  }
238
238
  return (ID)cache_intern(d->attr_cache, kp->key, kp->len);
@@ -253,7 +253,7 @@ static void push_key(ojParser p) {
253
253
  d->kend = d->khead + cap;
254
254
  }
255
255
  d->ktail->len = klen;
256
- if (klen <= sizeof(d->ktail->buf) + 1) {
256
+ if (klen < sizeof(d->ktail->buf)) {
257
257
  memcpy(d->ktail->buf, key, klen);
258
258
  d->ktail->buf[klen] = '\0';
259
259
  } else {
@@ -336,7 +336,7 @@ static void close_object(ojParser p) {
336
336
  #if HAVE_RB_HASH_BULK_INSERT
337
337
  for (vp = head; kp < d->ktail; kp++, vp += 2) {
338
338
  *vp = d->get_key(p, kp);
339
- if (sizeof(kp->buf) - 1 < (size_t)kp->len) {
339
+ if (sizeof(kp->buf) <= (size_t)kp->len) {
340
340
  xfree(kp->key);
341
341
  }
342
342
  }
@@ -344,7 +344,7 @@ static void close_object(ojParser p) {
344
344
  #else
345
345
  for (vp = head; kp < d->ktail; kp++, vp += 2) {
346
346
  rb_hash_aset(obj, d->get_key(p, kp), *(vp + 1));
347
- if (sizeof(kp->buf) - 1 < (size_t)kp->len) {
347
+ if (sizeof(kp->buf) <= (size_t)kp->len) {
348
348
  xfree(kp->key);
349
349
  }
350
350
  }
@@ -368,7 +368,7 @@ static void close_object_class(ojParser p) {
368
368
 
369
369
  for (vp = head; kp < d->ktail; kp++, vp += 2) {
370
370
  rb_funcall(obj, hset_id, 2, d->get_key(p, kp), *(vp + 1));
371
- if (sizeof(kp->buf) - 1 < (size_t)kp->len) {
371
+ if (sizeof(kp->buf) <= (size_t)kp->len) {
372
372
  xfree(kp->key);
373
373
  }
374
374
  }
@@ -396,7 +396,7 @@ static void close_object_create(ojParser p) {
396
396
  #if HAVE_RB_HASH_BULK_INSERT
397
397
  for (vp = head; kp < d->ktail; kp++, vp += 2) {
398
398
  *vp = d->get_key(p, kp);
399
- if (sizeof(kp->buf) - 1 < (size_t)kp->len) {
399
+ if (sizeof(kp->buf) <= (size_t)kp->len) {
400
400
  xfree(kp->key);
401
401
  }
402
402
  }
@@ -404,7 +404,7 @@ static void close_object_create(ojParser p) {
404
404
  #else
405
405
  for (vp = head; kp < d->ktail; kp++, vp += 2) {
406
406
  rb_hash_aset(obj, d->get_key(p, kp), *(vp + 1));
407
- if (sizeof(kp->buf) - 1 < (size_t)kp->len) {
407
+ if (sizeof(kp->buf) <= (size_t)kp->len) {
408
408
  xfree(kp->key);
409
409
  }
410
410
  }
@@ -413,7 +413,7 @@ static void close_object_create(ojParser p) {
413
413
  obj = rb_class_new_instance(0, NULL, d->hash_class);
414
414
  for (vp = head; kp < d->ktail; kp++, vp += 2) {
415
415
  rb_funcall(obj, hset_id, 2, d->get_key(p, kp), *(vp + 1));
416
- if (sizeof(kp->buf) - 1 < (size_t)kp->len) {
416
+ if (sizeof(kp->buf) <= (size_t)kp->len) {
417
417
  xfree(kp->key);
418
418
  }
419
419
  }
@@ -428,7 +428,7 @@ static void close_object_create(ojParser p) {
428
428
  #if HAVE_RB_HASH_BULK_INSERT
429
429
  for (vp = head; kp < d->ktail; kp++, vp += 2) {
430
430
  *vp = d->get_key(p, kp);
431
- if (sizeof(kp->buf) - 1 < (size_t)kp->len) {
431
+ if (sizeof(kp->buf) <= (size_t)kp->len) {
432
432
  xfree(kp->key);
433
433
  }
434
434
  }
@@ -436,7 +436,7 @@ static void close_object_create(ojParser p) {
436
436
  #else
437
437
  for (vp = head; kp < d->ktail; kp++, vp += 2) {
438
438
  rb_hash_aset(arg, d->get_key(p, kp), *(vp + 1));
439
- if (sizeof(kp->buf) - 1 < (size_t)kp->len) {
439
+ if (sizeof(kp->buf) <= (size_t)kp->len) {
440
440
  xfree(kp->key);
441
441
  }
442
442
  }
@@ -446,7 +446,7 @@ static void close_object_create(ojParser p) {
446
446
  obj = rb_class_new_instance(0, NULL, clas);
447
447
  for (vp = head; kp < d->ktail; kp++, vp += 2) {
448
448
  rb_ivar_set(obj, get_attr_id(p, kp), *(vp + 1));
449
- if (sizeof(kp->buf) - 1 < (size_t)kp->len) {
449
+ if (sizeof(kp->buf) <= (size_t)kp->len) {
450
450
  xfree(kp->key);
451
451
  }
452
452
  }
data/lib/oj/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
 
2
2
  module Oj
3
3
  # Current version of the module.
4
- VERSION = '3.13.3'
4
+ VERSION = '3.13.7'
5
5
  end
data/pages/Options.md CHANGED
@@ -75,12 +75,22 @@ parse option to match the JSON gem. In that case either `Float`,
75
75
  If true Hash keys are cached or interned. There are trade-offs with
76
76
  caching keys. Large caches will use more memory and in extreme cases
77
77
  (like over a million) the cache may be slower than not using
78
- it. Repeated parsing of similar JSON docs is where cache_keys shines.
78
+ it. Repeated parsing of similar JSON docs is where cache_keys shines
79
+ especially with symbol keys.
80
+
81
+ There is a maximum length for cached keys. Any key longer than 34
82
+ bytes is not cached. Everything still works but the key is not cached.
79
83
 
80
84
  ### :cache_strings [Int]
81
85
 
82
86
  Shorter strings can be cached for better performance. A limit,
83
- cache_strings, defines the upper limit on what strings are cached.
87
+ cache_strings, defines the upper limit on what strings are cached. As
88
+ with cached keys only strings less than 35 bytes are cached even if
89
+ the limit is set higher. Setting the limit to zero effectively
90
+ disables the caching of string values.
91
+
92
+ Note that caching for strings is for string values and not Hash keys
93
+ or Object attributes.
84
94
 
85
95
  ### :circular [Boolean]
86
96
 
data/test/bar.rb CHANGED
@@ -1,35 +1,16 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- $: << File.dirname(__FILE__)
4
- $oj_dir = File.dirname(File.expand_path(File.dirname(__FILE__)))
5
- %w(lib ext).each do |dir|
6
- $: << File.join($oj_dir, dir)
7
- end
8
-
9
- require 'active_support'
10
- require "active_support/json"
11
-
12
- $s = "\u2014 & \n \u{1F618}"
13
-
14
- =begin
15
- def check(label)
16
- puts "\n--- #{label} --------------------"
17
-
18
- ActiveSupport::JSON::Encoding.use_standard_json_time_format = true
19
- puts "with standard_json == true: t.to_json - #{$t.to_json}"
20
- ActiveSupport::JSON::Encoding.use_standard_json_time_format = false
21
- puts "with standard_json == false: t.to_json - #{$t.to_json}"
22
- end
23
-
24
- check('Before Oj')
25
- =end
3
+ $: << '.'
4
+ $: << File.join(File.dirname(__FILE__), "../lib")
5
+ $: << File.join(File.dirname(__FILE__), "../ext")
26
6
 
27
7
  require 'oj'
28
8
 
29
- ActiveSupport::JSON::Encoding.escape_html_entities_in_json = false
30
- puts "ActiveSupport.encode(s) - #{ActiveSupport::JSON.encode($s)}"
9
+ json = %|[{"x12345678901234567890": true}]|
31
10
 
32
- Oj.optimize_rails
33
- Oj.default_options = { mode: :rails }
11
+ p = Oj::Parser.new(:usual)
12
+ p.cache_keys = false
13
+ p.symbol_keys = true
14
+ x = p.parse(json)
34
15
 
35
- puts "Oj.dump(s) - #{Oj.dump($s)}"
16
+ pp x
data/test/foo.rb CHANGED
@@ -1,13 +1,13 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- $: << File.dirname(__FILE__)
4
- $oj_dir = File.dirname(File.expand_path(File.dirname(__FILE__)))
5
- %w(lib ext).each do |dir|
6
- $: << File.join($oj_dir, dir)
7
- end
8
-
9
3
  require 'oj'
10
4
 
11
- p = Oj::Parser.new(:debug)
5
+ Oj::default_options = {cache_str: 0, cache_keys: true, mode: :strict}
6
+
7
+ puts "Ruby version: #{RUBY_VERSION}"
8
+ puts "Oj version: #{Oj::VERSION}"
9
+
10
+ puts "cache_keys: #{Oj::default_options[:cache_keys]}"
11
+ puts "cache_str: #{Oj::default_options[:cache_str]}"
12
12
 
13
- p.parse("[true, false]")
13
+ Oj.load('{"":""}').each_pair {|k,v| puts "k.frozen?: #{k.frozen?}\nv.frozen?: #{v.frozen?}"}
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
- # encoding: UTF-8
2
+ # encoding: utf-8
3
3
 
4
4
  $: << File.dirname(__FILE__)
5
5
 
@@ -73,9 +73,14 @@ class UsualTest < Minitest::Test
73
73
  assert_equal({a: true, b: false}, doc)
74
74
  end
75
75
 
76
- def test_capacity
76
+ def test_strings
77
77
  p = Oj::Parser.new(:usual)
78
- p.capacity = 1000
78
+ doc = p.parse('{"ぴ": "", "ぴ ": "x", "c": "ぴーたー", "d": " ぴーたー "}')
79
+ assert_equal({'ぴ' => '', 'ぴ ' => 'x', 'c' => 'ぴーたー', 'd' => ' ぴーたー '}, doc)
80
+ end
81
+
82
+ def test_capacity
83
+ p = Oj::Parser.new(:usual, capacity: 1000)
79
84
  assert_equal(4096, p.capacity)
80
85
  p.capacity = 5000
81
86
  assert_equal(5000, p.capacity)
@@ -181,8 +186,7 @@ class UsualTest < Minitest::Test
181
186
  end
182
187
 
183
188
  def test_missing_class
184
- p = Oj::Parser.new(:usual)
185
- p.create_id = '^'
189
+ p = Oj::Parser.new(:usual, create_id: '^')
186
190
  json = '{"a":true,"^":"Auto","b":false}'
187
191
  doc = p.parse(json)
188
192
  assert_equal(Hash, doc.class)