Trying to transform an object of objects:
var items: {
item_a: {
state: 'item_a status'
},
item_b: {
state: 'item_b status'
}
};
into an array of objects, whilst adding a new array element to the object (the object key):
var items = [{
name: 'item_a',
state: 'item_a status'
}, {
name: 'item_b',
state: 'item_b status'
}];
My naive attempt, which works, is thus:
var arrayOfItems = [];
for(var x in items){
var itemObj = {
name: x
};
for(var y in items[x]){
itemObj[y] = items[x][y];
}
arrayOfItems.push(itemObj);
}
I'm wondering if there's a cleaner way to do this, using maybe something Underscore/LoDash?
I would use map() for this:
_.map(items, function(item, key) {
return _.assign({ name: key }, item);
});
// →
// [
// {
// name: 'item_a',
// state: 'item_a status'
// },
// {
// name: 'item_b',
// state: 'item_b status'
// }
// ]
Since map() always returns an array, you're halfway there. You just need the callback to generate your array items. You can use the assign() function to setup your new name property, and then add the rest of the properties.
var newItems = _.map(items, function(item, key){
item.name = key;
return item;
});
console.log(newItems);
Related
code1:
let commap = new Map();
data.forEach(function(item,index){
if(commap.has(item.comp)){
let arr = companyset.get(item.comp);
arr.push(item);
commap.set(item.comp,arr);
}else{
commap.set(item.comp,[item]);
}
});
code2:
let commap = new Map();
data.forEach(function(item,index){
commap .set(item.comp,commap.has(item.comp)?commap.get(item.comp).push(item):[item]);
});
The logic of the two pieces of code is the same, if there is more than one entry for company,code2 throw an error:'Uncaught TypeError: commap.get(...).push is not a function',
why did this error occur,is it the order of js execution?
Thanks a lot
The problem is that .push returns the new length of the array, not the array itself. So, when you set the value to companyset.get(item.company).push(item), you're setting the value to a number, not an array, which means when you try to push to it later, an error is thrown.
Remember that arrays, being non-primitive, can be changed simply with a reference to the array, without having to re-assign it. You only have to set the first time you come across a new company. Try something like this instead:
const data = [
{ company: 'foo' },
{ company: 'bar' },
{ company: 'foo' },
{ company: 'foo' },
];
const companyset = new Map();
data.forEach(function(item) {
const { company } = item;
const arr = companyset.get(company);
if (!arr) return companyset.set(company, [item]);
arr.push(item);
});
console.log(companyset.get('foo'));
But to combine an array into an object, it would be more appropriate to use reduce instead of forEach:
const data = [
{ company: 'foo' },
{ company: 'bar' },
{ company: 'foo' },
{ company: 'foo' },
];
const companyset = data.reduce((map, item) => {
const { company } = item;
const arr = map.get(company);
if (!arr) map.set(company, [item]);
else arr.push(item);
return map;
}, new Map());
console.log(companyset.get('foo'))
This question already has answers here:
Group array items using object
(19 answers)
Closed 6 years ago.
I'm trying to group the raw data from:
items:
[
{
category: "blog",
id : "586ba9f3a36b129f1336ed38",
content : "foo, bar!"
},
{
category: "blog",
id : "586ba9f3a36b129f1336ed3c",
content : "hello, world!"
},
{
category: "music",
id : "586ba9a6dfjb129f1332ldab",
content : "wow, shamwow!"
},
]
to
[
{
category: "blog",
items:
[
{
id : "586ba9f3a36b129f1336ed38",
content : "foo, bar!"
},
{
id : "586ba9f3a36b129f1336ed3c",
content : "hello, world!"
},
]
},
{
category: "music",
items:
[
{
id : "586ba9a6dfjb129f1332ldab",
content : "wow, shamwow!"
}
]
}
]
The format like this helps me to print the same category data together in the frontend.
The content of the category field is dynamically, so I'm not sure how do I store it to a temporary object and sort them, any thoughts?
(I can't think a better title for the question, please edit if you got a better title.)
You can do it using Array#reduce in one pass:
var items = [{"category":"blog","id":"586ba9f3a36b129f1336ed38","content":"foo, bar!"},{"category":"blog","id":"586ba9f3a36b129f1336ed3c","content":"hello, world!"},{"category":"music","id":"586ba9a6dfjb129f1332ldab","content":"wow, shamwow!"}];
var result = items.reduce(function(r, item) {
var current = r.hash[item.category];
if(!current) {
current = r.hash[item.category] = {
category: item.category,
items: []
};
r.arr.push(current);
}
current.items.push({
id: item.id,
content: item.content
});
return r;
}, { hash: {}, arr: [] }).arr;
console.log(result);
Or the ES6 way using Map:
const items = [{"category":"blog","id":"586ba9f3a36b129f1336ed38","content":"foo, bar!"},{"category":"blog","id":"586ba9f3a36b129f1336ed3c","content":"hello, world!"},{"category":"music","id":"586ba9a6dfjb129f1332ldab","content":"wow, shamwow!"}];
const result = [...items.reduce((r, { category, id, content }) => {
r.has(category) || r.set(category, {
category,
items: []
});
r.get(category).items.push({ id, content });
return r;
}, new Map).values()];
console.log(result);
Personally, without any helper libraries, I'd just do this
var step1 = items.reduce((result, {category, id, content}) => {
result[category] = result[category] || [];
result[category].push({id, content});
return result;
}, {});
var result = Object.keys(step1).map(category => ({category, items: step1[category]}));
Which babel converts to
var step1 = items.reduce(function (result, _ref) {
var category = _ref.category,
id = _ref.id,
content = _ref.content;
result[category] = result[category] || [];
result[category].push({ id: id, content: content });
return result;
}, {});
var result = Object.keys(step1).map(function (category) {
return { category: category, items: step1[category] };
});
So I just solved the question with the following code (jsfiddle):
// Items
// var items = []
// Create an empty object, used to store the different categories.
var temporaryObject = {}
// Scan for each of the objects in the `items` array.
items.forEach((item) =>
{
// Create a category in the teporary object if the category
// hasn't been created.
if(typeof temporaryObject[item.category] === "undefined")
temporaryObject[item.category] = []
// Push the item to the its category of the `temporaryObject`.
temporaryObject[item.category].push({
id : item.id,
content: item.content
})
})
// Create a empty array used to stores the sorted, grouped items.
var newItems = []
// Scan for each of the category in the `temporaryObject`
for(var category in temporaryObject)
{
// Push the new category in the `newItems` array.
newItems.push({
category: category,
items : []
})
// Get the last category index of the `newItems` array,
// so we can push the related data to the related category.
var lastItem = newItems.length - 1
// Scan for the related category in the `temporaryObject` object.
temporaryObject[category].forEach((item) =>
{
// Push the related data to the related category of the `newItems` array.
newItems[lastItem].items.push(item)
})
}
// Prints the sorted, grouped result.
console.log(newItems)
I currently have this object:
var obj = {
1: {
title: 'test',
children: {
2: {
title: 'test2',
children: {}
},
3: {
title: 'test3',
children: {}
}
}
}
};
The whole idea is I make a function to add an item to this object. As parameter I send the parent.
Now, I was wondering how I would get the right item object. For example if I send parent '2', it would get 2: from the children of 1:. The only way I can think of is a for loop, but I don't know if there's a more efficient way. The children can be extended even more, so a parent has children, those children have children endlessly. That's the whole idea at least.
I think with a few items a for loop is okay, but I think if I have over 50 items it's already slow, and it'll even be slower with more.
This solution use Object.keys() for getting all keys of the given object and an array iteration with short ciruit Array.prototype.some() looks for the key. If found the reference is returned, otherwise the item is checked for an object. If so the object reference is taken for a new search with getReference().
var obj = { 1: { title: 'test', children: { 2: { title: 'test2', children: {} }, 3: { title: 'test3', children: {} } } } };
function getReference(o, p) {
var r;
Object.keys(o).some(function (k) {
if (k === p) {
r = o[k];
return true;
}
if (typeof o[k] === 'object') {
r = getReference(o[k], p);
return !!r;
}
});
return r;
}
var x = getReference(obj, '2');
document.write(x.title);
If you want adding to be fast, you can preserve indexes of your child nodes in object or map (ES6). It could look like this:
function Tree() {
this.data = {};
this.indexes = {0: this.data};
}
Tree.prototype = {
addNode: function(parentIndex, index, node) {
// handle cases when parentIndex does not exist
// handle cases when index already exists
this.indexes[index] = node;
var parent = this.indexes[parentIndex];
parent.children = parent.children || {};
parent.children[index] = node;
}
}
var tree = new Tree();
tree.addNode(0, 1, { title: 'test' });
tree.addNode(1, 2, { title: 'test2' });
tree.addNode(1, 3, { title: 'test3' });
console.log(tree.data);
I've got a flat JavaScript object like this:
{
id: 3726492,
kind: 'user',
permalink: 'nicholas',
username: 'Nicholas',
...
a lot more attributes
}
I'd like to create a new object which only has a subset of the attributes of the original object.
Something like
var newObject = oldObject.fields(['id', 'username']);
newObject would be
{
id: 3726492,
username: 'Nicholas'
}
Is there already something like this?
Try this
function pick(data, keys) {
var result = {};
keys.forEach(function (key) {
if (data.hasOwnProperty(key)) {
result[key] = data[key];
}
});
return result;
}
var data = {
id: 3726492,
kind: 'user',
permalink: 'nicholas',
username: 'Nicholas'
}
var newData = pick(data, ['id', 'kind']);
console.log(newData);
In underscorejs or lodash there is method .pick
var data = {
id: 3726492,
kind: 'user',
permalink: 'nicholas',
username: 'Nicholas',
};
var newObject = _.pick(data, 'id', 'username');
console.log(newObject);
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore.js"></script>
You can use Array.prototype.reduce to reduce one object to another using the list of properties:
function subset(obj, propList) {
return propList.reduce(function(newObj, prop) {
obj.hasOwnProperty(prop) && (newObj[prop] = obj[prop]);
return newObj;
}, {});
}
var obj = {
id: 3726492,
kind: 'user',
permalink: 'nicholas',
username: 'Nicholas'
};
var newObj = subset(obj, ['id', 'username']);
console.log(newObj);
document.getElementById('json').innerText = JSON.stringify(newObj);
<pre id="json"></pre>
Not built-in, but you can sure define a simple function that does the job:
var original = {a:1112, b:434, c:666, d:222};
function fieldSubset(obj, fields) {
var subsetClone = {};
for( var i=0,l=fields.length; i<l; i++) {
// This can prevent filling undefined as properties
if(obj.hasOwnProperty(fields[i])) {
subsetClone[fields[i]] = obj[fields[i]];
}
}
return subsetClone;
}
fieldSubset(original, ["a", "c"]);
You can also use this in Object.prototype, but be aware that this might happen to conflict with native API in the future versions of JavaScript:
var original = {a:1112, b:434, c:666, d:222};
Object.defineProperty(Object.prototype, "fieldSubset", {
value: function(fields) {
var subsetClone = {};
for( var i=0,l=fields.length; i<l; i++) {
// This can prevent filling undefined as properties
if(this.hasOwnProperty(fields[i])) {
subsetClone[fields[i]] = this[fields[i]];
}
}
return subsetClone;
},
enumerable: false,
configurable: true}
);
original.fieldSubset(["a", "c"]);
One liner using Array.prototype.reduce. We are also using Object.assign. The idea is to keep extending a blank object with the keys found in the filters array. If you see, the reduce function takes a callback function with arg1,arg2,arg3 params as the first argument and an empty object as the second argument. This object will be cloned and extended with the help of the keys specified in the filters array.
var a = {
id: 3726492,
kind: 'user',
permalink: 'nicholas',
username: 'Nicholas',
};
var filters = ["id","username","permalink"];
var sub = Object.keys(a).reduce((arg1,arg2,arg3)=>{ var res = {}; if(filters.indexOf(arg2)>=0){ res[arg2] = a[arg2]; } return Object.assign(arg1,res);},{})
console.log(sub);
You haven't specifically mentioned what is the type of values behind your object's keys. Your current answers cover the shallow copy and deep copy.
Another alternative would be to create a view of the original object. This would be helpful if you have very large data objects and you do not want them copy in the memory.
function View(obj, properties) {
var view = {};
properties.forEach(function(prop) {
Object.defineProperty(view, prop, {
get: function() {
return obj[prop];
},
set: function(val) {
obj[prop] = val;
},
enumerable: true,
configurable: true
});
});
return view;
}
then with your data you can do:
var data = {
id: 3726492,
kind: 'user',
permalink: 'nicholas',
username: 'Nicholas',
},
view = new View(data, ['id', 'username']);
view.id; // 3736492
view.username; // Nicholas
of course you have to be aware that you can change your original object just by view.id = 'something else'. However it is easily preventable.
I receive (in my angularjs application) from a server a list of directories like this:
['.trash-user',
'cats',
'cats/css',
'cats/images/blog',
'cats/images/gallery']
And I would like to build a javascript variable which looks like this:
[{
label: '.trash-user'},
{label: 'cats',
children: [{
label: 'css'},
{label: 'images',
children: [{
label: 'blog'},
{label: 'gallery'}
]}
]}
}]
The paths are in random order.
Hope somebody has some really elegant solution, but any solution is appreciated!
Edit:
Here is my naive approach, I have real trouble with recursion.
I could only make level 0 to work:
var generateTree = function(filetree){
console.log('--------- filetree -------');
var model = [];
var paths = [];
for(var i=0;i<filetree.length;i++) {
paths = filetree[i].split('/');
for(var j=0;j<paths.length;++j) {
var property = false;
for(var k=0;k<model.length;++k) {
if (model[k].hasOwnProperty('label') &&
model[k].label === paths[0]) {
property = true;
}
}
if (!property) {
model.push({label: paths[0]});
}
}
}
console.log(model);
};
If you want an elegant solution, lets start with a more elegant output:
{
'.trash-user': {},
'cats': {
'css': {},
'images': {
'blog': {},
'gallery': {},
},
},
}
Objects are much better than arrays for storing unique keys and much faster too (order 1 instead of order n). To get the above output, do:
var obj = {};
src.forEach(p => p.split('/').reduce((o,name) => o[name] = o[name] || {}, obj));
or in pre-ES6 JavaScript:
var obj = {};
src.forEach(function(p) {
return p.split('/').reduce(function(o,name) {
return o[name] = o[name] || {};
}, obj);
});
Now you have a natural object tree which can easily be mapped to anything you want. For your desired output, do:
var convert = obj => Object.keys(obj).map(key => Object.keys(obj[key]).length?
{ label: key, children: convert(obj[key]) } : { label: key });
var arr = convert(obj);
or in pre-ES6 JavaScript:
function convert(obj) {
return Object.keys(obj).map(function(key) {
return Object.keys(obj[key]).length?
{ label: key, children: convert(obj[key])} : { label: key };
});
}
var arr = convert(obj);
I'll venture that generating the natural tree first and then converting to the array will scale better than any algorithm working on arrays directly, because of the faster look-up and the natural impedance match between objects and file trees.
JSFiddles: ES6 (e.g. Firefox), non-ES6.
Something like this should work:
function pathsToObject(paths) {
var result = [ ];
// Iterate through the original list, spliting up each path
// and passing it to our recursive processing function
paths.forEach(function(path) {
path = path.split('/');
buildFromSegments(result, path);
});
return result;
// Processes each path recursively, one segment at a time
function buildFromSegments(scope, pathSegments) {
// Remove the first segment from the path
var current = pathSegments.shift();
// See if that segment already exists in the current scope
var found = findInScope(scope, current);
// If we did not find a match, create the new object for
// this path segment
if (! found) {
scope.push(found = {
label: current
});
}
// If there are still path segments left, we need to create
// a children array (if we haven't already) and recurse further
if (pathSegments.length) {
found.children = found.children || [ ];
buildFromSegments(found.children, pathSegments);
}
}
// Attempts to find a ptah segment in the current scope
function findInScope(scope, find) {
for (var i = 0; i < scope.length; i++) {
if (scope[i].label === find) {
return scope[i];
}
}
}
}