Skip to content

Commit

Permalink
added hash circular support
Browse files Browse the repository at this point in the history
  • Loading branch information
Peter Ohler committed Mar 3, 2012
1 parent 4fcab53 commit fdacd8d
Show file tree
Hide file tree
Showing 5 changed files with 120 additions and 49 deletions.
14 changes: 11 additions & 3 deletions README.md
Expand Up @@ -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].
86 changes: 52 additions & 34 deletions ext/oj/dump.c
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down
13 changes: 8 additions & 5 deletions ext/oj/load.c
Expand Up @@ -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;
Expand All @@ -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);

Expand All @@ -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;
Expand Down
10 changes: 5 additions & 5 deletions notes
Expand Up @@ -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
Expand Down
46 changes: 44 additions & 2 deletions test/tests.rb
Expand Up @@ -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)
Expand Down

0 comments on commit fdacd8d

Please sign in to comment.