Resolve circular references from JSON object - javascript

If I have a serialized JSON from json.net like so:
User:{id:1,{Foo{id:1,prop:1}},
FooList{$ref: "1",Foo{id:2,prop:13}}
I want to have knockout output a foreach over FooList but I am not sure how to proceed because the $ref things could throw things.
I'm thinking the solution would be to somehow force all the Foos to be rendered in the FooList by not using:
PreserveReferencesHandling = PreserveReferencesHandling.Objects
but that seems wasteful..

I've found some bugs and implemented arrays support:
function resolveReferences(json) {
if (typeof json === 'string')
json = JSON.parse(json);
var byid = {}, // all objects by id
refs = []; // references to objects that could not be resolved
json = (function recurse(obj, prop, parent) {
if (typeof obj !== 'object' || !obj) // a primitive value
return obj;
if (Object.prototype.toString.call(obj) === '[object Array]') {
for (var i = 0; i < obj.length; i++)
// check also if the array element is not a primitive value
if (typeof obj[i] !== 'object' || !obj[i]) // a primitive value
continue;
else if ("$ref" in obj[i])
obj[i] = recurse(obj[i], i, obj);
else
obj[i] = recurse(obj[i], prop, obj);
return obj;
}
if ("$ref" in obj) { // a reference
var ref = obj.$ref;
if (ref in byid)
return byid[ref];
// else we have to make it lazy:
refs.push([parent, prop, ref]);
return;
} else if ("$id" in obj) {
var id = obj.$id;
delete obj.$id;
if ("$values" in obj) // an array
obj = obj.$values.map(recurse);
else // a plain object
for (var prop in obj)
obj[prop] = recurse(obj[prop], prop, obj);
byid[id] = obj;
}
return obj;
})(json); // run it!
for (var i = 0; i < refs.length; i++) { // resolve previously unknown references
var ref = refs[i];
ref[0][ref[1]] = byid[ref[2]];
// Notice that this throws if you put in a reference at top-level
}
return json;
}

The json object which you are receiving from the server contains Circular References. Before using the object you should have to first remove all the $ref properties from the object, means in place of $ref : "1" you have to put the object which this link points.
In your case may be it is pointing to the User's object whose id is 1
For this you should check out Douglas Crockfords Plugin on github.There is a cycle.js which can do the job for you.
or you can use the following code (not tested) :
function resolveReferences(json) {
if (typeof json === 'string')
json = JSON.parse(json);
var byid = {}, // all objects by id
refs = []; // references to objects that could not be resolved
json = (function recurse(obj, prop, parent) {
if (typeof obj !== 'object' || !obj) // a primitive value
return obj;
if ("$ref" in obj) { // a reference
var ref = obj.$ref;
if (ref in byid)
return byid[ref];
// else we have to make it lazy:
refs.push([parent, prop, ref]);
return;
} else if ("$id" in obj) {
var id = obj.$id;
delete obj.$id;
if ("$values" in obj) // an array
obj = obj.$values.map(recurse);
else // a plain object
for (var prop in obj)
obj[prop] = recurse(obj[prop], prop, obj)
byid[id] = obj;
}
return obj;
})(json); // run it!
for (var i=0; i<refs.length; i++) { // resolve previously unknown references
var ref = refs[i];
ref[0][ref[1]] = byid[refs[2]];
// Notice that this throws if you put in a reference at top-level
}
return json;
}
Let me know if it helps !

This is actually extremely simple if you take advantage of JSON.parse's reviver parameter.
Example below. See browser console for the output because StackOverflow's snippet console output will not provide an accurate picture of what the result is.
// example JSON
var j = '{"$id":"0","name":"Parent",' +
'"child":{"$id":"1", "name":"Child","parent":{"$ref":"0"}},' +
'"nullValue":null}';
function parseAndResolve(json) {
var refMap = {};
return JSON.parse(json, function (key, value) {
if (key === '$id') {
refMap[value] = this;
// return undefined so that the property is deleted
return void(0);
}
if (value && value.$ref) { return refMap[value.$ref]; }
return value;
});
}
console.log(parseAndResolve(j));
<b>See actual browser console for output.</b>

I had trouble with the array correction in the answer of Alexander Vasiliev.
I can't comment his answer (don't own enough reputations points ;-) ), so I had to add a new answer...
(where I had a popup as best practice not to answer on other answers and only on the original question - bof)
if (Object.prototype.toString.call(obj) === '[object Array]') {
for (var i = 0; i < obj.length; i++) {
// check also if the array element is not a primitive value
if (typeof obj[i] !== 'object' || !obj[i]) // a primitive value
return obj[i];
if ("$ref" in obj[i])
obj[i] = recurse(obj[i], i, obj);
else
obj[i] = recurse(obj[i], prop, obj);
}
return obj;
}

In the accepted implementation, if you're inspecting an array and come across a primitive value, you will return that value and overwrite that array. You want to instead continue inspecting all of the elements of the array and return the array at the end.
function resolveReferences(json) {
if (typeof json === 'string')
json = JSON.parse(json);
var byid = {}, // all objects by id
refs = []; // references to objects that could not be resolved
json = (function recurse(obj, prop, parent) {
if (typeof obj !== 'object' || !obj) // a primitive value
return obj;
if (Object.prototype.toString.call(obj) === '[object Array]') {
for (var i = 0; i < obj.length; i++)
// check also if the array element is not a primitive value
if (typeof obj[i] !== 'object' || !obj[i]) // a primitive value
continue;
else if ("$ref" in obj[i])
obj[i] = recurse(obj[i], i, obj);
else
obj[i] = recurse(obj[i], prop, obj);
return obj;
}
if ("$ref" in obj) { // a reference
var ref = obj.$ref;
if (ref in byid)
return byid[ref];
// else we have to make it lazy:
refs.push([parent, prop, ref]);
return;
} else if ("$id" in obj) {
var id = obj.$id;
delete obj.$id;
if ("$values" in obj) // an array
obj = obj.$values.map(recurse);
else // a plain object
for (var prop in obj)
obj[prop] = recurse(obj[prop], prop, obj);
byid[id] = obj;
}
return obj;
})(json); // run it!
for (var i = 0; i < refs.length; i++) { // resolve previously unknown references
var ref = refs[i];
ref[0][ref[1]] = byid[ref[2]];
// Notice that this throws if you put in a reference at top-level
}
return json;
}

my solution(works for arrays as well):
usage: rebuildJsonDotNetObj(jsonDotNetResponse)
The code:
function rebuildJsonDotNetObj(obj) {
var arr = [];
buildRefArray(obj, arr);
return setReferences(obj, arr)
}
function buildRefArray(obj, arr) {
if (!obj || obj['$ref'])
return;
var objId = obj['$id'];
if (!objId)
{
obj['$id'] = "x";
return;
}
var id = parseInt(objId);
var array = obj['$values'];
if (array && Array.isArray(array)) {
arr[id] = array;
array.forEach(function (elem) {
if (typeof elem === "object")
buildRefArray(elem, arr);
});
}
else {
arr[id] = obj;
for (var prop in obj) {
if (typeof obj[prop] === "object") {
buildRefArray(obj[prop], arr);
}
}
}
}
function setReferences(obj, arrRefs) {
if (!obj)
return obj;
var ref = obj['$ref'];
if (ref)
return arrRefs[parseInt(ref)];
if (!obj['$id']) //already visited
return obj;
var array = obj['$values'];
if (array && Array.isArray(array)) {
for (var i = 0; i < array.length; ++i)
array[i] = setReferences(array[i], arrRefs)
return array;
}
for (var prop in obj)
if (typeof obj[prop] === "object")
obj[prop] = setReferences(obj[prop], arrRefs)
delete obj['$id'];
return obj;
}

Related

How to omit nested object property

what would be the best method to remove notneeded property from this array?
function apiCall(...args) {
//at this point I need to remove 'notneeded' property from args
};
apiCall({a: 'one', b: 'two'}, {notneeded: '', c: 'three'}, 'hello');
Those passed objects could be primitives, functions etc., so ideally the solution would handle every case.
I have created following function for this problem but I am sure there is a better solution (possibly with new ECMA standards).
function omit(args, omitKey) {
let omitted = [];
for (let i = 0; i < args.length; i++) {
if (typeof args[i] === 'object') {
omitted.push(
Object.keys(args[i]).reduce((result, key) => {
if (key !== omitKey) {
result[key] = args[i][key];
}
return result;
}, {}),
);
} else {
omitted.push(args[i]);
}
}
return omitted;
}
Don't mix the array iteration into that method. Make it do one task only.
function omit(obj, keyToOmit) {
if (typeof obj != "object") return obj;
const result = {};
for (const key in obj) {
if (key != keyToOmit) {
result[key] = obj[key];
}
}
return result;
}
function apiCall(...args) {
args = args.map(arg => omit(arg, "notneeded"));
…
}

Deep cloning object (JS) [duplicate]

This question already has answers here:
What is the most efficient way to deep clone an object in JavaScript?
(67 answers)
Closed 4 years ago.
how can I deep clone an object, what could be wrong with this solution.
I wrote this decision, but I'm not sure if this is good, and what bottlenecks it has.
How to do it correctly on vanilla js, without using jQuery. If the object has (enumerable: false)?
let user = {
name: 'SomeName',sayHi: function(){console.log(this.name);}}
Object.defineProperty(user, 'sayHi', {enumerable:false});
function deepCloneNew(obj){
if (!obj) { return };
let cloneObj = {};
let keys = Object.getOwnPropertyNames(obj);
keys.forEach((key)=>{
if(typeof obj[key] === 'object' && obj[key] !== null){
deepCloneNew(obj[key]);
}
if(typeof obj[key] === 'function'){
Object.defineProperty(cloneObj, key, Object.getOwnPropertyDescriptor(obj, key));
}
if(typeof obj[key] !== 'object' && typeof obj[key] !== 'function' || obj[key] === null){
Object.defineProperty(cloneObj, key, Object.getOwnPropertyDescriptor(obj, key));
}
})
return cloneObj;
}
let copy = deepCloneNew(user);
Please Follow this
function clone(item) {
if (!item) { return item; } // null, undefined values check
var types = [ Number, String, Boolean ],
result;
// normalizing primitives if someone did new String('aaa'), or new Number('444');
types.forEach(function(type) {
if (item instanceof type) {
result = type( item );
}
});
if (typeof result == "undefined") {
if (Object.prototype.toString.call( item ) === "[object Array]") {
result = [];
item.forEach(function(child, index, array) {
result[index] = clone( child );
});
} else if (typeof item == "object") {
// testing that this is DOM
if (item.nodeType && typeof item.cloneNode == "function") {
var result = item.cloneNode( true );
} else if (!item.prototype) { // check that this is a literal
if (item instanceof Date) {
result = new Date(item);
} else {
// it is an object literal
result = {};
for (var i in item) {
result[i] = clone( item[i] );
}
}
} else {
// depending what you would like here,
// just keep the reference, or create new object
if (false && item.constructor) {
// would not advice to do that, reason? Read below
result = new item.constructor();
} else {
result = item;
}
}
} else {
result = item;
}
}
return result;
}

Find specific property inside an object

I have an object. I want to check if a specific property exists in it or not.
The issue is: the property that I am looking for, could be anywhere, i.e: the structure of the object is undefiend.
ex:
obj1 = { "propIWant": "xyz" }
obj2 = { "prop1": [ {"key": "value"}, {"key":"value"}, 1, {"key": { "propIWant": "xyz"}}]
I've tried the following, but it seems to fail:
var lastTry = function(entry){
// if entry is an array
if(typeof entry === 'object' && entry instanceof Array){
for(var i in entry)
entry[i] = this.lastTry(entry[i]);
}
// if entry is a normal object
else if(typeof entry === 'object'){
// iterate through the properties of the entry
for(var key in entry){
console.log('key is: ', entry[key])
// in case the entry itself is an array
if(typeof entry[key] === 'object' && entry[key] instanceof Array){
for(var i in entry[key]){
entry[key][i] = this.lastTry(entry[key][i]);
}
}
// in case the entry is a simple object
else if(typeof entry[key] === 'object') {
console.log('entry[key] is an object', entry[key], key)
// if we directely find the property.. modify it
if(entry[key].hasOwnProperty('_internal_url')){
**entry[key]['_internal_url'] = "http://localhost:4000"+entry[key]['_internal_url'];** <-- My objective
}
else{
// call this method again on that part
// for(var i in entry[key]){
// if(typeof entry[key][i] === 'object')
// entry[key][i] = this.lastTry(entry[key][i]);
// }
}
}else{
console.log('not found')
}
}
}
}
Can someone please help me out with it?I found the following: Find by key deep in a nested object but, instead of returning the found part, I want to edit the property and return the entire object with the modified property, not just the subset of the object that has that property.
Have you tried :
obj1.hasOwnProperty('propIWant')
If you wish to just check if property exists or not, you can stringify the object and then check if value exists in string or not.
var obj2 = {
"prop1": [{
"key": "value"
}, {
"key": "value"
},
1, {
"key": {
"propIWant": "xyz"
}
}
]
}
var key = 'propIWant';
var key2 = 'propIWant1';
function isPropInObject(obj, prop) {
var r = new RegExp(prop + "\":")
var match = JSON.stringify(obj).match(r);
return match && match.length > 0 ? true : false
}
console.log(isPropInObject(obj2, key))
console.log(isPropInObject(obj2, key2))
Arrays and objects data can be access in a common way:
obj[key] and arr[pos];
This way you can simplify your code. Please note that key can be a string in case of a object or a number in case of an array.
The version below only searches in the element and eventual children elements(both arrays and objects) in a depth-first logic.
var found = 0;
var findProp = function(entry) {
if(typeof entry === 'object')
for(var key in entry) {
findProp(entry[key]);
if( entry[key].hasOwnProperty('_internal_url')) {
found++;
entry[key]['_internal_url'] = "http://localhost:4000" + entry[key]['_internal_url'];
}
}
}
console.log('found ' + found + 'occurrences');
Well, check if the props is objects themselves and use a recursive function to find the "deep" property you are looking for?
function findProp(obj, lookFor) {
for (var prop in obj) {
if (prop == lookFor) return obj[prop]
if (typeof obj[prop] == 'object') {
var checkNested = findProp(obj[prop], lookFor)
if (checkNested) return checkNested
}
}
return false
}
It works with console.log(findProp(obj2, 'propIWant'))
demo -> http://jsfiddle.net/zqcurg70/

How do I SET to the Object's properties if I have a path array to the needed property?

var obj = {
people: {
John: {
pets:{
dog:{
name:"Umbrella",
age:12
},
cat:{
name:"Kitty",
age:5
}
}
}
}
};
var path=['people', 'John', 'pets', 'cat', 'name'];
var newName='Train';
How do I SET what the array wants (now it is cat's name) to the object? The array can be changed, so I can not write it by hand. I need a function to do it automatically depending on the array
You can use a recursive function like this one:
var prop = function (obj, chain) {
if (obj && chain.length > 0) {
return prop(obj[chain[0]], chain.slice(1));
}
return obj;
};
prop(obj, path);
Or even an iterative function:
var prop = function (obj, chain) {
var i = 0;
while (obj && i < chain.length) {
obj = obj[chain[i]];
i++;
}
return obj;
};
prop(obj, path);
EDIT: To set a value, you can do something like this:
var set = function (obj, chain, value) {
if (obj === undefined || obj === null) return;
var i = 0;
while (obj[chain[i]] !== undefined && obj[chain[0]] !== null && i < chain.length - 1) {
obj = obj[chain[i]];
i++;
}
obj[chain[i]] = value;
};
set(obj, path, 'Train');
use the below function recursively
foreach(var key in obj)
{
console.log('key'); //gives the key name like - people, John, Pets, dog, cat etc.,
}
Using for loop:
var cursor = obj;
for (var i = 0, len = path.length; i < len; i++) {
cursor = cursor[path[i]];
}

How to copy or duplicate an array of arrays

I'm trying to make a function that duplicates an array of arrays. I tried blah.slice(0); but it only copies the references. I need to make a duplicate that leaves the original intact.
I found this prototype method at http://my.opera.com/GreyWyvern/blog/show.dml/1725165
Object.prototype.clone = function() {
var newObj = (this instanceof Array) ? [] : {};
for (i in this) {
if (i == 'clone') continue;
if (this[i] && typeof this[i] == "object") {
newObj[i] = this[i].clone();
} else newObj[i] = this[i]
} return newObj;
};
It works, but messes up a jQuery plugin I'm using - so I need to turn it onto a function... and recursion isn't my strongest.
Your help would be appreciated!
Cheers,
function clone (existingArray) {
var newObj = (existingArray instanceof Array) ? [] : {};
for (i in existingArray) {
if (i == 'clone') continue;
if (existingArray[i] && typeof existingArray[i] == "object") {
newObj[i] = clone(existingArray[i]);
} else {
newObj[i] = existingArray[i]
}
}
return newObj;
}
For example:
clone = function(obj) {
if (!obj || typeof obj != "object")
return obj;
var isAry = Object.prototype.toString.call(obj).toLowerCase() == '[object array]';
var o = isAry ? [] : {};
for (var p in obj)
o[p] = clone(obj[p]);
return o;
}
improved as per comments

Categories