I have an array like so
let items = [
{name: "1"},
{name: "2"},
{name: "3"},
{unwrap: true,
items: [
{name: "4"},
{name: "5"},
{name: "6"},
]
},
{name: "7"},
]
How can I flatten the unwrap object to get the following output?
items = [
{name: "1"},
{name: "2"},
{name: "3"},
{name: "4"},
{name: "5"},
{name: "6"},
{name: "7"},
]
I thought I could do something like this:
items.map(item => {
if(item.hasOwnProperty("unwrap")){
return ... item.items
}
return item
})
However the ...s don't work as a return value.
I have come up with the somewhat clunky using a second array like so:
let output = []
items.forEach((item) => {
if (!item) {
return;
}
if (item.hasOwnProperty("unwrap")) {
return output.push(...item.contents);
}
return output.push(item);
});
flatMap is what you need.
const items=[{name:"1"},{name:"2"},{name:"3"},{unwrap:!0,items:[{name:"4"},{name:"5"},{name:"6"}]},{name:"7"}];
const result = items.flatMap(item => {
if (item.hasOwnProperty('unwrap')) {
return item.items;
}
return item;
});
console.log(result);
You can rely on flatMap:
items.flatMap((x) => {
if (!x.unwrap) {
return x;
}
return x.items;
});
Another way using reduce()
let items = [{name: "1"}, {name: "2"}, {name: "3"}, {unwrap: true, items: [{name: "4"}, {name: "5"}, {name: "6"}, ] }, {name: "7"}, ];
const res = items.reduce((p, c) => p.concat(c.items || c), []);
console.log(res);
Since obj.items won't exist on the regular objects, we can use the || operator to get the desired items, then concat those to the final array.
another way is:
items.flatMap(item=> item?.unwrap ? item.items: item )
or
const result = items.flatMap(item=> item?.unwrap ? item.items: item )
Related
Hello I am new to the site, and I have a problem with javascript that I do not know how to fix.
I have an array, which I want to turn into an object.
arr = [
{prefer: "sport_swimming", rating: "1"},
{prefer: "sport_running", rating: "5"},
{prefer: "sport_tennis", rating: "2"},
{prefer: "study_archeology", rating: "4"}];
obj = Object.assign({}, arr);
console.log(obj);
I want to get to something like this:
{
"sport": {
"swimming":"1",
"running":"5",
"tennis":"2"
},
"study":
{
"archeology":"4"
}
}
Using reduce you can look over the array and build an object using the keys you split off the property.
const arr = [
{prefer: "sport_swimming", rating: "1"},
{prefer: "sport_running", rating: "5"},
{prefer: "sport_tennis", rating: "2"},
{prefer: "study_archeology", rating: "4"}
];
const out = arr.reduce((acc, data) => {
const parts = data.prefer.split("_");
acc[parts[0]] = acc[parts[0]] || {};
acc[parts[0]][parts[1]] = data.rating;
return acc;
}, {});
console.log(out);
You can use reduce:
const arr = [
{ prefer: "sport_swimming", rating: "1" },
{ prefer: "sport_running", rating: "5" },
{ prefer: "sport_tennis", rating: "2" },
{ prefer: "study_archeology", rating: "4" }];
const result = arr.reduce((a, e) =>
([parent, child] = e.prefer.split('_'),
(a[parent] ??= {})[child] = e.rating,
a), {});
console.log(result);
I have an array of objects like so :
[
{key: "key1", label: "1"},
{key: "key2", label: "3"},
{key: "key3", label: "2"},
{key: "key4", label: "Second"},
{key: "key5", label: "First"}
]
I would like to sort this array so the alphabetical values come first like so :
[
{key: "key5", label: "First"},
{key: "key4", label: "Second"},
{key: "key1", label: "1"},
{key: "key3", label: "2"},
{key: "key2", label: "3"}
]
I came up with this solution:
sortArray(list: any[], key: string) {
return list.sort(compare);
function compare(a, b) {
const aIsAlphabetic = isAlphabetical(a[key]);
const bIsAlphabetic = isAlphabetical(b[key]);
if (aIsAlphabetic && !bIsAlphabetic) {
return -1;
} else if (bIsAlphabetic && !aIsAlphabetic) {
return 1;
}
if (a[key] < b[key]) {
return -1;
}
if (a[key] > b[key]) {
return 1;
}
return 0;
}
function isAlphabetical(value: string) {
return value.match(/[a-zA-Z]/i);
}
}
The solution works, but I don't like the idea of declaring several functions inside of sortArray, am I doing it right? Or are there any other tips to make properly?
This is quite an opinion based question, especially with javascript/typescript involved!
The short answer is that there is no correct answer. Your code, as you have said, works! It is also clean, readable and concise.
The downside of writing nested functional code like this is that it is not possible to unit test each function in isolation. If you wanted to properly test this function as a whole, you will have to pump a large number of test cases through it to make sure its doing its job.
Personally i would simply move those nested functions into a high level scope so that i could test them individually.
There's nothing wrong with your solution. If you may be writing similar sort functions you could use a generic function, passing match functions.
const sortArray = (arr, key, rgx, bothMatched, neitherMatched, onlyAMatched, onlyBMatched) => {
const compare = (a, b) => {
const aval = a[key] || "";
const bval = b[key] || "";
const amatched = aval.match(rgx);
const bmatched = bval.match(rgx);
if (amatched) {
if (bmatched) {
return bothMatched(aval,bval);
}
return onlyAMatched(a,b);
}
if (bmatched) {
return onlyBMatched(a,b)
}
return neitherMatched(aval,bval);
};
return arr.sort(compare);
};
const arr = [
{key: "key1", label: "1"},
{key: "key2", label: "3"},
{key: "key3", label: "2"},
{key: "key4", label: "Second"},
{key: "key5", label: "First"}
]
const key = 'label';
console.log("----- match like OP -----");
sortArray(
arr,
key,
/[a-zA-Z]/i,
(a,b) => a.localeCompare(b),
(a,b) => a.localeCompare(b),
() => -1,
() => 1)
.forEach(e => console.log(JSON.stringify(e)));
console.log("----- Alternative match -----");
sortArray(
arr,
key,
/^[\d]+$/,
(a,b) => parseInt(a,10) - parseInt(b,10),
(a,b) => a.localeCompare(b),
() => 1,
() => -1)
.forEach(e => console.log(JSON.stringify(e)));
console.log(".");
This should be enough. Still pattern 'a-zA-Z' is not safe for other languages.
let array=[{key: "key1", label: "1"}, {key: "key2", label: "3"}, {key: "key3", label: "2"}, {key: "key4", label: "Second"},{key: "key5", label: "First"}]
let isAlphanumeric = (value:string) => (value.match(/[a-zA-Z]/i))
console.log(array.sort((a, b) => a.label.localeCompare(b.label))
.filter(obj => isAlphanumeric(obj.label))
.concat(array.filter(obj => !isAlphanumeric(obj.label))))
I have an array of json objects, like so:
[
{id: "81238651", secondary_id: "P2087N", count: "2"},
{id: "89581097", secondary_id: "P2087N", count: "2"}
]
The goal is to get something that can be pretty-printed on the screen or to a log, such as a final string that reads like:
'id: '81238651', secondary_id: "P2087N", count: "2"\n
id: '89581097', secondary_id: "P2087N", count: "2"'
While I can loop over the array and use JSON.stringify() to put something together I was wondering if there were more sophisticated tools / packages that can assist in destructuring and prettifying things?
Easy readable code:
const arr = [
{id: "81238651", secondary_id: "P2087N", count: "2"},
{id: "89581097", secondary_id: "P2087N", count: "2"}
]
let finalStr = '';
for(let obj of arr) {
Object.keys(obj).forEach(key => {
finalStr += String(key + ': ' + obj[key] + ', ');
});
finalStr += '\n';
}
console.log(finalStr);
This will format is out, can change out the pattern how you like.
const rawObjects = [
{id: "81238651", secondary_id: "P2087N", count: "2"},
{id: "89581097", secondary_id: "P2087N", count: "2"}
];
const formatted = rawObjects.map(o => Object.keys(o)
.reduce((a, v, i) => a + `${v}: ${o[v]}${(i<Object.keys(o).length-1)?', ' : ''}`, ''))
.join('\n');
console.log(formatted);
I have three arrays.
One of them contains values I will be testing. The two others are arrays of object which might include the values of my first array under the name key.
const myArray = ["foo", "bar"];
const testArray1 = [
{name: "foo"},
{name: "bar"},
{name: "something else"}
]
const testArray2 = [
{name: "foo"},
{name: "rab"},
{name: "something else"}
]
I am trying to write a condition which would return true only if the tested array contains all of the values of my first array.
With the same example it would give me something like this :
if (testArray1.containsAll(myArray)) // true
if (testArray2.containsAll(myArray)) // false
What is the best way to resolve this ?
Thanks, any help much appreciated
With array.prototype.every and array.prototype.find, it should be:
const myArray = ["foo", "bar"];
const testArray1 = [
{name: "foo"},
{name: "bar"},
{name: "something else"}
];
const testArray2 = [
{name: "foo"},
{name: "rab"},
{name: "something"}
];
console.log(myArray.every(s => testArray1.find(o => o.name === s)));
console.log(myArray.every(s => testArray2.find(o => o.name === s)));
can be use, every and some. these are return only true/false
const myArray = ["foo", "bar"];
const testArray1 = [
{name: "foo"},
{name: "bar"},
{name: "something else"}
]
const testArray2 = [
{name: "foo"},
{name: "rab"},
{name: "something else"}
]
let result1 = testArray1.every(item => myArray.some(array => item.name == array))
let result2 = testArray2.every(item => myArray.some(array => item.name == array))
console.log('result1', result1)
console.log('result2', result2)
Check this out. May not be the best way but works perfectly fine.
const myArray = ["foo", "bar"];
const testArray1 = [
{name: "foo"},
{name: "bar"},
{name: "something else"}
]
const testArray2 = [
{name: "foo"},
{name: "rab"},
{name: "something else"}
]
let aFlag = testArray1.filter( a => myArray.includes(a.name)).length === myArray.length;
let bFlag = testArray2.filter( a => myArray.includes(a.name)).length === myArray.length;
console.log(aFlag, bFlag)
I create users2 array that has 2 elements. I want to change property value of only one element. The output should be 2 2 2 5, but it is 2 2 5 5, so I change property value of all elements in array.
var users2 = [];
var fieldArray = [];
users2.push({"id": 0}, {"id": 1});
fieldArray.push([{text: "2"}, {text: "2"}, {text: "2"}]);
users2[0].arrShow = fieldArray;
users2[1].arrShow = fieldArray;
console.log(users2[0].arrShow[0][0].text);
console.log(users2[1].arrShow[0][0].text);
users2[1].arrShow[0][0].text = "5";
console.log(users2[0].arrShow[0][0].text);
console.log(users2[1].arrShow[0][0].text);
Your two user objects are both using the same arrShow (which is also accessed via fieldArray), and thus using the same 0th entry of the 0th entry in that array. Naturally, changing the state of that object is reflected regardless of which path you take to look at that object.
Instead, give your users separate arrShows with separate objects in them:
var users2 = [
{
id: 0,
arrShow: [
[{text: "2"}, {text: "2"}, {text: "2"}]
]
},
{
id: 1,
arrShow: [
[{text: "2"}, {text: "2"}, {text: "2"}]
]
}
];
console.log(users2[0].arrShow[0][0].text);
console.log(users2[1].arrShow[0][0].text);
users2[1].arrShow[0][0].text = "5";
console.log(users2[0].arrShow[0][0].text);
console.log(users2[1].arrShow[0][0].text);
Or doing it separately after initialization:
var users2 = [
{id: 0, arrShow: []},
{id: 1, arrShow: []}
];
users2[0].arrShow.push(
[{text: "2"}, {text: "2"}, {text: "2"}]
);
users2[1].arrShow.push(
[{text: "2"}, {text: "2"}, {text: "2"}]
);
console.log(users2[0].arrShow[0][0].text);
console.log(users2[1].arrShow[0][0].text);
users2[1].arrShow[0][0].text = "5";
console.log(users2[0].arrShow[0][0].text);
console.log(users2[1].arrShow[0][0].text);
Or if you need to base arrShow on a separate fieldArray, you need to copy both fieldArray and the objects in it; you can do that with Array#map and Object.assign (which is newish, but readily polyfilled for older environments):
var users2 = [
{id: 0, arrShow: []},
{id: 1, arrShow: []}
];
var fieldArray = [{text: "2"}, {text: "2"}, {text: "2"}];
users2.forEach(function(user) {
user.arrShow.push(
fieldArray.map(function(entry) {
return Object.assign({}, entry);
})
);
});
console.log(users2[0].arrShow[0][0].text);
console.log(users2[1].arrShow[0][0].text);
users2[1].arrShow[0][0].text = "5";
console.log(users2[0].arrShow[0][0].text);
console.log(users2[1].arrShow[0][0].text);
You're probably wanting to avoid repetitive code, and have assumed that assignment will implicitly create a deep copy for you. That's not what happens.
To create a standard object structure, create a function that returns a whole new set of objects.
function makeUser(id) {
return {
id: id,
arrShow: [
[{text: "2"}, {text: "2"}, {text: "2"}]
]
};
}
Then invoke it when you need a new set of data.
var users2 = [
makeUser(0),
makeUser(1),
];
Now mutating one user's data will no longer affect the other.
Here's a demo:
var users2 = [
makeUser(0),
makeUser(1),
];
console.log(users2[0].arrShow[0][0].text);
console.log(users2[1].arrShow[0][0].text);
users2[1].arrShow[0][0].text = "5";
console.log(users2[0].arrShow[0][0].text);
console.log(users2[1].arrShow[0][0].text);
function makeUser(id) {
return {
id: id,
arrShow: [
[{text: "2"}, {text: "2"}, {text: "2"}]
]
};
}
This can also be done with constructor functions or the ES6 class syntax.
class User {
constructor(id) {
this.id = id;
this.arrShow = [
[{text: "2"}, {text: "2"}, {text: "2"}]
];
}
}
var users2 = [
new User(0),
new User(1),
];
console.log(users2[0].arrShow[0][0].text);
console.log(users2[1].arrShow[0][0].text);
users2[1].arrShow[0][0].text = "5";
console.log(users2[0].arrShow[0][0].text);
console.log(users2[1].arrShow[0][0].text);