Avoiding TypeError: Convert circular structure to JSON

I know this has been asked several times, but I still can't seem to find a solution to this problem. (I just started using KO 2 weeks ago.)

I have a bunch of fields, each of which is bound to observables in my VM. When the user clicks the GO button, I want to convert all fields entered to a JSON string and send to the server. This is the ViewModel:

function ViewModel() {
    var self = this;

    function searchVar() {
        self.users = ko.observable();
        self.fromdate = ko.observable();
        self.todate = ko.observable();
        self.eqpname = ko.observable();
        self.maker = ko.observable();
        self.model = ko.observable();
        self.makercode = ko.observable();
        self.partname = ko.observable();
        self.vendor = ko.observable();
    }

    self.searchVars = ko.observableArray();

    self.addSearchVar = function (vm) {
        self.searchVars.removeAll();
        self.searchVars.push(vm);
        //console.log(vm);
        var data = ko.toJSON(self.searchVars);
        alert(data);
    };

    var sv = new searchVar(); //not really using this

}
    var vm = new ViewModel();

      

The problem is that the addSearchVar function creates a reference to the searchVars array again and this creates a circular reference error (did I formulate this correctly?). Is there a better way to do this? (tried using nested model but couldn't wrap it around it).

The markup is pretty much standard. data-bind:"value: [fieldname]"

etc. I placed ko.applyBindings(vm)

mine at the bottom of the page as I have a lot of dynamically generated components. Button properties:<input type="button" id="btnGo" value="Go" style="background-color:#CCFFCC" data-bind="click: addSearchVar" />

Appreciate any ideas. Thank you. / A ^

+2


source to share


1 answer


You can prevent this by removing / replacing circular references right before json encode your viewmodel. One way to do this is to use Douglas JSON.decycle

:

if (typeof JSON.decycle !== 'function') {
    JSON.decycle = function decycle(object) {
        'use strict';

// Make a deep copy of an object or array, assuring that there is at most
// one instance of each object or array in the resulting structure. The
// duplicate references (which might be forming cycles) are replaced with
// an object of the form
//      {$ref: PATH}
// where the PATH is a JSONPath string that locates the first occurance.
// So,
//      var a = [];
//      a[0] = a;
//      return JSON.stringify(JSON.decycle(a));
// produces the string '[{"$ref":"$"}]'.

// JSONPath is used to locate the unique object. $ indicates the top level of
// the object or array. [NUMBER] or [STRING] indicates a child member or
// property.

        var objects = [],   // Keep a reference to each unique object or array
            paths = [];     // Keep the path to each unique object or array

        return (function derez(value, path) {

// The derez recurses through the object, producing the deep copy.

            var i,          // The loop counter
                name,       // Property name
                nu;         // The new object or array

// typeof null === 'object', so go on if this value is really an object but not
// one of the weird builtin objects.

            if (typeof value === 'object' && value !== null &&
                    !(value instanceof Boolean) &&
                    !(value instanceof Date)    &&
                    !(value instanceof Number)  &&
                    !(value instanceof RegExp)  &&
                    !(value instanceof String)) {

// If the value is an object or array, look to see if we have already
// encountered it. If so, return a $ref/path object. This is a hard way,
// linear search that will get slower as the number of unique objects grows.

                for (i = 0; i < objects.length; i += 1) {
                    if (objects[i] === value) {
                        return {$ref: paths[i]};
                    }
                }

// Otherwise, accumulate the unique value and its path.

                objects.push(value);
                paths.push(path);

// If it is an array, replicate the array.

                if (Object.prototype.toString.apply(value) === '[object Array]') {
                    nu = [];
                    for (i = 0; i < value.length; i += 1) {
                        nu[i] = derez(value[i], path + '[' + i + ']');
                    }
                } else {

// If it is an object, replicate the object.

                    nu = {};
                    for (name in value) {
                        if (Object.prototype.hasOwnProperty.call(value, name)) {
                            nu[name] = derez(value[name],
                                path + '[' + JSON.stringify(name) + ']');
                        }
                    }
                }
                return nu;
            }
            return value;
        }(object, '$'));
    };
}

      

https://github.com/douglascrockford/JSON-js/blob/master/cycle.js

This replaces all circular references with the reference value.

In your case, you would use it like this:



var data = JSON.stringify(JSON.decycle(ko.toJS(self.searchVars)));

      

Another option is to remove all cirular references:

JSON.stringifyOnce = function (obj, replacer, space) {
    var cache = [];
    var json = JSON.stringify(obj, function(key, value) {
        if (typeof value === 'object' && value !== null) {
            if (cache.indexOf(value) !== -1) {
                // circular reference found, discard key
                return;
            }
            // store value in our collection
            cache.push(value);
        }
        return replacer ? replacer(key, value) : value;
    }, space);
    cache = null;
    return json;
};

      

And then:

var data = JSON.stringifyOnce(ko.toJS(self.searchVars));

      

0


source







All Articles