Reduce array of objects to parameter with the same objects - javascript

I hope the title is enough self-explanatory but I will try to clarify with a fiddle I've created. There are two arrays, one called tags which is what I currently have and tags_ideal which is what I really want to achieve.
Current state:
var tags = [
{
id: 1,
color: 'red',
l10n: [
{
name: 'Something something',
lang: 'english'
},
{
name: 'Etwas etwas',
lang: 'deutsch'
}
]
},
{
id: 2,
color: 'blue',
l10n: ...
}
]
What I'm after:
var tags_ideal = [
{
id: 1,
color: 'red',
l10n: {
'english': {
name: 'Something something',
},
'deutsch': {
name: 'Etwas etwas',
}
}
},
{
id: 2,
color: 'blue',
l10n: ...
}
]
I have the first case and want to convert all the stuff from l10n so that they don't have a lang: english parameter but rather have an object called english and the name/title inside of that. Below both tags is what I tried to do and what works when I pass just the l10n object into it, but not the whole thing (I do understand why, and now I would like to know how to do what I am really after).
Also, please note that my arrays are not correct at this point. I have appended three dots at the start of the next object just to point out that there is more than one object in my array.
Here's the fiddle:
https://jsfiddle.net/cgvpuj70/

You could iterate the outer array and map new objects for the inner arrays.
var tags = [{ id: 1, color: 'red', l10n: [{ name: 'Something something', lang: 'english' }, { name: 'Etwas etwas', lang: 'deutsch' }] }, { id: 2, color: 'blue', l10n: [] }];
tags.forEach(a => a.l10n = a.l10n.map(b => ({ [b.lang]: { name: b.name } })));
console.log(tags);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Use .reduce() on the l10n to make an object populated with the keys/values derived from each object in the current Array.
var tags = [
{
id: 1,
color: 'red',
l10n: [
{
name: 'Something something',
lang: 'english'
},
{
name: 'Etwas etwas',
lang: 'deutsch'
}
]
},
{
id: 2,
color: 'blue'
}
]
tags.forEach(t => {
t.l10n = t.l10n && t.l10n.reduce((o, data) =>
Object.assign(o, {[data.lang]: {name: data.name}})
, {})
})
console.log(tags);

All you have to do is create an empty object and fill it up. First, create the new object. Then, for each element of the l10n array, set object[element.lang] equal to element.name. Now you've go the new json structure.
Here's a modified translate function:
function translate(title) {
var object = {};
for (var i=0; i<title.length; i++) {
var element = title[i];
object[element.lang] = element.name;
}
return object;
}
Edit: Here's how you can translate the entire object
var newTags = [];
for (var i=0; i<tags.length; i++) {
var edited = {};
edited.id = tags[i].id;
edited.color = tags[i].color;
edited.l10n = translate(tags[i].l10n);
newTags.push(edited);
}

Related

Add id:s for objs in arr. R.assoc('id', i++) not incrementing i

I am trying to add id:s to objects in an array with Ramda, but the id just equals 1 for every object.
let i = 1;
return R.evolve({
cms: {
components: R.map(R.assoc('id', i++)),
},
}, state),
I assume that has something to do with the i++. One is not supposed to mutate like that with Ramda.
But then, how would I do this correctly.
You have to wrap it in a function, otherwise i++ is evaluated once and then applied to all your elements.
const state = {
cms: {
components: [{
name: 'Serge'
}, {
name: 'Odile'
}, {
name: 'Simon'
}, {
name: 'Émile'
}]
}
};
let i = 1,
modifiedState = R.evolve({
cms: {
components: R.map((element) => R.assoc('id', i++, element)),
},
}, state);
console.log(modifiedState.cms.components);
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.25.0/ramda.min.js"></script>
Or you could use Ramda's addIndex like this:
const transform = R.evolve({
cms: {
components: R.addIndex(R.map)((comp, i) => R.assoc('id', i + 1, comp)),
},
})
const state = {
cms: {
components: [
{name: 'Serge'},
{name: 'Odile'},
{name: 'Simon'},
{name: 'Émile'}
]
}
};
console.log(transform(state))
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.25.0/ramda.min.js"></script>

Javascript: Filter array of objects

What am I doing wrong here?
var locations = [
{ id: 1, name: 'N'},
{ id: 2, name: 'P'}
]
var employee = { location_id: 1 }
locations.filter((location) => {
return location.id == employee.location_id
});
console.log(locations);
this returns undefined when I'm trying to make it return { id: 1, name: 'N'}.
filter() function is not mutable - which means it returns a new array with the filtered objects and do not 'mutate' the original array - you must assign it to another variable - see demo below:
locations = [
{ id: 1, name: 'N'},
{ id: 2, name: 'P'}
]
employee = { location_id: 1 }
var result = locations.filter((location) => {
return location.id == employee.location_id
})
console.log(result);
You need a variable for the result of filtering with Array#filter
The filter() method creates a new array with all elements that pass the test implemented by the provided function.
var locations = [
{ id: 1, name: 'N'},
{ id: 2, name: 'P'}
],
employee = { location_id: 1 },
result = locations.filter((location) => {
return location.id == employee.location_id
});
console.log(result);
You need to store the result of .filter(). It doesn't mutate the original array.
On a side note, you can shorten your callback function by removing the curly brackets and return statement.
locations = locations.filter(loc => loc.id == employee.location_id);

how to rescursively deep merge an array of objects

I want to merge two arrays of objects. Those objects have the same structure, but one of them is missing the hide property. I want to copy the value of hide property from one object to the other that is missing this property. The important part is that I don't want to mutate any of these arrays!
The first array looks like this (notice that there is hide property):
let first_array = [
{
name: 'John',
age: 40,
hide: true,
childs: [
{
name: 'Alice',
age: 20,
hide: false,
childs: [
{
name: 'Mike',
age: 2,
hide: true
}
]
}
]
},
{
name: 'Peter',
age: 40,
hide: true,
childs: [
{
name: 'Andrew',
age: 20,
hide: true,
childs: [
{
name: 'Jessica',
age: 2,
hide: true
}
]
}
]
}
]
The second array looks almost the same! The only thing missing is hide property.
let second_array = [
{
name: 'John',
age: 40,
childs: [
{
name: 'Alice',
age: 20,
childs: [
{
name: 'Mike',
age: 2,
}
]
}
]
},
{
name: 'Peter',
age: 40,
childs: [
{
name: 'Andrew',
age: 20,
childs: [
{
name: 'Jessica',
age: 2,
}
]
}
]
}
]
Now, I want to create new array with where within each object there is hide property.
I know how to do this recursively in the imperative way, but unfortunately I'm mutating data - which I don't want to do.
function getHideProperty(first, second) {
for (let i = 0; i < second.length; i++) {
for (let j = 0; j < first.length; j++) {
if (second[i].name === first[j].name) {
second[i].hide = first[j].hide
if (second[i].childs) {
second[i].childs = getHideProperty(first[j].childs, second[i].childs)
}
}
}
}
return second
}
Now I can create new array with merged objects:
const newArray = getHideProperty(second_array, first_array)
Now, every object in second_array has hide property. But I mutated the array :(
How to achieve such result without mutating the array?
You'll need to:
Create a new array to store the new information, and return that
Deep-copy second[i] to store in the new array, prior to modifying anything
For #2, choose your favorite answer from What is the most efficient way to deep clone an object in JavaScript?
For #1, very roughly (see comments):
function getHideProperty(first, second) {
const result = []; // Our result array
for (let i = 0; i < second.length; i++) {
const secondEntry = result[i] = deepCopy(second[i]); // The deep copy, and let's avoid constantly re-retrieving second[i]/result[i]
for (let j = 0; j < first.length; j++) {
if (secondentry.name === first[j].name) {
secondentry.hide = first[j].hide
if (secondEntry.childs) {
// Could be more efficient here, since the entries in `childs` are already copies; left as an exercise to the reader...
secondEntry.childs = getHideProperty(first[j].childs, secondEntry.childs)
}
}
}
}
return result;
}
This is not meant to be an all-singing, all-dancing solution. It's meant to help you along the way. Note the deepCopy placeholder for your preferred solution to #2. :-)
If you do something like the above (nested loops) and find that it's a performance problem, you can create a Map of the entries in first keyed by their names, and then look them up in the map when looping through second (rather than the nested loop). The complexity is only useful if you run into a performance problem with the simple nested loops solution.
This is a functional approach that doesn't mutate any of the original arrays or their items:
function getHideProperty(first, second) {
return second.map(function(item) {
var corresponding = first.find(function(searchItem) {
return searchItem.name === item.name;
});
return Object.assign({},
item,
{ hide: corresponding.hide },
item.childs
? { childs: getHideProperty(item.childs, corresponding.childs) }
: {}
);
});
}

Create new array from iterating JSON objects and getting only 1 of its inner array

See jsfiddle here: https://jsfiddle.net/remenyLx/2/
I have data that contains objects that each have an array of images. I want only the first image of each object.
var data1 = [
{
id: 1,
images: [
{ name: '1a' },
{ name: '1b' }
]
},
{
id: 2,
images: [
{ name: '2a' },
{ name: '2b' }
]
},
{
id: 3
},
{
id: 4,
images: []
}
];
var filtered = [];
var b = data1.forEach((element, index, array) => {
if(element.images && element.images.length)
filtered.push(element.images[0].name);
});
console.log(filtered);
The output needs to be flat:
['1a', '2a']
How can I make this prettier?
I'm not too familiar with JS map, reduce and filter and I think those would make my code more sensible; the forEach feels unnecessary.
First you can filter out elements without proper images property and then map it to new array:
const filtered = data1
.filter(e => e.images && e.images.length)
.map(e => e.images[0].name)
To do this in one loop you can use reduce function:
const filtered = data1.reduce((r, e) => {
if (e.images && e.images.length) {
r.push(e.images[0].name)
}
return r
}, [])
You can use reduce() to return this result.
var data1 = [{
id: 1,
images: [{
name: '1a'
}, {
name: '1b'
}]
}, {
id: 2,
images: [{
name: '2a'
}, {
name: '2b'
}]
}, {
id: 3
}, {
id: 4,
images: []
}];
var result = data1.reduce(function(r, e) {
if (e.hasOwnProperty('images') && e.images.length) r.push(e.images[0].name);
return r;
}, [])
console.log(result);
All answers are creating NEW arrays before projecting the final result : (filter and map creates a new array each) so basically it's creating twice.
Another approach is only to yield expected values :
Using iterator functions
function* foo(g)
{
for (let i = 0; i < g.length; i++)
{
if (g[i]['images'] && g[i]["images"].length)
yield g[i]['images'][0]["name"];
}
}
var iterator = foo(data1) ;
var result = iterator.next();
while (!result.done)
{
console.log(result.value)
result = iterator.next();
}
This will not create any additional array and only return the expected values !
However if you must return an array , rather than to do something with the actual values , then use other solutions suggested here.
https://jsfiddle.net/remenyLx/7/

Sort array of objects by arbitrary list in JavaScript

Given an array of objects like this:
objects = [
{ id: 'aaaa', description: 'foo' },
{ id: 'bbbb', description: 'bar' },
{ id: 'cccc', description: 'baz' }
];
And an array of strings like this:
order = [ 'bbbb', 'aaaa', 'cccc' ];
How would I sort the first array so that the id attribute matches the order of the second array?
Try this:
objects.sort(function(a, b){
return order.indexOf(a.id) - order.indexOf(b.id)
});
Assuming the variables are like you declared them in the question, this should return:
[
{ id: 'bbbb', description: 'bar' },
{ id: 'aaaa', description: 'foo' },
{ id: 'cccc', description: 'baz' }
];
(It actually modifies the objects variable)
You need a way to translate the string into the position in the array, i.e. an index-of function for an array.
There is one in newer browsers, but to be backwards compatible you need to add it if it's not there:
if (!Array.prototype.indexOf) {
Array.prototype.indexOf = function(str) {
var i;
for (i = 0; i < this.length; i++) if (this[i] == str) return i;
return -1;
}
}
Now you can sort the array by turning the string into an index:
objects.sort(function(x,y){ return order.indexOf(x.id) - order.indexOf(y.id); });
Demo: http://jsfiddle.net/Guffa/u3CQW/
Use a mapping object for (almost) constant access time:
/* Create a mapping object `orderIndex`:
{
"bbbb": 0,
"aaaa": 1,
"cccc": 2
}
*/
const orderIndex = {}
order.forEach((value, index) => orderIndex[value] = index);
// Sort
objects.sort((a, b) => orderIndex[a.id] - orderIndex[b.id]);
// data
const objects = [
{ id: 'aaaa', description: 'foo' },
{ id: 'bbbb', description: 'bar' },
{ id: 'cccc', description: 'baz' }
];
const order = [ 'bbbb', 'aaaa', 'cccc' ];
/* Create a mapping object `orderIndex`:
{
"bbbb": 0,
"aaaa": 1,
"cccc": 2
}
*/
const orderIndex = {}
order.forEach((value, index) => orderIndex[value] = index);
// Sort
objects.sort((a, b) => orderIndex[a.id] - orderIndex[b.id]);
// Log
console.log('orderIndex:', orderIndex);
console.log('objects:', objects);

Categories

Resources