Related
I have an array of generated objects like the following:
[
{obj1: {
key: 'value'
}},
{obj2: {
key: 'value2'
}},
{obj3: {
key: 'value3'
}}
]
I would like to flatten the array, with the following output:
[
{
key: 'value'
},
{
key: 'value2'
},
{
key: 'value3'
}
]
I am doing this with a for loop, which works, but the array will be quite large in size and wonder if there is a more efficient way to do this?
for (var key in array) {
let obj = array[key];
for (var key in obj) {
newArray.push(obj[key]);
}
}
output:
newArray: [
{
key: 'value'
},
{
key: 'value2'
},
{
key: 'value3'
}
]
I'm looking for the simplest method, ES6 or Lodash also welcome for solutions.
Updated to reflect correct array format.
You can simply use reduce and Object.values
let arr = [{obj1: {key: `value`}},{obj2: {key: `value2`
}},{obj3: {key: `value3`}}]
let op = arr.reduce((op,e)=> op.concat(Object.values(e)),[])
console.log(op)
You can use simple for loop when you care about speed.
let arr = [{obj1: {key: `value`}},{obj2: {key: `value2`
}},{obj3: {key: `value3`}}]
let op = []
for(let i=0; i<arr.length; i++){
let values = Object.values(arr[i])
op = op.concat(values)
}
console.log(op)
You can use Array.map, and Object.values.
Map "maps" each element in an array to a new array.
It takes each element of an array, and performs an operation on it.
The result of this operation becomes the corresponding element in a new Array.
This new Array is what's returned.
To convert Objects into Arrays: you can use Object.values, Object.keys, and Object.entries.
Object.values de-references each key in an object, and turns it into an array element holding that key's value.
const arr = [
{obj1: {key: 'value'}},
{obj2: {key: 'value2'}},
{obj3: {key: 'value3'}}
];
let newArr = arr.map(obj => (
{key: Object.values(obj)[0].key}
));
console.log(newArr);
To return an object, it must be wrapped in parenthesis.
In the first iteration, obj == { obj1: { key: 'value' }}, the first element in the input Array, arr.
And,
Object.values(obj) == [{key: 'value'}]
So, we need to grab the element at index 0 to pull the object out of the array {key: 'value'}.
Alternatively, if you know you can rely on the naming structure of the elements in your array (the outer object's key), you could do this, which may be easier to reason about:
const arr = [
{obj1: {key: 'value'}},
{obj2: {key: 'value2'}},
{obj3: {key: 'value3'}}
];
let newArr2 = arr.map( (obj, i) => (
{ key: obj['obj'+(i+1)].key }
));
console.log(newArr2);
Note: you'll need to wrap the i+1 in parenthesis, to force addition to take precedence over JS auto type conversion and string concatenation. Otherwise instead of obj1, obj2, obj3, you'll get obj01, obj11, obj21 as the object keys.
I'm trying to get my head around map functions.
Here is my working code and output using a nested for loop:
var jsonsToAddTo = [
{'cat':'k1','key2':'a'},
{'cat':'k1','key2':'b'},
{'cat':'k2','key2':'a'},
{'cat':'k2','key2':'b'},
{'cat':'k3','key2':'a'}
]
var additionalData = [
{'pk':'k1','key3':'data1'},
{'pk':'k2','key3':'data2'},
{'pk':'k3','key3':'data3'},
]
// Adds a key value pair from sourceJson to targetJson based on a matching value
function denormalizeJsonOnKey(targetJsonArray,targetKeyToMatch, sourceJsonArray, sourceKeyToMatch, keyToAdd){
for(thisJson in targetJsonArray){
for(thatJson in sourceJsonArray){
if(targetJsonArray[thisJson][targetKeyToMatch]==sourceJsonArray[thatJson][sourceKeyToMatch]){
console.log('match');
targetJsonArray[thisJson][keyToAdd]=sourceJsonArray[thatJson][keyToAdd];
}
}
}
return targetJsonArray
}
console.log(denormalizeJsonOnKey(jsonsToAddTo,'cat',additionalData,'pk','key3'))
OUTPUT:
[
{ cat: 'k1', key2: 'a', key3: 'data1' },
{ cat: 'k1', key2: 'b', key3: 'data1' },
{ cat: 'k2', key2: 'a', key3: 'data2' },
{ cat: 'k2', key2: 'b', key3: 'data2' },
{ cat: 'k3', key2: 'a', key3: 'data3' }
]
I can't figure out how to handle the nesting using a map function on an array.
Using ES6 can simplify using Array#find() and Object#assign()
var data = [
{'cat':'k1','key2':'a'},
{'cat':'k1','key2':'b'},
{'cat':'k2','key2':'a'},
{'cat':'k2','key2':'b'},
{'cat':'k3','key2':'a'}
]
var data2 = [
{'pk':'k1','key3':'data1'},
{'pk':'k2','key3':'data2'},
{'pk':'k3','key3':'data3'},
]
const mergeData= (arr1, arr2, matchKey, filterKey, includeKey)=>{
arr1.forEach(o => {
const newObj ={};
const match = arr2.find(e => e[filterKey] === o[matchKey])
newObj[includeKey] = match ? match[includeKey] : null;
Object.assign(o, newObj);
});
}
mergeData(data, data2,'cat', 'pk', 'key3')
console.log(data)
Here is a solution that takes advantage of map and object spread to produce a new array with the desired key added into the target array's elements:
var jsonsToAddTo = [
{'cat':'k1','key2':'a'},
{'cat':'k1','key2':'b'},
{'cat':'k2','key2':'a'},
{'cat':'k2','key2':'b'},
{'cat':'k3','key2':'a'}
]
var additionalData = [
{'pk':'k1','key3':'data1'},
{'pk':'k2','key3':'data2'},
{'pk':'k3','key3':'data3'},
]
function denormalizeJsonOnKey(targetJsonArray,targetKeyToMatch, sourceJsonArray, sourceKeyToMatch, keyToAdd){
return targetJsonArray.map(thisJson => {
const addObj = sourceJsonArray.find(thatJson => thatJson[sourceKeyToMatch] === thisJson[targetKeyToMatch]);
return {
...thisJson,
...addObj ? {[keyToAdd]: addObj[keyToAdd]} : {},
}
});
}
console.log(denormalizeJsonOnKey(jsonsToAddTo, 'cat', additionalData, 'pk', 'key3'))
Note that this solution won't mutate the original array, so the jsonsToAddTo variable will be the same after you invoke the function. If you want to replace the original, you can always just re-assign it:
jsonsToAddTo = denormalizeJsonOnKey(jsonsToAddTo, 'cat', additionalData, 'pk', 'key3')
Try this,
using maps for both iteration,
var jsonsToAddTo = [{'cat':'k1','key2':'a'},{'cat':'k1','key2':'b'},
{'cat':'k2','key2':'a'},{'cat':'k2','key2':'b'},
{'cat':'k3','key2':'a'}]
var additionalData = [{'pk':'k1','key3':'data1'},{'pk':'k2','key3':'data2'},{'pk':'k3','key3':'data3'},
]
function denormalizeJsonOnKey(targetJsonArray,targetKeyToMatch, sourceJsonArray, sourceKeyToMatch, keyToAdd){
jsonsToAddTo.map((obj,index)=> {
additionalData.map((o,idx)=> {
if(obj[targetKeyToMatch]==o[sourceKeyToMatch]){
obj[keyToAdd]=o[keyToAdd];
}
})
})
return jsonsToAddTo
}
console.log(denormalizeJsonOnKey(jsonsToAddTo,'cat',additionalData,'pk','key3'))
var targetJsonArray = jsonsToAddTo.map(function(json, index) {
additionalData.forEach(function(data) {
if (data.pk === json.cat) {
json.key3 = data.key3;
}
})
return json;
})
Rather than nesting loops here, which will iterate the entire additionalData array for every entry in jsonsToAddTo, I suggest building an object map of the additionalData dataset once at the beginning, and then reference this within a .map on the target dataset:
var jsonsToAddTo = [
{'cat':'k1','key2':'a'},
{'cat':'k1','key2':'b'},
{'cat':'k2','key2':'a'},
{'cat':'k2','key2':'b'},
{'cat':'k3','key2':'a'}
]
var additionalData = [
{'pk':'k1','key3':'data1'},
{'pk':'k2','key3':'data2'},
{'pk':'k3','key3':'data3'},
]
// Adds a key value pair from sourceJson to targetJson based on a matching value
function denormalizeJsonOnKey(targetJsonArray,targetKeyToMatch, sourceJsonArray, sourceKeyToMatch, keyToAdd){
// Build an object of items keyed on sourceKeyToMatch
const sourceJsonMap = sourceJsonArray.reduce((obj, item) => (obj[item[sourceKeyToMatch]]=item, obj), {});
return targetJsonArray.map(item => {
const targetValue = item[targetKeyToMatch];
if (sourceJsonMap.hasOwnProperty(targetValue)) {
item[keyToAdd] = sourceJsonMap[targetValue][keyToAdd];
}
return item;
});
}
console.log(denormalizeJsonOnKey(jsonsToAddTo,'cat',additionalData,'pk','key3'))
Doing it this way should be far more efficient, especially if the dataset you are working on is fairly large.
I would like to create this JS function with these arguments:
transform([{a:1, b:'1', c:true},{a:'1', b:2, c:3, d:false}, {a:1, c:'test'}], ['a','b','c']);
First argument is an array of objects
Second one is array of keys.
I would like to get this output object:
{a:[1, '1', 1], b:['1', 2],c:[true, 3, 'test']}
As you can see the second argument became the keys to the created object
and all values under these keys where grouped together.
And maybe an option to pass a unique argument to function and get this (duplicate values removed):
{a:[1, '1'], b:['1', 2], c:[true, 3, 'test']}
What is the fast and/or elegant way to do it?
Is there any lodash/underscore helper for it?
As an additional generalism. How can the input (the first argument) be a generic collection with nested levels (array or object of nested levels of arrays or objects) ?
Thanks.
You can use Array.prototype.reduce
let param1 = [{a:1,b:'1',c:true},{a:'1',b:2,c:3,d:false},{a:1,c:'test'}];
let param2 = ['a', 'b', 'c'];
function test(objArr, keys) {
let returnObject = {};
keys.forEach(key => returnObject[key] = []);
return objArr.reduce((ret, obj) => {
keys.forEach(key => {
if (obj[key] !== undefined)
ret[key].push(obj[key]);
});
return ret;
}, returnObject);
}
console.log(JSON.stringify(test(param1, param2)));
Outputs:
{"a":[1,"1",1],"b":["1",2],"c":[true,3,"test"]}
Try this:
function transform(data,keys){
let results = {};
//loop all you keys
keys.forEach(index => {
//loop your arrays
data.forEach(element => {
//if there is a match add the key to the results object
if(index in element) {
if(!(index in results)) results[index] = [];
//check if a value already exists for a given key.
if(!(element[index] in results[index])) results[index].push(element[index]);
}
});
});
return results;
}
console.log(transform([{a:1,b:'1',c:true},{a:'1',b:2,c:3,d:false},{a:1,c:'test'}], ['a','b','c']));
You can loop over the key array and pass this key to another function which will use forEach method. This getMatchedKeyValues using forEachwill return an array of elements whose key matches
var arr = [{
a: 1,
b: '1',
c: true
}, {
a: '1',
b: 2,
c: 3,
d: false
}, {
a: 1,
c: 'test'
}];
var keys = ['a', 'b', 'c']
function transform(keyArray) {
var newObj = {};
// looping over key array
keyArray.forEach(function(item) {
// adding key property and calling a function which will return
// an array of elements whose key is same
newObj[item] = getMatchedKeyValues(item)
})
return newObj;
}
function getMatchedKeyValues(keyName) {
var valArray = [];
arr.forEach(function(item) {
if (item[keyName]) {
valArray.push(item[keyName])
}
})
return valArray;
}
console.log(transform(keys))
I coded below , pls have a look this solution.
function test(arr, arr1) {
return arr.reduce((total, current) => {
arr1.forEach(curr => {
if (typeof total[curr] === "undefined") total[curr] = [];
if (current[curr]) total[curr].push(current[curr]);
});
return total;
}, {});
}
console.log(
test(
[
{ a: 1, b: "1", c: true },
{ a: "1", b: 2, c: 3, d: false },
{ a: 1, c: "test" }
],
["a", "b", "c"]
)
);
i'm doing a project in VueJS and i have an array of data, which consists of a number of objects.
These objects are pulled from a PHP Backend and consist of values like
id: 2123
name: "Name Value"
status: "active"
account_id: "2KGGALS2353255"
Imagine i want to split these by the keys names into a similar array but i want to have a parent object that consists of two child objects
[
0: {
core: {
id: 2123
name: "Name Value"
},
extra: {
status: "active",
account_id: "2KGGALS2353255"
}
]
The question is how can i achieve this with Javascript? I don't really want to modify the data in PHP beforehand unless this is something very unadvised to do in Javascript.
I can use VueJS and Lodash.
I was looking for lodash's pick() method.
https://lodash.com/docs/4.17.4#pick
This should work for your purpose
function separate(obj, keys) {
let target = {}, rest = {};
Object.keys(obj).forEach(function(key) {
if (keys.includes(key)) {
target[key] = obj[key];
} else {
rest[key] = obj[key];
}
});
return { target: target, rest: rest };
}
let stuff = {
id: 2123,
name: "Name Value",
status: "active",
account_id: "2KGGALS2353255"
};
let separated = separate(stuff, ['id', 'name']);
console.log({
core: separated.target,
extra: separated.rest
});
Using ES6's object destructuring, and the object rest spread proposal, which requires a babel transform, you can Array#map the array into a new array of objects in the required format:
const arr = [{"id":1,"name":"Name1","status":"active","account_id":"2KGGALS2353255"},{"id":2,"name":"Name2","status":"active","account_id":"4ABCLS2353255"},{"id":3,"name":"Name3","status":"active","account_id":"6LMNALS2353255"}];
const result = arr.map(({ id, name, ...extra }) => ({
core: {
id,
name
},
extra
}));
console.log(result);
You can do the same thing using lodash's _.pick() to the get the core, and _.omit() to get the extra:
var arr = [{"id":1,"name":"Name1","status":"active","account_id":"2KGGALS2353255"},{"id":2,"name":"Name2","status":"active","account_id":"4ABCLS2353255"},{"id":3,"name":"Name3","status":"active","account_id":"6LMNALS2353255"}];
var result = arr.map(function(obj) {
return {
core: _.pick(obj, ['id', 'name']),
extra: _.omit(obj, ['id', 'name'])
};
});
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>
I created an immutable map (with Immutable-JS) from a list of objects:
var result = [{'id': 2}, {'id': 4}];
var map = Immutable.fromJS(result);
Now i want to get the object with id = 4.
Is there an easier way than this:
var object = map.filter(function(obj){
return obj.get('id') === 4
}).first();
Essentially, no: you're performing a list lookup by value, not by index, so it will always be a linear traversal.
An improvement would be to use find instead of filter:
var result = map.find(function(obj){return obj.get('id') === 4;});
The first thing to note is that you're not actually creating a map, you're creating a list:
var result = [{'id': 2}, {'id': 4}];
var map = Immutable.fromJS(result);
Immutable.Map.isMap(map); // false
Immutable.List.isList(map); // true
In order to create a map you can use a reviver argument in your toJS call (docs), but it's certainly not the most intuitive api, alternatively you can do something like:
// lets use letters rather than numbers as numbers get coerced to strings anyway
var result = [{'id': 'a'}, {'id': 'b'}];
var map = Immutable.Map(result.reduce(function(previous, current) {
previous[ current.id ] = current;
return previous;
}, {}));
Immutable.Map.isMap(map); // true
Now we have a proper Immutable.js map which has a get method
var item = Map.get('a'); // {id: 'a'}
It may be important to guarantee the order of the array. If that's the case:
Use an OrderedMap
Do a set method on the OrderedMap at each iteration of your source array
The example below uses "withMutations" for better performance.
var OrderedMap = Immutable.OrderedMap
// Get new OrderedMap
function getOm(arr) {
return OrderedMap().withMutations(map => {
arr.forEach(item => map.set(item.id, item))
})
}
// Source collection
var srcArray = [
{
id: 123,
value: 'foo'
},
{
id: 456,
value: 'bar'
}
]
var myOrderedMap = getOm(srcArray)
myOrderedMap.get(123)
// --> { id: 123, value: 'foo' }
myOrderedMap.toObject()
// --> { 123: {id: 123, value: 'foo'}, 456: {id: 456, value: 'bar'} }
myOrderedMap.toArray()
// --> [ {id: 123, value: 'foo'}, { id: 456, value: 'bar' } ]
When using fromJS for array, you'll get List not map. It will be better and easier if you create a map. The following code will convert the result into Immutable map.
const map = result.reduce((map, json) =>
map.set(json.id, Immutable.fromJS(json))
, Map());
Now, you can
map.get('2'); //{'id': 2}
Note, if the result has nested structure and if that has array, it will be a List with the above code.
With ES2015 syntax (and constants):
const result = map.find(o => o.get('id') === 4);
Is there already a way thats easier? I don't know. but you can write your own function. Something like this should work:
var myFunc = function(id){
var object = map.filter(function(obj){return obj.get('id') === id}).first();
return object;
}
Then you would just do:
var myObj = myFunc(4);