Permalink
Browse files

added hash circular support

  • Loading branch information...
1 parent 4fcab53 commit fdacd8d561ed425c40c78025ce9e2bac91dec191 Peter Ohler committed Mar 3, 2012
Showing with 120 additions and 49 deletions.
  1. +11 −3 README.md
  2. +52 −34 ext/oj/dump.c
  3. +8 −5 ext/oj/load.c
  4. +5 −5 notes
  5. +44 −2 test/tests.rb
View
14 README.md
@@ -278,6 +278,14 @@ the value. An example is {"^#3":[2,5]}.
12. A "^i" JSON entry in either an Object or Array is the ID of the Ruby
Object being encoded. It is used when the :circular flag is set. It can appear
-in either a JSON Object or in a JSON Array. If alone it represented a link to
-the original Hash or JSON. If an added attribute it is the ID of the original
-Object or Array. An example is {"^o":"Oj::Bag","^i":1,"x":3,"me":{"^i":1}}.
+in either a JSON Object or in a JSON Array. In an Object the "^i" key has a
+corresponding reference Fixnum. In an array the sequence will include an
+embedded reference number. An example is
+{"^o":"Oj::Bag","^i":1,"x":["^i2":2,true],"me":{"^r":1}}.
+
+13. A "^r" JSON entry in an Object is a references to a Object or Array that
+already appears in the JSON String. It must match up with a previous "^i"
+ID. An example is {"^o":"Oj::Bag","^i":1,"x":3,"me":{"^r":1}}.
+
+14. If an Array element is a String and starts with "^i" then it is encoded as
+an String Object. An example is [{"^s":"^i37"},3].
View
86 ext/oj/dump.c
@@ -201,6 +201,37 @@ dump_hex(u_char c, Out out) {
*out->cur++ = hex_chars[d];
}
+// returns 0 if not using circular references, -1 if not further writing is
+// needed (duplicate), and a positive value if the object was added to the cache.
+static long
+check_circular(VALUE obj, Out out) {
+ slot_t id = 0;
+ slot_t *slot;
+
+ if (ObjectMode == out->opts->mode && Yes == out->opts->circular) {
+ if (0 == (id = oj_cache8_get(out->circ_cache, obj, &slot))) {
+ out->circ_cnt++;
+ id = out->circ_cnt;
+ *slot = id;
+ } else {
+ if (out->end - out->cur <= 18) {
+ grow(out, 18);
+ }
+ *out->cur++ = '{';
+ *out->cur++ = '"';
+ *out->cur++ = '^';
+ *out->cur++ = 'r';
+ *out->cur++ = '"';
+ *out->cur++ = ':';
+ dump_ulong(id, out);
+ *out->cur++ = '}';
+
+ return -1;
+ }
+ }
+ return (long)id;
+}
+
static void
dump_nil(Out out) {
size_t size = 4;
@@ -578,17 +609,35 @@ hash_cb_object(VALUE key, VALUE value, Out out) {
static void
dump_hash(VALUE obj, int depth, int mode, Out out) {
- int cnt = (int)RHASH_SIZE(obj);
+ int cnt = (int)RHASH_SIZE(obj);
+ size_t size = depth * out->indent + 2;
if (out->end - out->cur <= 2) {
grow(out, 2);
}
- *out->cur++ = '{';
if (0 == cnt) {
+ *out->cur++ = '{';
*out->cur++ = '}';
} else {
- size_t size = depth * out->indent + 2;
+ long id = check_circular(obj, out);
+ if (0 > id) {
+ return;
+ }
+ *out->cur++ = '{';
+ if (0 < id) {
+ if (out->end - out->cur <= (long)size + 16) {
+ grow(out, size + 16);
+ }
+ fill_indent(out, depth + 1);
+ *out->cur++ = '"';
+ *out->cur++ = '^';
+ *out->cur++ = 'i';
+ *out->cur++ = '"';
+ *out->cur++ = ':';
+ dump_ulong(id, out);
+ *out->cur++ = ',';
+ }
out->depth = depth + 1;
if (ObjectMode == mode) {
rb_hash_foreach(obj, hash_cb_object, (VALUE)out);
@@ -699,37 +748,6 @@ dump_obj_comp(VALUE obj, int depth, Out out) {
*out->cur = '\0';
}
-// returns 0 if not using circular references, -1 if not further writing is
-// needed (duplicate), and a positive value if the object was added to the cache.
-static long
-check_circular(VALUE obj, Out out) {
- slot_t id = 0;
- slot_t *slot;
-
- if (Yes == out->opts->circular) {
- if (0 == (id = oj_cache8_get(out->circ_cache, obj, &slot))) {
- out->circ_cnt++;
- id = out->circ_cnt;
- *slot = id;
- } else {
- if (out->end - out->cur <= 18) {
- grow(out, 18);
- }
- *out->cur++ = '{';
- *out->cur++ = '"';
- *out->cur++ = '^';
- *out->cur++ = 'i';
- *out->cur++ = '"';
- *out->cur++ = ':';
- dump_ulong(id, out);
- *out->cur++ = '}';
-
- return -1;
- }
- }
- return (long)id;
-}
-
inline static void
dump_obj_obj(VALUE obj, int depth, Out out) {
long id = check_circular(obj, out);
View
13 ext/oj/load.c
@@ -376,11 +376,14 @@ read_obj(ParseInfo pi) {
obj_type = T_STRUCT;
key = Qundef;
break;
- case 'i': // Id for circular reference
+ case 'r': // Id for circular reference
val = read_next(pi, T_FIXNUM);
if (T_FIXNUM == rb_type(val)) {
obj_type = T_FIXNUM;
obj = circ_array_get(pi->circ_array, NUM2ULONG(val));
+ if (Qundef == obj || Qnil == obj) {
+ raise_error("Failed to find referenced object", pi->str, pi->s);
+ }
key = Qundef;
}
break;
@@ -394,6 +397,10 @@ read_obj(ParseInfo pi) {
if (Qundef == val && Qundef == (val = read_next(pi, 0))) {
raise_error("unexpected character", pi->str, pi->s);
}
+ if (Qundef == obj) {
+ obj = rb_hash_new();
+ obj_type = T_HASH;
+ }
if (ObjectMode == pi->options->mode && 0 != ks && '^' == *ks) {
int val_type = rb_type(val);
@@ -410,10 +417,6 @@ read_obj(ParseInfo pi) {
}
}
if (Qundef != key) {
- if (Qundef == obj) {
- obj = rb_hash_new();
- obj_type = T_HASH;
- }
if (T_OBJECT == obj_type) {
VALUE *slot;
ID var_id;
View
10 notes
@@ -7,11 +7,11 @@
- next
- circular reference
- - where to put id
- - wrapper - others do not understand it
- - inside
- - no problem on Object
- - Hash and Array have conflicts unless escaped (could do as is done for others in hash)
+ - test
+ - array
+ - ^in for reference to current array
+ - ^r for reference
+ - perf
- stream
View
46 test/tests.rb
@@ -416,16 +416,58 @@ def test_circular_object
obj = Jam.new(nil, 58)
obj.x = obj
json = Oj.dump(obj, :mode => :object, :indent => 2, :circular => true)
- puts json
assert_equal(%{{
"^o":"Jam",
"^i":1,
- "x":{"^i":1},
+ "x":{"^r":1},
"y":58}}, json)
obj2 = Oj.load(json, :mode => :object, :circular => true)
assert_equal(obj2.x.__id__, obj2.__id__)
end
+ def test_circular_hash
+ h = { 'a' => 7 }
+ h['b'] = h
+ json = Oj.dump(h, :mode => :object, :indent => 2, :circular => true)
+ assert_equal(%{{
+ "^i":1,
+ "a":7,
+ "b":{"^r":1}}}, json)
+ h2 = Oj.load(json, :mode => :object, :circular => true)
+ assert_equal(h['b'].__id__, h.__id__)
+ end
+
+ def xtest_circular_array
+ a = [7]
+ a << a
+ json = Oj.dump(a, :mode => :object, :indent => 2, :circular => true)
+ puts json
+ assert_equal(%{[
+ "^i1",
+ 7,
+ {"^r":1}]}, json)
+ h2 = Oj.load(json, :mode => :object, :circular => true)
+ assert_equal(h['b'].__id__, h.__id__)
+ end
+
+ def test_circular
+ h = { 'a' => 7 }
+ obj = Jam.new(h, 58)
+ obj.x['b'] = obj
+ json = Oj.dump(obj, :mode => :object, :indent => 2, :circular => true)
+ assert_equal(%{{
+ "^o":"Jam",
+ "^i":1,
+ "x":{
+ "^i":2,
+ "a":7,
+ "b":{"^r":1}
+ },
+ "y":58}}, json)
+ obj2 = Oj.load(json, :mode => :object, :circular => true)
+ assert_equal(obj.x.__id__, h.__id__)
+ assert_equal(h['b'].__id__, obj.__id__)
+ end
def dump_and_load(obj, trace=false)
json = Oj.dump(obj, :indent => 2)

0 comments on commit fdacd8d

Please sign in to comment.