Suppose there is an array like this:
const a = [ {p:1}, {p:2}, {p:3} ];
Is it possible to destructure this array in order to obtain p = [1, 2, 3] ?
Because this does not work :
const [ ...{ p } ] = a; // no error, same as const p = a.p;
// p = undefined;
Edit
In response to all the answers saying that I need to use Array.prototype.map, I am aware of this. I was simply wondering if there was a way to map during the destructuring process, and the answer is : no, I need to destructure the array itself, then use map as a separate step.
For example:
const data = {
id: 123,
name: 'John',
attributes: [{ id:300, label:'attrA' }, { id:301, label:'attrB' }]
};
function format(data) {
const { id, name, attributes } = data;
const attr = attributes.map(({ label }) => label);
return { id, name, attr };
}
console.log( format(data) };
// { id:123, name:'John', attr:['attrA', 'attrB'] }
I was simply wondering if there was a way, directly during destructuring, without using map (and, respectfully, without the bloated lodash library), to retrive all label properties into an array of strings.
Honestly I think that what you are looking for doesn't exist, normally you would map the array to create a new array using values from properties. In this specific case it would be like this
const p = a.map(element => element.p)
Of course, there are some packages that have many utilities to help, like Lodash's map function with the 'property' iteratee
you can destructure the first item like this :
const [{ p }] = a;
but for getting all values you need to use .map
and the simplest way might be this :
const val = a.map(({p}) => p)
Here's a generalized solution that groups all properties into arrays, letting you destructure any property:
const group = (array) => array.reduce((acc,obj) => {
for(let [key,val] of Object.entries(obj)){
acc[key] ||= [];
acc[key].push(val)
}
return acc
}, {})
const ar = [ {p:1}, {p:2}, {p:3} ];
const {p} = group(ar)
console.log(p)
const ar2 = [{a:2,b:1},{a:5,b:4}, {c:1}]
const {a,b,c} = group(ar2)
console.log(a,b,c)
Related
Hello I'm trying to create an object that includes under the same property name a bunch of array values,
This what I'm trying
const quiz = [
{question: 'Who is the main character of DBZ',
options: ['Vegetta','Gohan','Goku']}
]
const newObj = {
options: []
}
quiz.forEach((item)=>{
item.options.forEach((item, index)=>{
newObj.options[`name${index}`] = item
})
})
expected value =
newObj = {
options: [{name: 'Vegetta'},{name:'Gohan'},{name:'Goku'}]
}
actual value received =
newObj = {
{ options: [ name0: 'Vegetta', name1: 'Gohan', name2: 'Goku' ] }}
Thanks in advance!
As you've noticed, newObj.options[`name${index}`] = item creates a new key on your options array, and sets that to item. You instead want to push an object of the form {name: item} into your array. There are a few ways you could go about this, one way is to use .push() like so:
quiz.forEach((item)=>{
item.options.forEach((item)=>{
newObj.options.push({name: item});
})
});
while not as common, you can also use set the current index of options, which is slightly different to the above example, as it will maintain the same index, which can be important if quiz is a sparse array that you want to keep the same indexing of on options:
quiz.forEach((item)=>{
item.options.forEach((item, index)=>{
newObj.options[index] = {name: item};
})
});
Example of the difference:
const arr = [1, 2,,,5]; // sparse array
const pushRes = [];
const indxRes = [];
arr.forEach(n => pushRes.push(n));
arr.forEach((n, i) => indxRes[i] = n);
console.log("Push result", pushRes);
console.log("Index result", indxRes);
For a different approach, you also have the option of using something like .flatMap() and .map() to create your options array, which you can use to create newObj:
const quiz = [
{question: 'Who is the main character of DBZ',
options: ['Vegetta','Gohan','Goku']}
];
const options = quiz.flatMap(({options}) => options.map(name => ({name})));
const newObj = {options};
console.log(newObj);
I tried to contrcut the object based on response coming from API.
my key is assigned this.RootKeyValue and my response is assigned to this.keyResponse
this.RootKeyValue is the key of parent of first object .
In second object based on the DynamicKey value need to create the key and values .
this.RootKeyValue = "AccountDetails";
this.keyResponse =
[
{ICICI: 2,DynamicKey: "ICICI"},
{SBI: 1.25,DynamicKey: "SBI"}
{HDFC: 1.75,DynamicKey: "HDFC"}
]
how to construct the object like below using above key and response.
Expected result :
{
AccountDetails :
{ ICICI :2 , SBI: 1.25,HDFC: 1.75 }
}
I am new to react please suggest how to construct object using the dynamic key values
You build a dynamic object using square bracket notation
const obj = { ["SomeDynamicKey"]: someValue }
So in your case you can use reduce to build the object from your array:
this.RootKeyValue = "AccountDetails";
this.keyResponse =
[
{ICICI: 2,DynamicKey: "ICICI"},
{SBI: 1.25,DynamicKey: "SBI"},
{HDFC: 1.75,DynamicKey: "HDFC"}
]
const result = {
[this.RootKeyValue] : this.keyResponse.reduce( (acc,item) => ({
...acc,
[item.DynamicKey]: item[item.DynamicKey]})
,{})
}
console.log(result)
As the give array already has the dynamic keys and associate value in same object , you can create your desired object very easily like this.
let given = [
{ICICI: 2,DynamicKey: "ICICI"},
{SBI: 1.25,DynamicKey: "SBI"},
{HDFC: 1.75,DynamicKey: "HDFC"}
] , AccountDetails = {};
given.forEach(item => {
AccountDetails[item.DynamicKey] = item[item.DynamicKey] ? item[item.DynamicKey] : '';
})
console.log(AccountDetails);
You can use square brackets to handle this very easily, e.g.
const key = 'DYNAMIC_KEY';
const obj = { [key]: 'value' };
const RootKeyValue = "AccountDetails";
const keyResponse = [
{ ICICI: 2,DynamicKey: "ICICI" },
{ SBI: 1.25,DynamicKey: "SBI" },
{ HDFC: 1.75,DynamicKey: "HDFC" }
];
const newData = { [RootKeyValue]: {} };
keyResponse.forEach(item => {
newData[RootKeyValue][item.DynamicKey] = item[item.DynamicKey];
})
console.log(newData)
I need to get the value of roles in the following example.
const obj ={
id: 1,
"website.com": {
roles: ["SuperUser"]
}
}
const r = obj.hasOwnProperty("roles")
console.log(r)
Its parent objects name ("website.com") can change everytime as Im requesting it from the db. What is the best way to get this variable?
The obj would also be relatively large I just didnt include it in the example.
You could iterate over the object and exclude id. Example:
for (var x in obj) {
if (x !== 'id') {
console.log(obj[x].roles)
}
}
EDIT (to address your question edit):
If the root object has many keys, it would probably make sense to instead either move the domain from a key to a value (for example, domain: 'website.com' and move the roles up (flattening the object); or you could check for a key that looks like a domain using a regex. Example: if (/^[a-zA-Z0-9][a-zA-Z0-9-]{1,61}[a-zA-Z0-9](?:\.[a-zA-Z]{2,})+$/.test(x) rather than if (x !== 'id'). The regex way would probably be brittle.
EDIT 2:
You could use the hasOwnProperty check like this:
let roles
for (let x in obj) {
if (obj[x].hasOwnProperty(roles)) {
roles = obj[x])
}
}
You can get the roles by destructuring the roles property from the value of obj['website.com'].
If you want to do this dynamically, you will need to figure out key has a corresponding object with the property roles. Once you find all valid candidates, you can access the first (or whichever one you want) and then grab its value.
const hasRoles = obj => Object.entries(obj)
.filter(([key, value]) =>
value.hasOwnProperty('roles'));
const obj = {
id: 1,
"website.com": {
roles: ["SuperUser"]
}
}
const [ first ] = hasRoles(obj);
const [ website, { roles } ] = first;
console.log(`website = ${website} | roles = ${roles}`);
Alternatively, for a greedy match:
const hasRolesGreedy = obj => Object.entries(obj)
.find(([key, value]) =>
value.hasOwnProperty('roles'));
const obj = {
id: 1,
"website.com": {
roles: ["SuperUser"]
}
}
const found = hasRolesGreedy(obj);
const [ website, { roles } ] = found;
console.log(`website = ${website} | roles = ${roles}`);
only access to the nested key like this:
let roles = obj["website.com"].roles;
console.log(roles);
That way, you will get an array, then you can iterate or get a value by the index.
Since you don't know what the name is, you need to iterate over all properties and find the first with the roles property:
const testObj ={
id: 1,
"website.com": {
roles: ["SuperUser"]
}
}
const testObj2 = {
id: 2,
"anotherwebsite.com":{
roles: ["AnotherSuperUser"]
}
}
function getRoles(obj){
for(let x in obj){
if(Object.prototype.hasOwnProperty.call(obj, x))
{
if(obj[x].roles){
return obj[x].roles;
}
}
}
return undefined;
}
console.log(getRoles(testObj));
console.log(getRoles(testObj2));
const orignalArr = [
{
personName: 'Joe'
}
]
expected output:
const convertedArr = [
{
name: 'Joe'
}
]
I'm thinking the renamed keys are defined in an object (but fine if there's a better way to map them):
const keymaps = {
personName: 'name'
};
How can I do this with Ramda?
Something with R.map
There is an entry in Ramda's Cookbook for this:
const renameKeys = R.curry((keysMap, obj) =>
R.reduce((acc, key) => R.assoc(keysMap[key] || key, obj[key], acc), {}, R.keys(obj))
);
const originalArr = [{personName: 'Joe'}]
console .log (
R.map (renameKeys ({personName: 'name'}), originalArr)
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js"></script>
But with the ubiquity of ES6, it's pretty easy to write this directly:
const renameKeys = (keysMap) => (obj) => Object.entries(obj).reduce(
(a, [k, v]) => k in keysMap ? {...a, [keysMap[k]]: v} : {...a, [k]: v},
{}
)
You can combine Ramda with Ramda Adjunct. Using the renameKeys (https://char0n.github.io/ramda-adjunct/2.27.0/RA.html#.renameKeys) method is very useful. With it you can simply do something like this:
const people = [
{
personName: 'Joe'
}
]
const renameKeys = R.map(RA.renameKeys({ personName: 'name' }));
const __people__ = renameKeys(people);
console.log(__people__) // [ { name: 'Joe' }]
Hope it helped you :)
This is my take on renameKeys. The main idea is to separate the keys and values to two array. Map the array of keys, and replace with values from keyMap (if exist), then zip back to object:
const { pipe, toPairs, transpose, converge, zipObj, head, map, last } = R
const renameKeys = keysMap => pipe(
toPairs, // convert to entries
transpose, // convert to array of keys, and array of values
converge(zipObj, [ // zip back to object
pipe(head, map(key => keysMap[key] || key)), // rename the keys
last // get the values
])
)
const originalArr = [{ personName: 'Joe', lastName: 'greg' }]
const result = R.map(renameKeys({ personName: 'name' }), originalArr)
console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js"></script>
My idea to make it is to first check that the old prop I want to rename exists, and the new key I want to create doesn’t.
Then, I will use the S_ common combinator to make it point-free.
Find JS common combinators here
const {
allPass, assoc, compose: B, complement, has, omit, prop, when
} = require('ramda');
const S_ = (f) => (g) => (x) => f (g (x)) (x);
const renameKey = (newKey) => (oldKey) => when(allPass([
has(oldKey)
, complement(has)(newKey)
]))
(B(omit([oldKey]), S_(assoc(newKey))(prop(oldKey))))
const obj = { fullname: 'Jon' };
renameKey('name')('fullname')(obj) // => { name: ‘Jon’ }
Here is my own solution, not too many arrow functions (just one), mostly pure Ramda calls. And it is one of shortest, if not the shortest ;)
First, based on your example
const { apply, compose, either, flip, identity, map, mergeAll, objOf, prop, replace, toPairs, useWith } = require('ramda');
const RenameKeys = f => compose(mergeAll, map(apply(useWith(objOf, [f]))), toPairs);
const originalArr = [
{
personName: 'Joe',
},
];
const keymaps = {
personName: 'name',
};
// const HowToRename = flip(prop)(keymaps); // if you don't have keys not specified in keymaps explicitly
const HowToRename = either(flip(prop)(keymaps), identity);
console.log(map(RenameKeys(HowToRename))(originalArr));
Second option, using any arbitrary lambda with renaming rules:
const { apply, compose, map, mergeAll, objOf, replace, toPairs, useWith } = require('ramda');
const RenameKeys = f => compose(mergeAll, map(apply(useWith(objOf, [f]))), toPairs);
const HowToRename = replace(/(?<=.)(?!$)/g, '_'); // for example
console.log(RenameKeys(HowToRename)({ one: 1, two: 2, three: 3 }));
Yields
{ o_n_e: 1, t_w_o: 2, t_h_r_e_e: 3 }
Third, you can use object-based rename rules from the first example and use fallback strategy, e.g. replace like in the second example, instead of identity.
Assume , we have :
var all=[
{firstname:'Ahmed', age:12},
{firstname:'Saleh', children:5 }
{fullname: 'Xod BOD', children: 1}
];
The expected result is ['firstname','age', 'children', 'fullname']: the union of keys of all objects of that array:
all.map((e) => Object.keys(e) ).reduce((a,b)=>[...a,...b],[]);
This is work fine , However, i am seeking a solution more performance using directly reduce method without map , I did the following and it is failed.
all.reduce((a,b) =>Object.assign([...Object.keys(a),...Object.keys(b)]),[])
You can use Set, reduce() and Object.keys() there is no need for map.
var all=[
{firstname:'Ahmed', age:12},
{firstname:'Saleh', children:5 },
{fullname: 'Xod BOD', children: 1}
];
var result = [...new Set(all.reduce((r, e) => [...r, ...Object.keys(e)], []))];
console.log(result)
Here's a solution using generic procedures concat, flatMap, and the ES6 Set.
It's similar to #NenadVracar's solution but uses higher-order functions instead of a complex, do-it-all-in-one-line implementation. This reduces complexity in your transformation and makes it easier to re-use procedures in other areas of your program.
Not that ... spread syntax is bad, but you'll also notice this solution does not necessitate it.
var all = [
{firstname:'Ahmed', age:12},
{firstname:'Saleh', children:5 },
{fullname: 'Xod BOD', children: 1}
];
const concat = (x,y) => x.concat(y);
const flatMap = f => xs => xs.map(f).reduce(concat, []);
const unionKeys = xs =>
Array.from(new Set(flatMap (Object.keys) (xs)));
console.log(unionKeys(all));
// [ 'firstname', 'age', 'children', 'fullname' ]
Just out of curiosity, I've been benchmarking some solutions to your problem using different approaches (reduce vs foreach vs set). Looks like Set behaves well for small arrays but it's extremely slow for bigger arrays (being the best solution the foreach one).
Hope it helps.
var all = [{
firstname: 'Ahmed',
age: 12
}, {
firstname: 'Saleh',
children: 5
}, {
fullname: 'Xod BOD',
children: 1
}],
result,
res = {};
const concat = (x,y) => x.concat(y);
const flatMap = f => xs => xs.map(f).reduce(concat, []);
const unionKeys = xs =>
Array.from(new Set(flatMap (Object.keys) (xs)));
for(var i = 0; i < 10; i++)
all = all.concat(all);
console.time("Reduce");
result = Object.keys(all.reduce((memo, obj) => Object.assign(memo, obj), {}));
console.timeEnd("Reduce");
console.time("foreach");
all.forEach(obj => Object.assign(res, obj));
result = Object.keys(res);
console.timeEnd("foreach");
console.time("Set");
result = [...new Set(all.reduce((r, e) => r.concat(Object.keys(e)), []))];
console.timeEnd("Set");
console.time("Set2");
result = unionKeys(all);
console.timeEnd("Set2");
Try this code:
var union = new Set(getKeys(all));
console.log(union);
// if you need it to be array
console.log(Array.from(union));
//returns the keys of the objects inside the collection
function getKeys(collection) {
return collection.reduce(
function(union, current) {
if(!(union instanceof Array)) {
union = Object.keys(union);
}
return union.concat(Object.keys(current));
});
}