JSON Serialization – Resolving $ref in JSON Objects

cjsonserialization

I have written a single page application that uses rest services to retrieve JSON objects. The JSON objects being returned are C# objects serialized using the Newtonsoft.JSON library. The returned JSON object contains $ref pointers to specific $id objects within the same JSON object (The question here shows an example). I need to access the actual data that the $ref points to, but I do not know how to access it without locating the original source.

I've found solutions such as writing a new contract serializer and writing my own implementation of resolving the references. I feel like there should be an easier solution than these two. I've also explored the Newtonsoft JSON serialization settings; I've tried setting PreserveReferenceHandling to None, but I haven't had any success there.

I'm looking for solutions other than the ones I've mentioned. I'm hoping there's a simple solution that requires very little code to implement and no additional frameworks.

UPDATE

I've been reading some more about this and discovered that $ref is a JSON Pointer fragment. The reference number is an ID that references an item from a different schema. That appears to be the solution, but a schema needs to be defined for it to work.

RFC for JSON Pointer Fragments

Info about JSON Pointer Fragments

References:
Resolve circular JSON references
Serialization Settings

Best Answer

I was able to resolve the "$ref" links in my json code by adapting the code, this blogger posted, to be Angular compliant.

http://willseitz-code.blogspot.com/2013/01/javascript-to-deserialize-json-that.html

I created an angular service to resolve the "$ref" I get from NewtonSofts JSON serializer.

// http://willseitz-code.blogspot.com/2013/01/javascript-to-deserialize-json-that.html
(function () {
'use strict';

angular
    .module('app')
    .factory('jsonPointerParseService', jsonPointerParseService);

jsonPointerParseService.$inject = [];

function jsonPointerParseService() {
    var hashOfObjects = {};

    var service = {
        pointerParse: pointerParse
    };

    return service;

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

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

    // Set the max depth of your object graph because JSON.stringify will not be able to
    // serialize a large object graph back to 
    function setMaxDepth(obj, depth) {
        // If this is not an object or array just return there is no need to 
        // set its max depth.
        if (jQuery.type(obj) !== "array" && jQuery.type(obj) !== "object") {
            return obj;
        }

        var newObj = {};

        // If this object was an array we want to return the same type in order
        // to keep this variable's consistency.
        if (jQuery.type(obj) === "array") {
            newObj = [];
        }

        // For each object or array cut off its tree at the depth value by 
        // recursively diving into the tree.
        angular.forEach(obj, function (value, key) {
            if (depth == 1) {
                newObj = null;
            }
            else if (jQuery.type(value) === "array") {
                if (setMaxDepth(value, depth - 1)) {
                    newObj[key] = setMaxDepth(value, depth - 1)
                } else {
                    newObj = [];
                }
            } else if (jQuery.type(value) === "object") {
                if (setMaxDepth(value, depth - 1)) {
                    newObj[key] = setMaxDepth(value, depth - 1)
                } else {
                    newObj = [];
                }
            } else {
                newObj[key] = value;
            }
        }, newObj);

        return newObj;
    }

    function pointerParse(obj, depth) {
        var newObj = obj;

        hashOfObjects = {};
        collectIds(newObj);
        setReferences(newObj);

        if (depth) {
            newObj = setMaxDepth(newObj, depth);
        }

        return newObj;
    }
}
})();

Usage would be like so:

 var newObj = jsonPointerParseService.pointerParse(obj);

Please keep in mind, this is an Angular service. So I inject it into whichever controller needs to have these references resolved, and make the call to pointerParse() accordingly.

Please note though that if you want to post this object back to the server, your browsers JSON.stringify() call has to be able to handle an object with circular references. By default, in my case, Angular does not call JSON.stringify() with the parameters required to be able to stringify an object with circular references. So, in this case I would suggest posting only a small portion of data and not this entire object back or instantiating your own call to stringify an object like this.

Lastly, I must say that dealing with $ref is a pain and I decided to preserve reference handling with only arrays. This seemed to work well for me. I still had to access array values with $values however I didnt have to worry about resolving references and didnt have to worry about the server blowing up trying to serialize all of the object circular references to json.

json.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Serialize;

json.SerializerSettings.PreserveReferencesHandling = Newtonsoft.Json.PreserveReferencesHandling.Arrays;

Update: Even with only resolving arrays there are still references that you may need to resolve. In those instances use the pointerParse() function to take care of them.

Related Topic