Friday, January 25, 2013

JavaScript to deserialize JSON that preserves references


For client-server communications we increasingly use JSON to communicate objects. As our client code grows more complex we send richer and richer objects. One issue is that when you serialize to JSON and then deserialize on the client you lose object relationships. For example, let's say I send a List<Albums> but I also send a FavoriteAlbum. Now in code the actual Album object may be pointed to by the list and by FavoriteAlbum. It is a single object, but when you convert it to JSON it becomes two objects.

So JSON.Net has already solved this problem with "PreserveReferenceHandling". It attaches an $id property to every object and objects that are references to previously serialized objects have a $ref property and nothing else. This allows you to accurately represent non-trivial sets of object relationships.

The issue I ran into is that I didn't see any automatic tools for handling this. In using SignalR I found that SignalR automatically did JSON deserialization, but ignored the whole scheme of $id and $ref, so I put together some code to use these 'hints' to correctly build the right structure of the client. This code is happy path right now and it is dependent, somewhat unnecessarily, on Jquery. It also hasn't been tuned for performance, but I think it is a good start and is fine for prototyping.


var JSONPointerParse = (function ($) {
var hashOfObjects = {};

var collectIds = function (obj) {
    if ($.type(obj) === "object") {
        if (obj.hasOwnProperty("$id")) {
            hashOfObjects[obj.$id] = obj;
        }
        for (var prop in obj) {
            collectIds(obj[prop]);
        }
    } else if ($.type(obj) === "array") {
        obj.forEach(function (element) {
            collectIds(element);
        });
    }
};

var setReferences = function (obj) {
    if ($.type(obj) === "object") {
        for (var prop in obj) {
            if ($.type(obj[prop]) === "object" && 
                obj[prop].hasOwnProperty("$ref")) {
                obj[prop] = hashOfObjects[obj[prop]["$ref"]];
            } else {
                setReferences(obj[prop]);
            }
        }
    } else if ($.type(obj) === "array") {
        obj.forEach(function (element, index, array) {
            if ($.type(element) === "object" &&
                element.hasOwnProperty("$ref")) {
                array[index] = hashOfObjects[element["$ref"]];
            } else {
                setReferences(element);
            }
        });
    }
};

var pointerParse = function (obj) {
    hashOfObjects = {};
    collectIds(obj);
    setReferences(obj);
};
return { Run: pointerParse };
})(jQuery);

1 comment:

  1. Do you have a github page for this? If not it is ok I will create one based on the slight modifications I made to your code.

    ReplyDelete