Building a nested tree of data from plain array of objects - javascript

I have an array of objects, like those ones:
{
"short_id": "2p45q",
"path": "/",
"name": {
"en-US": "IndustrialDesign"
}
}
...
{
"short_id": "2q56r",
"path": "/2p45q/",
"name": {
"en-US": "Automotive"
}
}
I must iterate over each element of the array and check the path, then find the parent of the element and push it in a new array property of that parent called sub. Each child can have a sub property on it's own, thus being a parent of more children. The final result (for this example) would look like:
{
"short_id": "2p45q",
"path": "/",
"name": {
"en-US": "Test A"
},
"sub": [
{
"short_id": "2q56r",
"path": "/2p45q/",
"name": {
"en-US": "Test A.1"
}
}
]
}
I have a working code (using this jsonpath lib):
function(categories) {
var _categories = [];
angular.forEach(angular.copy(categories), function(_category) {
if (_category.path === "/") {
_categories.push(_category);
} else {
var _path = _category.path.split("/");
_path.pop();
var _parent = _path.pop();
jsonpath.apply(_categories, "$..[?(#.short_id=='" + _parent + "')]", function(obj) {
if(!obj.hasOwnProperty("sub")) {
obj.sub = [];
}
obj.sub.push(_category);
return obj;
});
}
});
return _categories;
}
but the performance is really bad, mainly because I'm querying the entire array for each iteration.
My question is how can I optimize my code?
Notes:
Each short_id is exactly 5 characters long.
Each character in short_id can be [0-9a-z]
path is guaranteed to start and end with a /

Create another tmp object as Hashmap, so you can just use path and id to create a new key to store.
Logic :
If path is '/', its root, put to the _categories array.
If not, check if the target parent is exist in the hashStore or not, if not, create a fake one, and put it self to target is sub attr.
For all element, create a key by _category.path + _category.short_id + '/', and check if its exist in the hashStore, if exist, the one in store should be a fake, get sub from fake. Then assign itself to the hashStore by created key.
Use a key to decide whether the object exist in the map or not should be O(1).
So the performance of the this function should be O(n) while n is the number of element in origin list.
function buildTree(categories) {
var _categories = [];
var store = {};
angular.forEach(angular.copy(categories), function(_category) {
if (_category.path === '/') {
_categories.push(_category);
} else {
var target;
// If parent exist
if (typeof store[_category.path] !== 'undefined') {
// Check parent have sub or not, if not, create one.
target = store[_category.path];
if (typeof store[_category.path].sub === 'undefined') {
target.sub = [];
}
} else {
// Create fake parent.
target = {sub: []};
store[_category.path] = target;
}
// Push to parent's sub
target.sub.push(_category);
}
// Create key map
var key = _category.path + _category.short_id + '/';
// If fake object exist, get its sub;
if (typeof store[key] !== 'undefined') {
_category.sub = store[key].sub;
}
store[key] = _category;
});
return _categories;
}

This solution is more flexible in that it doesn't require knowledge of path length or correlation with short_id
var source = [{
"short_id": "2p45q",
"path": "/",
"name": {
"en-US": "IndustrialDesign"
}
}, {
"short_id": "2q56r",
"path": "/2p45q/",
"name": {
"en-US": "Automotive"
}
}];
function buildTree(arr) {
var source = arr.slice();
source.sort(function(a, b) {
return a.path.length <= b.path.length;
});
var tree = source.splice(0, 1)[0];
tree.subo = {};
source.forEach(function(i) {
var re = /[^\/]*\//g;
var context = tree;
while ((m = re.exec(i.path.substr(1))) !== null) {
if (context.subo[m[0]] === undefined) {
context.subo[m[0]] = i;
i.subo = {};
return;
}
context = context.subo[m[0]];
}
});
(function subOsub(i) {
var keys = Object.keys(i.subo);
if (keys.length > 0) {
i.sub = [];
for (var j = 0; j < keys.length; j++) {
i.sub.push(i.subo[keys[j]]);
subOsub(i.subo[keys[j]]);
}
}
delete i.subo;
})(tree);
return tree;
}
alert(JSON.stringify(buildTree(source), null, ' '));

Well, just examine the path of each object to see where to put it.
You just need a mapping of paths to objects. E.g.
var objs = [
{
"short_id": "2p45q",
"path": "/",
"name": {
"en-US": "IndustrialDesign"
}
},
{
"short_id": "blah",
"path": "/2p45q/foo/",
"name": {
"blah": "blah"
}
},
{
"short_id": "2q56r",
"path": "/2p45q/",
"name": {
"en-US": "Automotive"
}
}
];
// map paths to objects (one iteration)
var path_to_obj = {};
objs.forEach(function(obj){
path_to_obj[obj.path] = obj;
});
// add objects to the sub array of their parent (one iteration)
objs.forEach(function(obj){
var parentpath = obj.path.replace(/[^\/]*\/$/, '');
var parent = path_to_obj[parentpath];
if(parent){
parent.sub = parent.sub || [];
parent.sub.push(obj);
}
});
var pre = document.createElement('pre');
pre.innerHTML = 'Result:\n' + JSON.stringify(path_to_obj['/'], null, 4);
document.body.appendChild(pre);

Related

Grouping by fields after reduce is not working in JavaScript

There is a complex object and based on an array which is given as an input I need to modify its properties. Illustration is shown below. If the "field" is same , add them to "or" array .If its different "field" add them to "and" array along with its "value". I am using Set to get keys from both source and input and using them to group based on its keys. Also whenever there are duplicates .ie., suppose the "filterObj" already has the same (field, value) pair. Be it in "and" or inside "or",Then don't add it in the final object
Sandbox: https://codesandbox.io/s/optimistic-mirzakhani-pogpw-so-dpvis
There is a TestCases file in the sandbox which its needs to pass
let filterObj = {
feature: "test",
filter: {
and: [{ field: "field2" }]
}
};
let obj = [{ field: "field2", value: "3" }];
let all_filters = [];
if (filterObj.filter.and && filterObj.filter.and.hasOwnProperty("or")) {
all_filters = [...filterObj.filter.and.or];
} else if (filterObj.filter.and) {
all_filters = [...filterObj.filter.and];
}
const all_objs = [...obj, ...all_filters];
const uniqKeys = all_objs.reduce(
(acc, curr) => [...new Set([...acc, curr.field])],
[]
);
const updateItems = uniqKeys.map(obj => {
const filter_items = all_objs.filter(item => item.field === obj);
let resultObj = {};
if (filter_items && filter_items.length > 1) {
resultObj.or = [...filter_items];
} else if (filter_items && filter_items.length === 1) {
resultObj = { ...filter_items[0] };
}
return resultObj;
});
var result = { ...filterObj, filter: { and: [...updateItems] } };
console.log(result);
Try it.
I redid the implementation, it happened more universally.
Parses any filters according to your algorithm that it finds.
All test cases are worked.
Sandbox link: https://codesandbox.io/s/optimistic-mirzakhani-pogpw-so-i1u6h
let filterObj = {
feature: "test",
filter: {
and: [
{
field: "field1",
value: "2"
}
]
}
};
let obj = [
{
field: "field1",
value: "2"
},
{
field: "field1",
value: "1"
}
];
var FilterController = function(filter) {
var self = this;
self.filter = filter;
// encapsulated map of objects by fields
var storeMap = {};
// counter of objects
var counter = 0;
var tryPutObjectToMap = function(object) {
if (typeof object === "object") {
// get type for grouping
var objectType = self.getObjectGroupType(object);
if (objectType !== null) {
// cheack have group
if (!storeMap.hasOwnProperty(objectType)) {
storeMap[objectType] = [];
}
var duplicate = storeMap[objectType].find(function(sObject) {
return self.getObjectValue(sObject) === self.getObjectValue(object);
});
// check duplicate
if (duplicate === undefined) {
counter++;
storeMap[objectType].push(object);
} else {
// TODO: Handle duplicates
}
} else {
// TODO: handle incorrect object
}
}
};
// get filter structure from map
var getFilterStructureFromMap = function() {
var result = {};
// check exists root filter and filed if have objects
if (counter > 0) {
result["and"] = [];
}
for (var key in storeMap) {
if (storeMap.hasOwnProperty(key)) {
var array = storeMap[key];
if (array.length > 1) {
result["and"].push({
// clone array
or: array.slice()
});
} else {
result["and"].push(array[0]);
}
}
}
return result;
};
// rewrite and get current filter
// if you need^ create new object for result
self.rewriteAndGetFilter = function() {
self.filter.filter = getFilterStructureFromMap();
return self.filter;
};
// not prototype function for have access to storeMap
self.putObjects = function(objects) {
if (Array.isArray(objects)) {
// recursive push array elements
objects.forEach(element => self.putObjects(element));
// handle array
} else if (typeof objects === "object") {
// handle object
if (objects.hasOwnProperty("and") || objects.hasOwnProperty("or")) {
for (var key in objects) {
//no matter `or` or `and` the same grouping by field
// inner object field
if (objects.hasOwnProperty(key)) {
self.putObjects(objects[key]);
}
}
} else {
// filters props not found, try push to store map
tryPutObjectToMap(objects);
}
} else {
// TODO: Handle errors
}
};
if (self.filter.hasOwnProperty("filter")) {
// put and parse current objects from filter
self.putObjects(self.filter.filter);
}
};
// function for grouping objects.
// for you get filed name from object.
// change if need other ways to compare objects.
FilterController.prototype.getObjectGroupType = function(obj) {
if (typeof obj === "object" && obj.hasOwnProperty("field")) {
return obj.field;
}
return null;
};
// get object value
FilterController.prototype.getObjectValue = function(obj) {
if (typeof obj === "object" && obj.hasOwnProperty("value")) {
return obj.value;
}
return null;
};
var ctrl = new FilterController(filterObj);
ctrl.putObjects(obj);
var totalFilter = ctrl.rewriteAndGetFilter();
console.log(totalFilter);
console.log(JSON.stringify(totalFilter));
EDIT 1
I did not change the logic; I made a function based on it.
let filterObj = {
feature: "test",
filter: {
and: [
{
field: "field1",
value: "2"
}
]
}
};
let obj = [
{
field: "field1",
value: 2
},
{
field: "field1",
value: "1"
}
];
function appendToFilter(filter, inputObjects) {
var storeMap = {};
var counter = 0;
var handlingQueue = [];
// if filter isset the appen to handling queue
if (filter.hasOwnProperty("filter")) {
handlingQueue.push(filter.filter);
}
// append other object to queue
handlingQueue.push(inputObjects);
// get first and remove from queue
var currentObject = handlingQueue.shift();
while (currentObject !== undefined) {
if (Array.isArray(currentObject)) {
currentObject.forEach(element => handlingQueue.push(element));
} else if (typeof currentObject === "object") {
if (currentObject.hasOwnProperty("and") || currentObject.hasOwnProperty("or")) {
for (var key in currentObject) {
if (currentObject.hasOwnProperty(key)) {
handlingQueue.push(currentObject[key]);
}
}
} else {
// TODO: append fild exists check
if (currentObject.field) {
if (!storeMap.hasOwnProperty(currentObject.field)) {
storeMap[currentObject.field] = [];
}
var localValue = currentObject.value;
// check duplicate
if (storeMap[currentObject.field].find(object => object.value === localValue) === undefined) {
counter++;
storeMap[currentObject.field].push(currentObject);
}
}
}
}
currentObject = handlingQueue.shift();
}
// create new filter settings
var newFilter = {};
// check exists root filter and filed if have objects
if (counter > 0) { newFilter["and"] = []; }
for (var storeKey in storeMap) {
if (storeMap.hasOwnProperty(storeKey)) {
var array = storeMap[storeKey];
if (array.length > 1) {
newFilter["and"].push({
// clone array
or: array.slice()
});
} else {
newFilter["and"].push(array[0]);
}
}
}
filter.filter = newFilter;
}
// update filterObj
appendToFilter(filterObj, obj);
console.log(filterObj);
EDIT 2,3 (UPDATED)
With others objects support.
export function appendToFilter(filter, inputObjects) {
var storeMap = {};
var others = [];
var counter = 0;
var handlingQueue = [];
// if filter isset the appen to handling queue
if (filter.hasOwnProperty("filter") && filter.filter.hasOwnProperty("and")) {
handlingQueue.push(filter.filter.and);
}
// append other object to queue
handlingQueue.push(inputObjects);
// get first and remove from queue
var currentObject = handlingQueue.shift();
while (currentObject !== undefined) {
if (Array.isArray(currentObject)) {
currentObject.forEach(element => handlingQueue.push(element));
} else if (typeof currentObject === "object") {
if (
currentObject.hasOwnProperty("and") ||
currentObject.hasOwnProperty("or")
) {
for (var key in currentObject) {
if (currentObject.hasOwnProperty(key)) {
handlingQueue.push(currentObject[key]);
}
}
} else {
// TODO: append fild exists check
if (currentObject.field) {
if (!storeMap.hasOwnProperty(currentObject.field)) {
storeMap[currentObject.field] = [];
}
var localValue = currentObject.value;
// check duplicate
if (
storeMap[currentObject.field].find(
object => object.value === localValue
) === undefined
) {
counter++;
storeMap[currentObject.field].push(currentObject);
}
} else {
// handle others objects^ without field "field"
counter++;
others.push(currentObject);
}
}
}
currentObject = handlingQueue.shift();
}
// create new filter settings
var newFilter = {};
// check exists root filter and filed if have objects
if (counter > 0) {
newFilter["and"] = [];
}
for (var storeKey in storeMap) {
if (storeMap.hasOwnProperty(storeKey)) {
var array = storeMap[storeKey];
if (array.length > 1) {
newFilter["and"].push({
// clone array
or: array.slice()
});
} else {
newFilter["and"].push(array[0]);
}
}
}
// Append others to result filter
others.forEach(other => newFilter["and"].push(other));
filter.filter = newFilter;
}

Appending HTML elements on a loop

Given a JSON structure like this one:
[
"Hi, ",
{
"tag": "a",
"attr": {
"href": "https://example.com",
"target": "_blank"
},
"body": [
"click ",
{
"tag": "strong",
"body": [
"here "
]
}
]
},
"to get ",
{
"tag": "em",
"body": [
"amazing "
]
},
"offers."
]
I am trying to iterate over it to convert the values into HTML tags. With the above JSON I was hoping to construct this:
<span>Hi, </span>click <strong>here</strong>to get <em>amazing </em><span>offers.</span>
So I'm passing this JSON into a recursive function like so:
stringHtmlText(content) {
let result = content.map(tranche => {
if (typeof tranche === "object") {
let attrs = [];
for (let attr in tranche.attr) {
if (tranche.attr.hasOwnProperty(attr)) {
let thisAttr = {};
thisAttr[attr] = tranche.attr[attr];
attrs.push(thisAttr);
}
}
return tranche.body.map(entry => {
if (typeof entry === "object") {
let childNode = this.stringHtmlText([entry]);
if(Array.isArray(childNode)) {
childNode = childNode[0];
}
let parentNode = this.buildElement(tranche.tag, attrs, '');
//THIS IS THE OFFENDING LINE
parentNode.appendChild(childNode);
return parentNode;
} else {
return this.buildElement(tranche.tag, attrs, entry);
}
})[0];
} else {
return this.buildElement('span', [], tranche);
}
});
return result;
}
Where buildElement is a convenience method that creates the nodes, sets attributes and appends any text nodes:
buildElement(tag, attributes, value = '') {
let node = document.createElement(tag);
if (value) {
let text = document.createTextNode(value);
node.appendChild(text);
}
if (attributes.length === 0) {
return node;
}
return this.setAttributes(node, attributes);
}
The issue I am running into is that even though when debugging I see the "strong" node being passed to parentNode.appendChild(childNode), when the value is returned the parentNode "a" tag has no child "strong", giving me a result like this:
<span>Hi, </span>click <span>to get </span><em>amazing </em><span>offers.</span>
Which is obviously lacking the "strong" tag inside the "a" tag. Why is the node not being appended to the parent?
Seems like the issue was that the second mapping function was actually generating two nodes in the cases where there was both plain text and an additional node in the body.
Since the first iteration only contains the text node, the way to get the second, complete iteration was to pass the results of the mapping to a variable and then return the last index in the array, like so:
stringHtmlText(content) {
{
return content.map(tranche => {
if (typeof tranche === "object") {
let attrs = [];
for (let attr in tranche.attr) {
if (tranche.attr.hasOwnProperty(attr)) {
let thisAttr = {};
thisAttr[attr] = tranche.attr[attr];
attrs.push(thisAttr);
}
}
let parentNode;
//Assign to variable
let trancher = tranche.body.map(entry => {
if (typeof entry === "object") {
let childNode = this.stringHtmlText([entry]);
if (Array.isArray(childNode)) {
childNode = childNode[0];
}
parentNode.appendChild(childNode);
return parentNode;
} else {
parentNode = this.buildElement(tranche.tag, attrs, entry);
return parentNode;
}
});
// Return only the last, complete node
return trancher[(trancher.length - 1)]
} else {
return this.buildElement('span', [], tranche);
}
});
}
}

How can I get some value of a deep-nested object?

There are two types of arrays which I have to build dynamically.
data['fields']['title']
and
data['fields']['description']['html']
it returns the content of this structure:
{
"fields": {
"title": "Headline",
"description": {
"html": "<p>description text</p>"
}
},
"meta": {
"id": "995915463198380032"
}
}
The problem is "dynamically".
I call a function and give the path through it like "description>html".
I split the string into "description" and "html".
But how do I build now the array: data['fields']['description']['html']
Sometimes there is a level more or less like "title".
If I want to call title, the array is like data['fields']['title']
So the content and the number of parts in the array are dynamic.
I tried by myself this:
function comfort_x(item_fields) {
var splitter = item_fields.split(">");
var content = new Array();
for (var i = 1; i < splitter.length; ++i) {
content['splitter['+i+']'] = splitter[i];
}
data['fields'][splitter[0]][splitter[1]];
}
Thank you for your help.
You can create a function that will look up to the level you are passing. You can just split your path by > and reduce that array with the source input.
(data, path) => path.split(">").reduce((r, e) => r[e], data);
Here is a example.
var obj = {
"fields": {
"title": "Headline",
"description": {
"html": "<p>description text</p>"
}
},
"meta": {
"id": "995915463198380032"
}
}
var lookUp = (o, path) => path.split(">").reduce((r, e) => r[e], o);
console.log('fields: ', lookUp(obj, 'fields'))
console.log('fields>title: ', lookUp(obj, 'fields>title'))
console.log('fields>description>html: ', lookUp(obj, 'fields>description>html'))
It works with this addinitional work-around:
switch (splitter.length) {
case 0:
item.innerHTML = data['fields'];
break;
case 1:
item.innerHTML = data['fields'][splitter[0]];
break;
case 2:
item.innerHTML = data['fields'][splitter[0]][splitter[1]];
break;
case 3:
item.innerHTML = data['fields'][splitter[0]][splitter[1]][splitter[2]];
}
maybe you have a smarter solution.
/*
Be careful! The function won’t return a new object,
but take a reference to the input object aka. alter
it and don’t deep-clone it.
*/
const myObject = {};
function setValueByKeypath(object, path, value, separator = ">") {
path = path.split(separator);
let currentPart = object;
for (let i = 0, len = path.length; i < len; i++) {
const key = path[i];
const keyValue = (i === len - 1) ? value : {};
if (typeof currentPart[key] === "undefined") {
currentPart[key] = keyValue;
}
currentPart = currentPart[key];
}
}
setValueByKeypath(myObject, "fields>description>html", "<p>description text</p>");
console.log(myObject);
setValueByKeypath(myObject, "fields>title", "Headline");
console.log(myObject);
setValueByKeypath(myObject, "meta>id", "995915463198380032");
console.log(myObject);

How to restore circular references (e.g. "$id") from Json.NET-serialized JSON?

Is there an existing javascript library which will deserialize Json.Net with reference loop handling?
{
"$id": "1",
"AppViewColumns": [
{
"$id": "2",
"AppView": {"$ref":"1"},
"ColumnID": 1,
}
]
}
this should deserialize to an object with a reference loop between the object in the array and the outer object
The answers given almost worked for me, but the latest version of MVC, JSON.Net, and DNX uses "$ref" and "$id", and they may be out of order. So I've modified user2864740's answer.
I should note that this code does not handle array references, which are also possible.
function RestoreJsonNetReferences(g) {
var ids = {};
function getIds(s) {
// we care naught about primitives
if (s === null || typeof s !== "object") { return s; }
var id = s['$id'];
if (typeof id != "undefined") {
delete s['$id'];
// either return previously known object, or
// remember this object linking for later
if (ids[id]) {
throw "Duplicate ID " + id + "found.";
}
ids[id] = s;
}
// then, recursively for each key/index, relink the sub-graph
if (s.hasOwnProperty('length')) {
// array or array-like; a different guard may be more appropriate
for (var i = 0; i < s.length; i++) {
getIds(s[i]);
}
} else {
// other objects
for (var p in s) {
if (s.hasOwnProperty(p)) {
getIds(s[p]);
}
}
}
}
function relink(s) {
// we care naught about primitives
if (s === null || typeof s !== "object") { return s; }
var id = s['$ref'];
delete s['$ref'];
// either return previously known object, or
// remember this object linking for later
if (typeof id != "undefined") {
return ids[id];
}
// then, recursively for each key/index, relink the sub-graph
if (s.hasOwnProperty('length')) {
// array or array-like; a different guard may be more appropriate
for (var i = 0; i < s.length; i++) {
s[i] = relink(s[i]);
}
} else {
// other objects
for (var p in s) {
if (s.hasOwnProperty(p)) {
s[p] = relink(s[p]);
}
}
}
return s;
}
getIds(g);
return relink(g);
}
I'm not aware of existing libraries with such support, but one could use the standard JSON.parse method and then manually walk the result restoring the circular references - it'd just be a simple store/lookup based on the $id property. (A similar approach can be used for reversing the process.)
Here is some sample code that uses such an approach. This code assumes the JSON has already been parsed to the relevant JS object graph - it also modifies the supplied data. YMMV.
function restoreJsonNetCR(g) {
var ids = {};
function relink (s) {
// we care naught about primitives
if (s === null || typeof s !== "object") { return s; }
var id = s['$id'];
delete s['$id'];
// either return previously known object, or
// remember this object linking for later
if (ids[id]) {
return ids[id];
}
ids[id] = s;
// then, recursively for each key/index, relink the sub-graph
if (s.hasOwnProperty('length')) {
// array or array-like; a different guard may be more appropriate
for (var i = 0; i < s.length; i++) {
s[i] = relink(s[i]);
}
} else {
// other objects
for (var p in s) {
if (s.hasOwnProperty(p)) {
s[p] = relink(s[p]);
}
}
}
return s;
}
return relink(g);
}
And the usage
var d = {
"$id": "1",
"AppViewColumns": [
{
"$id": "2",
"AppView": {"$id":"1"},
"ColumnID": 1,
}
]
};
d = restoreJsonNetCR(d);
// the following works well in Chrome, YMMV in other developer tools
console.log(d);
DrSammyD created an underscore plugin variant with round-trip support.
Ok so I created a more robust method which will use $id as well as $ref, because that's actually how json.net handles circular references. Also you have to get your references after the id has been registered otherwise it won't find the object that's been referenced, so I also have to hold the objects that are requesting the reference, along with the property they want to set and the id they are requesting.
This is heavily lodash/underscore based
(function (factory) {
'use strict';
if (typeof define === 'function' && define.amd) {
define(['lodash'], factory);
} else {
factory(_);
}
})(function (_) {
var opts = {
refProp: '$ref',
idProp: '$id',
clone: true
};
_.mixin({
relink: function (obj, optsParam) {
var options = optsParam !== undefined ? optsParam : {};
_.defaults(options, _.relink.prototype.opts);
obj = options.clone ? _.clone(obj, true) : obj;
var ids = {};
var refs = [];
function rl(s) {
// we care naught about primitives
if (!_.isObject(s)) {
return s;
}
if (s[options.refProp]) {
return null;
}
if (s[options.idProp] === 0 || s[options.idProp]) {
ids[s[options.idProp]] = s;
}
delete s[options.idProp];
_(s).pairs().each(function (pair) {
if (pair[1]) {
s[pair[0]] = rl(pair[1]);
if (s[pair[0]] === null) {
if (pair[1][options.refProp] !== undefined) {
refs.push({ 'parent': s, 'prop': pair[0], 'ref': pair[1][options.refProp] });
}
}
}
});
return s;
}
var partialLink = rl(obj);
_(refs).each(function (recordedRef) {
recordedRef['parent'][recordedRef['prop']] = ids[recordedRef['ref']] || {};
});
return partialLink;
},
resolve: function (obj, optsParam) {
var options = optsParam !== undefined ? optsParam : {};
_.defaults(options, _.resolve.prototype.opts);
obj = options.clone ? _.clone(obj, true) : obj;
var objs = [{}];
function rs(s) {
// we care naught about primitives
if (!_.isObject(s)) {
return s;
}
var replacementObj = {};
if (objs.indexOf(s) != -1) {
replacementObj[options.refProp] = objs.indexOf(s);
return replacementObj;
}
objs.push(s);
s[options.idProp] = objs.indexOf(s);
_(s).pairs().each(function (pair) {
s[pair[0]] = rs(pair[1]);
});
return s;
}
return rs(obj);
}
});
_(_.resolve.prototype).assign({ opts: opts });
_(_.relink.prototype).assign({ opts: opts });
});
I created a gist here

Adding a value to a jquery tree object

Suppose I have an object like that.
var t = {
"obj1": {
"obj1.1": "test",
"obj1.2": {
"obj1.1.1": null, // <-- "obj1.1.1" has to have a value.
"obj1.1.2": "test"
}
}
};
And a path to the node where I'd like to add a value, i.e.:
var path = ['obj1', 'obj1.1', 'test'];
How do I add the value programmatically?
Try this:
function setDeepValue(obj, value, path) {
if(path.length > 1){
var p=path.shift();
if(obj[p]==null || typeof obj[p]!== 'object'){
obj[p] = {};
}
setDeepValue(obj[p], value, path);
}else{
obj[path[0]] = value;
}
}
var obj = {};
var path = ['obj1', 'obj1.1'];
setDeepValue(obj, 'test', path);
console.log(obj); // {"obj1":{"obj1.1":"test"}}
You will however need to fix your object:
var t = {
"obj1": {
"obj1.1": "test",
"obj1.2": {
"obj1.1.1": null, // <-- "obj1.1.1" has to have a value.
"obj1.1.2": "test"
}
}
};
A object can't have a key without a value, so "obj1.1.1" will have to have a value, even if it's null.
Supposing this object
var obj = {
"obj1" : {
"obj1.1" : "",
"obj1.2" : {
"obj1.1.1" : "",
"obj1.1.2" : ""
}
}
}
var path = ['obj1', 'obj1.1', 'test'];
the algorithm is:
var el = obj;
for(var i = 0; i < path.length-2; i++) {
el = el[path[i]];
}
el[path[i]] = path[i+1];
or as function
function setpath(obj,path) {
var el = obj;
for(var i = 0; i < path.length-2; i++) {
console.log(i+"="+path[i]);
el = el[path[i]];
}
el[path[i]] = path[i+1];
console.log(el);
}
setpath(obj,['obj1', 'obj1.1', 'test']);
var tree = {
"obj1": {
"obj1.1": "test",
"obj1.2": {
"obj1.1.1": null,
"obj1.1.2": "test"
}
}
};
var path = ['obj1', 'obj1.1', 'test'];
A static way of setting 'test':
tree[path[0]][path[1]] = path[2];
A static way of replacing 'test' with newObj:
var newObj = { ... };
tree[path[0]][path[1]][path[2]] = newObj;

Categories