Dear Stack Overflow Community,
I have a question regarding object property replacement (you can skip to the bottom to read the question) . I'm working on an application, that gets a teacher object from the Back-end. The Back-end is using java hibernate to to a query of the database and serialize the object to be send to the front end (me). I get this teacher object, but there is circular references in the object. Which java handles by adding a reference id in place of the object. So i get this teacher object, and inside the object are these reference id's that i need to replace with the actual object, the problem being that they are nested objects. What i currently do is create a stack, and traverse the objects to find these id's and reference id's. When i find an id i put it on the stack. Not a problem, but when i find a reference id i need to repalce it with the one i found on the stack. Once again this is hard to do because i do these through a recursive function, so i don't have the physical object.
My question is, how can i replace nested object properties
JS BIN
var stack = {};
var add = function(prop, data) {
if (prop == '@id') {
stack[data[prop]] = data;
}
if (prop == '@ref') {
// console.log(stack[data[prop]])
// How do i replace the value with the stack object
// test.PROPERTY = stack[data[prop]] is what im looking for
}
}
var traverse = function(object) {
for (var property in object) {
if (object.hasOwnProperty(property)) {
add(property, object);
if (object[property] && typeof object[property] === 'object') {
traverse(object[property]);
}
}
}
}
var test = {
'teacher': {
'course': {
"@id": "1",
'students': [{
"@id": "2",
'name': "John",
'course': {
"@ref": "1",
}
}, {
"@id": "2",
'name': "Next",
'course': {
"@ref": "1",
}
}]
}
}
};
setTimeout(() => {
console.log(stack);
// console.log(test);
}, 500);
traverse(test);
Consider iterating over the data structure twice:
Recursive traversal for populating an id-to-object map (where the keys are ids and the values are the actual objects). You can initialize the map in the first recursive call, and then pass it to the next recursion levels.
Recursive traversal for replacing the references with the actual objects. Since you now have a map, this replacement can be done in-place.
Code
You should first traverse through the object and collect:
Having such a list is a key of solution. It allows you both: avoid second recursive traverse of the test
object, and storing enough information to replace the whole {"@ref":"..."}
object and not only it's contents.
Next, you should iterate through the list and replace all {"@ref":"..."}
objects with references to actual objects from dictionary.
var test = {
'teacher': {
'course': {
"@id": "1",
'students': [{
"@id": "2",
'name': "John",
'course': {
"@ref": "1",
}
}, {
"@id": "2",
'name': "Next",
'course': {
"@ref": "1",
}
}]
}
}
};
function collectRefs(obj, dic, list) {
obj.hasOwnProperty('@id') && (dic[obj['@id']] = obj); // populate dictionary
Object.keys(obj).forEach(function(key) {
if(obj[key] && typeof obj[key] === 'object') {
if(obj[key].hasOwnProperty('@ref')) {
list.push([obj, key]) // populate list
} else {
collectRefs(obj[key], dic, list) // traverse recursively
}
}
});
return obj;
}
function recover(obj) {
var dic = {}, list = [];
collectRefs(obj, dic, list);
list.forEach(function(item) {
item[0][item[1]] = dic[item[0][item[1]]['@ref']]; // make replacements
});
return obj;
}
console.log(recover(test).teacher.course.students[0].course['@id']); // 1
At the end all {"@ref": "1"}
will be replaced with reference to object {"@id": "1", ...}
P.S.
By the way, there is a better solution to serialize/deserialize JSON with circular references (cycle.js) provided by inventor of JSON format Douglas Crockford. It utilizes JSONPath as value of @ref
objects and, therefore does not require to have @id
on referred objects. Also, there is no need to have additional data structures (like dictionary and list from example above) to reconstruct original object with circular references. So, if you can change the way your Back-end serializes objects, I recommend you to look into it.
I don't know if it's what you want
var test = {
'teacher': {
'course': {
"@id": "1",
'students': [{
"@id": "2",
'name': "John",
'course': {
"@ref": "1",
}
}, {
"@id": "2",
'name': "Next",
'course': {
"@ref": "1",
}
}]
}
}
};
let tmpObj = {};
let noRef = {};
function traverse(obj){
Object.keys(obj).forEach(function(v){
if(v !== '@ref' && typeof obj[v] === 'object'){
var newObj = obj[v];
if(obj[v]['@id']){
if(!tmpObj[v]){
tmpObj[v] = {};
}
tmpObj[v][obj[v]['@id']] = obj;
}
if(obj[v]['@ref'] ){
if(tmpObj[v] && tmpObj[v][obj[v]['@ref']]){
obj[v]['@ref'] = tmpObj[v][obj[v]['@ref']][v];
}else{
if(!noRef[v]){
noRef[v] = [];
}
noRef[v].push(newObj)
}
}
traverse(newObj);
}
})
}
traverse(test);
Object.keys(noRef).forEach(function(v){
v.forEach(function(vv){
vv['@ref'] = tmpObj[v][vv['@ref']][v];
})
});
console.log(test);
The object noRef
it's in the case the ref object was not yet found, so I put in it the parent object of the current ref, and when traverse has finished I loop on it to put the associatin to not found ref
If you directly set the value. in the next iterations, the object will have a circular reference and you end up in an infinite loop. So push the assignment operation into the event queue( a simple way to do it is to call setTimeout passing 0 sec). I hope this will help you.
var stack = {};
var add = function(prop, data) {
if (prop == '@id') {
stack[data[prop]] = data;
}
if (prop == '@ref') {
// console.log(stack[data[prop]])
// How do i replace the value with the stack object
setTimeout(function(){
data[prop] = stack
},0);
}
}
var traverse = function(object) {
for (var property in object) {
if (object.hasOwnProperty(property)) {
add(property, object);
if (object[property] && typeof object[property] === 'object') {
traverse(object[property]);
}
}
}
}
var test = {
'teacher': {
'course': {
"@id": "1",
'students': [{
"@id": "2",
'name': "John",
'course': {
"@ref": "1",
}
}, {
"@id": "2",
'name': "Next",
'course': {
"@ref": "1",
}
}]
}
}
};
setTimeout(() => {
console.log(stack);
// console.log(test);
}, 500);
traverse(test);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With