Creating complex objects dynamically in javascript - javascript

Is it possible to create complex objects at runtime in javascript ? If so, what is the correct syntax ?
var food = {};
food["fruit"]["yellow"] = "banana";
food["meat"]["red"] = "steak";
food."fruit"."green" = "apple";

It's not clear what you're trying to do. If you want to build that object up all at once, then you could do something like:
var food = {
fruit: {
yellow: 'banana',
green: 'apple'
},
meat: {
red: 'steak'
}
};
If you need to piece it together one nested object at a time, then you just need to make sure that you are creating a new object to add properties to.
For example, your line:
food["fruit"]["yellow"] = "banana";
will probably fail because food.fruit does not exist.
You should do:
var food = {};
food.fruit = {};
food.fruit.yellow = 'banana';

You could write a function to add data to your object.
e.g.
function addEntry(obj, entry) {
if(entry.length < 2) return;
if(entry.length === 2) obj[entry[0]] = entry[1];
else {
if(!obj[entry[0]] || typeof obj[entry[0]] !== "object") obj[entry[0]] = {};
addEntry(obj[entry[0]], entry.slice(1));
}
}
var data = [
["fruit", "yellow", "banana"],
["meat", "red", "steak"],
["fruit", "green", "apple"]
];
var obj = {};
for(var i = 0; i < data.length; i++) {
addEntry(obj, data[i]);
}
console.log(obj);

Related

How to convert an array into a hierarchical array

I have some data which is
var currentData = [
{'ticket':'CAP', 'child':'CT-1'},
{'ticket':'CAP', 'child':'CT-2'},
{'ticket':'CT-1', 'child':'CT-1-A'},
{'ticket':'CT-1', 'child':'CT-1-B'}
];
The data is flat and I need to convert it into something like:
{
'ticket': 'CAP',
children : [{
'ticket' : 'CT-1',
'children' : [{
'ticket' : 'CT-1-A',
'children' : []
}, {
'ticket' : 'CT-1-B',
'children' : []
}],
[{
'ticket' : 'CT-2',
'children' : []
}]
}]
}
(I think the above is valid)?
I'm very lost as to how. I am going to show my effort but, I'm not sure if my approach is correct or not.
var currentData = [{'ticket':'cap', 'child':'CT-1'},{'ticket':'cap', 'child':'CT-2'}, {'ticket':'CT-1', 'child':'CT-1-A'},{'ticket':'CT-1', 'child':'CT-1-B'}];
var newList = [];
function convert(list){
if (newList.length <= 0){
var child = [];
var emptyChild = [];
child.push({'ticket': list[0].child, 'child': emptyChild });
newList.push({'ticket': list[0].ticket, 'children' : child});
list.splice(0,1);
} // the if statement above works fine
for(var i = 0; i < list.length; i++) {
var ticket = list[i].ticket;
for(var j = 0; j < newList.length; j++) {
if (newList[j].ticket == ticket){
var child;
var emptyChild = [];
child = {'ticket': list[i].child, 'child': emptyChild };
newList[j].children.push(child);
list.splice(i,1);
break;
} // the if above works
else{
var child2 = getFromChildren(ticket, newList, list[i]); // child2 is Always null, even if getFromChildren returns an object
newList[j].children.push(child2);
list.splice(i,1);
break;
}
}
}
if (list.length > 0){
convert(list);
}
}
function getFromChildren(ticket, list, itemToAdd){
if (list == null || list[0].children == null)
return;
for(var i = 0; i < list.length; i++) {
if (list[i] == null)
return;
if (list[i].ticket == ticket){
list[i].child.push(itemToAdd.child); // ** can't do this, javascript passes by value, not by reference :(
} else{
getFromChildren(ticket, list[i].children, itemToAdd);
}
}
}
convert(currentData);
I think I've made a mess of it. In the comments I've put a ** explaining that it isn't working due to JavaScript not passing by reference, however upon further reading I don't think that is correct as I'm passing the object which is by reference?
Edit
The data, shown with currentData will not always start at the root sadly either
function convert(arr) {
var children = {}; // this object will hold a reference to all children arrays
var res = arr.reduce(function(res, o) { // for each object o in the array arr
if(!res[o.ticket]) { // if there is no object for the element o.ticket
res[o.ticket] = {ticket: o.ticket, children: []}; // then creates an object for it
children[o.ticket] = res[o.ticket].children; // and store a reference to its children array
}
if(!res[o.child]) { // if there is no object for the element o.child
res[o.child] = {ticket: o.child, children: []}; // then creates an object for it
children[o.child] = res[o.child].children; // and store a reference to its children array
}
return res;
}, {});
arr.forEach(function(o) { // now for each object o in the array arr
children[o.ticket].push(res[o.child]); // add the object of o.child (from res) to its children array
delete res[o.child]; // and remove the child object from the object res
});
return res;
}
var currentData = [
{'ticket':'CAP', 'child':'CT-1'},
{'ticket':'CAP', 'child':'CT-2'},
{'ticket':'CT-1', 'child':'CT-1-A'},
{'ticket':'CT-1', 'child':'CT-1-B'}
];
console.log(convert(currentData));
Explanation:
The reduce part creates an object of the form: { ticket: "...", children: [] } for each element (child or not). So right after reduce, the object res will be:
res = {
'CAP': { ticket: 'CAP', children: [] },
'CT-1': { ticket: 'CT-1', children: [] },
'CT-2': { ticket: 'CT-2', children: [] },
'CT-1-A': { ticket: 'CT-1-A', children: [] },
'CT-1-B': { ticket: 'CT-1-B', children: [] },
}
Now comes the forEach bit which loops over the array once more, and now for each object it fetches the object of .child from res above, push it into .ticket object's children (which a reference to it is stored in children object), then remove the .child object from the object res.
Below uses reduce to get the data grouped to a Map, then I convert the data to an object like you've shown above. You'll need a modern browser to run below snippet, or use a transpiler like babeljs to convert it to es5 syntax.
let currentData = [
{'ticket':'CAP', 'child':'CT-1'},
{'ticket':'CAP', 'child':'CT-2'},
{'ticket':'CT-1', 'child':'CT-1-A'},
{'ticket':'CT-1', 'child':'CT-1-B'}
];
let children = currentData.map(e => e.child);
currentData.sort((a,b) => children.indexOf(a.ticket));
let res = currentData.reduce((a,b) => {
if (! children.includes(b.ticket)) {
return a.set(b.ticket, (a.get(b.ticket) || [])
.concat({ticket: b.child,
children: currentData
.filter(el => el.ticket === b.child)
.map(el => ({ticket: el.child, children: []}))}))
}
return a;
}, new Map);
let r = {};
for (let [key,value] of res.entries()) {
r.ticket = key;
r.children = value;
}
console.log(r);
Solution using recursion, starting node can be changed.
var currentData = [{'ticket': 'cap','child': 'CT-1'}, {'ticket': 'cap','child': 'CT-2'}, {'ticket': 'CT-1','child': 'CT-1-A'}, {'ticket': 'CT-1','child': 'CT-1-B'}];
function convert(data, start){
return {
ticket: start,
childs: data.filter(d => d.ticket == start)
.reduce((curr, next) => curr.concat([next.child]), [])
.map(c => convert(data, c))
}
}
let result = convert(currentData, 'cap');
console.log(result);
.as-console-wrapper{top: 0; max-height: none!important;}
I would go with a simple for approach, like this:
var currentData = [
{'ticket':'CAP', 'child':'CT-1'},
{'ticket':'CAP', 'child':'CT-2'},
{'ticket':'CT-1', 'child':'CT-1-A'},
{'ticket':'CT-1', 'child':'CT-1-B'}
];
var leafs = {};
var roots = {};
var tickets = {};
for(var i=0; i<currentData.length; i++){
var ticket = currentData[i].ticket;
var child = currentData[i].child;
if(!tickets[ticket]){
tickets[ticket] = {ticket:ticket,children:[]};
if(!leafs[ticket]){
roots[ticket] = true;
}
}
if(!tickets[child]){
tickets[child] = {ticket:child,children:[]};
}
delete roots[child];
leafs[child] = true;
tickets[ticket].children.push(tickets[child]);
}
for(var ticket in roots){
console.log(tickets[ticket]);
}
Well, if you are not familiar with reduce, map , forEach with callbacks to iterate, then here is a approach I came with, where the code is flat, storing object references in another map object and iterating exactly once the source array.
The code is much cleaner, if something is understandable add comments I will explain;
var currentData = [
{'ticket':'CT-1', 'child':'CT-1-A'},
{'ticket':'CT-1', 'child':'CT-1-B'},
{'ticket':'CAP', 'child':'CT-1'},
{'ticket':'CAP', 'child':'CT-2'}
];
function buildHierarchy(flatArr) {
let root = {},
nonRoot = {},
tempMap = {};
Object.setPrototypeOf(root, nonRoot);
for (let idx = 0; idx < flatArr.length; idx++) {
let currTicket = flatArr[idx];
let tempTicket = tempMap[currTicket.ticket] || {ticket: currTicket.ticket, children: []};
tempMap[currTicket.ticket] = tempTicket;
if (currTicket.child) {
let tempChild = tempMap[currTicket.child] || {ticket: currTicket.child, children: []};
tempTicket.children.push(tempChild);
tempMap[currTicket.child] = tempChild;
delete root[tempChild.ticket];
nonRoot[tempChild.ticket] = true;
}
root[tempTicket.ticket] = true;
}
return tempMap[Object.keys(root)[0]];
}
console.log(buildHierarchy(currentData));
I have changed the sequence of your source array in order to put the root object anywhere, and the code should work on that.

How to build nested properties from key strings

var keys1 = ["foo", "moreFoo"],
value1 = "bar",
keys2 = ["foo", "ultraFoo"],
value2 = "bigBar";
I'd like to make a function which would build me an object :
object {
foo : {moreFoo: "bar", ultraFoo: "bigBar"}
}
I thought of taking each one of my arrays and doing the following :
function recursiveObjectBuild(object, keys, value) {
var index = 0;
function loop(object, index) {
var key = keys[index];
//Property exists, go into it
if (key in object) {
loop(object[key], ++index);
//Property doesn't exist, create it and go into it
} else if (index < keys.length-1) {
object[key] = {};
loop(object[key], ++index);
//At last key, set value
} else {
object[key] = value;
return object;
}
}
return loop(object, 0);
}
Which should work IMO but doesn't (infinite loop, must be a stupid mistake but can't see it).
And I'm sure there must be a much simpler way
Try the following:
function objectBuild(object, keys, value) {
for (var i = 0; i < keys.length-1; i++) {
if (!object.hasOwnProperty(keys[i]))
object[keys[i]] = {};
object = object[keys[i]];
}
object[keys[keys.length-1]] = value;
}
Example usage (see it in action):
var object = {};
objectBuild(object, ["foo", "moreFoo"], "bar");
objectBuild(object, ["foo", "ultraFoo"], "bigBar");
// object --> {foo: {moreFoo: "bar", ultraFoo: "bigBar}}

Setting up a variable length two-dimensional array

I have a string as follows :
Panther^Pink,Green,Yellow|Dog^Hot,Top
This string means I have 2 main blocks(separated by a '|') :
"Panther" and "Dog"
Under these two main blocks, I have, lets say "subcategories".
I wanted to create a 2-dimensional array represented (in logic) as follows :
Panther(Array 1) => Pink(Element 1),Green(Element 2), Yellow(Element 3)
Dog(Array 2) => Hot(Element 1), Top(Element 2)
Also,I want to be able to add a main block, lets say "Cat" with possible categories "Cute,Proud" to the two dimensional array
I've managed to get an Array containing "Panther^Pink,Green,Yellow" and "Dog^Hot,Top" by using JavaScript's split function.
Note that this string is received via Ajax and can be of any length, though the format shown above is always used.
----------------------------- EDIT ----------------------------
Ok, my script so far is :
$(document).ready(function(){
appFunc.setNoOfAppBlock('Panther^Pink,Green,Yellow|Dog^Hot,Top');
appFunc.alertPing();
});
var appFunc = (function(stringWithSeper) {
var result = {},
i,
categories = new Array(),
subcategories;
return {
setNoOfAppBlock: function(stringWithSeper){
categories = stringWithSeper.split("|");
for (i = 0; i < categories.length; i++) {
subcategories = categories[i].split("^");
result[subcategories[0]] = subcategories[1].split(",");
}
},
alertPing: function(){
alert(result["Panther"][1]);
}
};
})();
However, the function "alertPing" isn't "alerting" anything.What am am I doing wrong ?
To me the most logical representation of your data:
Panther^Pink,Green,Yellow|Dog^Hot,Top
Is with a JavaScript object with a property for each category, each of which is an array with the subcategories:
var data = {
Panther : ["Pink", "Green", "Yellow"],
Dog : ["Hot", "Top"]
}
You would then access that by saying, e.g., data["Dog"][1] (gives "Top").
If that format is acceptable to you then you could parse it as follows:
function parseData(data) {
var result = {},
i,
categories = data.split("|"),
subcategories;
for (i = 0; i < categories.length; i++) {
subcategories = categories[i].split("^");
result[subcategories[0]] = subcategories[1].split(",");
}
return result;
}
var str = "Panther^Pink,Green,Yellow|Dog^Hot,Top";
var data = parseData(str);
Assuming you're trying to parse your data into something like this:
var result = {
Panther: ["Pink", "Green", "Yellow"],
Dog: ["Hot", "Top"]
}
you can use string.split() to break up your string into subarrays:
var str = "Panther^Pink,Green,Yellow|Dog^Hot,Top";
var result = {}, temp;
var blocks = str.split("|");
for (var i = 0; i < blocks.length; i++) {
temp = blocks[i].split("^");
result[temp[0]] = temp[1].split(",");
}
Data can then be added to that data structure like this:
result["Cat"] = ["Cute", "Proud"];
Data can be read from that data structure like this:
var dogItems = result["Dog"]; // gives you an array ["Hot", "Top"]
You can use something like:
function parseInput(_input) {
var output = [];
var parts = _input.split('|');
var part;
for(var i=0; i<parts.length; i++) {
part = parts[i].split('^');
output[part[0]] = part[1].split(',');
}
return output;
}
Calling parseInput('Panther^Pink,Green,Yellow|Dog^Hot,Top'); will return:
output [
"Panther" => [ "Pink", "Green", "Yellow" ],
"Dog" => [ "Hot", "Top" ]
]
To add another item to the list, you can use:
output["Cat"] = ["Cute", "Proud"];

Find duplicates without going through the list twice?

I need to know if one or more duplicates exist in a list. Is there a way to do this without travelling through the list more than once?
Thanks guys for the suggestions. I ended up using this because it was the simplest to implement:
var names = [];
var namesLen = names.length;
for (i=0; i<namesLen; i++) {
for (x=0; x<namesLen; x++) {
if (names[i] === names[x] && (i !== x)) {alert('dupe')}
}
}
Well the usual way to do that would be to put each item in a hashmap dictionary and you could check if it was already inserted. If your list is of objects they you would have to create your own hash function on the object as you would know what makes each one unique. Check out the answer to this question.
JavaScript Hashmap Equivalent
This method uses an object as a lookup table to keep track of how many and which dups were found. It then returns an object with each dup and the dup count.
function findDups(list) {
var uniques = {}, val;
var dups = {};
for (var i = 0, len = list.length; i < len; i++) {
val = list[i];
if (val in uniques) {
uniques[val]++;
dups[val] = uniques[val];
} else {
uniques[val] = 1;
}
}
return(dups);
}
var data = [1,2,3,4,5,2,3,2,6,8,9,9];
findDups(data); // returns {2: 3, 3: 2, 9: 2}
var data2 = [1,2,3,4,5,6,7,8,9];
findDups(data2); // returns {}
var data3 = [1,1,1,1,1,2,3,4];
findDups(data3); // returns {1: 5}
Since we now have ES6 available with the built-in Map object, here's a version of findDups() that uses the Map object:
function findDups(list) {
const uniques = new Set(); // set of items found
const dups = new Map(); // count of items that have dups
for (let val of list) {
if (uniques.has(val)) {
let cnt = dups.get(val) || 1;
dups.set(val, ++cnt);
} else {
uniques.add(val);
}
}
return dups;
}
var data = [1,2,3,4,5,2,3,2,6,8,9,9];
log(findDups(data)); // returns {2 => 3, 3 => 2, 9 => 2}
var data2 = [1,2,3,4,5,6,7,8,9];
log(findDups(data2)); // returns empty map
var data3 = [1,1,1,1,1,2,3,4];
log(findDups(data3)); // returns {1 => 5}
// display resulting Map object (only used for debugging display in snippet)
function log(map) {
let output = [];
for (let [key, value] of map) {
output.push(key + " => " + value);
}
let div = document.createElement("div");
div.innerHTML = "{" + output.join(", ") + "}";
document.body.appendChild(div);
}
If your strings are in an array (A) you can use A.some-
it will return true and quit as soon as it finds a duplicate,
or return false if it has checked them all without any duplicates.
has_duplicates= A.some(function(itm){
return A.indexOf(itm)===A.lastIndexOf(itm);
});
If your list was just words or phrases, you could put them into an associative array.
var list=new Array("foo", "bar", "foobar", "foo", "bar");
var newlist= new Array();
for(i in list){
if(newlist[list[i]])
newlist[list[i]]++;
else
newlist[list[i]]=1;
}
Your final array should look like this:
"foo"=>2, "bar"=>2, "foobar"=>1

Initializing a 'multidimensional' object in javascript

I'm having an issue with trying to populate a multidimensional object in javascript before all of the dimensions are defined.
For example this is what I want to do:
var multiVar = {};
var levelone = 'one';
var leveltwo = 'two';
multiVar[levelone][leveltwo]['levelthree'] = 'test'
It would be extremely cumbersome to have to create each dimension with a line like this:
var multiVar = {};
multiVar['levelone'] = {};
multiVar['levelone']['leveltwo'] = {};
multiVar['levelone']['leveltwo']['levelthree'] = 'test'
The reason why I need to do it without iterative priming is because I don't know how many dimensions there will be nor what the keys it will have. It needs to be dynamic.
Is there a way to do that in a dynamic way?
You could write a function which ensures the existence of the necessary "dimensions", but you won't be able to use dot or bracket notation to get this safety. Something like this:
function setPropertySafe(obj)
{
function isObject(o)
{
if (o === null) return false;
var type = typeof o;
return type === 'object' || type === 'function';
}
if (!isObject(obj)) return;
var prop;
for (var i=1; i < arguments.length-1; i++)
{
prop = arguments[i];
if (!isObject(obj[prop])) obj[prop] = {};
if (i < arguments.length-2) obj = obj[prop];
}
obj[prop] = arguments[i];
}
Example usage:
var multiVar = {};
setPropertySafe(multiVar, 'levelone', 'leveltwo', 'levelthree', 'test');
/*
multiVar = {
levelone: {
leveltwo: {
levelthree: "test"
}
}
}
*/

Categories

Resources