6

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

5
  • It would help if you posted your JSON pointer string Commented Aug 26, 2014 at 21:10
  • The question contains a link to an example of the same problem. My particular example would be too long to post. The object I am working with has at least 15 other objects and others within. The example I provided in the link should suffice to explain the problem. Commented Aug 26, 2014 at 21:13
  • Just trying to help you make your question clearer. Judging by the lack of responses, I would copy the JSON into your question. I, like others, take a quick look and if I have to dig through links, I give up. Commented Aug 26, 2014 at 21:19
  • Yeah, your right. Anyways, I determined the solution to the problem. So, it's all good. Commented Aug 26, 2014 at 21:26
  • 3
    Then please answer your own question for the benefit of others Commented Aug 26, 2014 at 21:31

1 Answer 1

4

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.

1
  • Updated for Angular2+ and created an interceptor for it. See this repo
    – JerMah
    Commented Aug 13, 2020 at 12:28

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.