Related
I have two arrays which look like below:
array1 = [
{
id: 'A',
values: [
{ date: '1/1/2022', measure: 231 },
{ date: '1/2/2022', measure: 31 },
],
},
{
id: 'B',
values: [
{ date: '1/1/2020', measure: 51 },
{ date: '1/2/2020', measure: 66 },
],
},
];
const array2 = [
{
id: 'AA',
values: [
{ date: '1/1/2022', measure: 23 },
{ date: '1/2/2022', measure: 67 },
],
},
{
id: 'BB',
values: [
{ date: '1/1/2020', measure: 90 },
{ date: '1/2/2020', measure: 100 },
],
},
];
The arrays have unequal ids but it is known key A should be merged with AA, B should be merged with BB and so on. Dates are equal in case of A and AA, B and BB, etc.
I want to merge A and AA (and rest) as below:
arrayFinall = [
{
id: 'A-AA',
values: [
{date:"1/1/2022", measure1: 231, measure2: 23 },
{date: "1/2/2022", measure1: 31, measure2: 67}},
],
{
id: 'B-BB',
values: [
{date:"1/1/2020", measure1: 51, measure1: 90},
{date:"1/2/2020", measure1: 66, measure1: 100},
}
]
Either creating a new array that has both measures and the date for a new key A-AA
or
push measure from array2 into appropriate position in array 1 work in this case.
const array1 = [
{
id: 'A',
values: [
{ date: '1/1/2022', measure: 231 },
{ date: '1/2/2022', measure: 31 },
],
},
{
id: 'B',
values: [
{ date: '1/1/2020', measure: 51 },
{ date: '1/2/2020', measure: 66 },
],
},
];
const array2 = [
{
id: 'AA',
values: [
{ date: '1/1/2022', measure: 23 },
{ date: '1/2/2022', measure: 67 },
],
},
{
id: 'BB',
values: [
{ date: '1/1/2020', measure: 90 },
{ date: '1/2/2020', measure: 100 },
],
},
];
function mergeArrays(array1, array2) {
const result = [];
const keys = Object.keys(array1);
keys.forEach((key) => {
const array1Values = array1[key].values;
const array2Values = array2[key].values;
const values = [];
array1Values.forEach((value) => {
const date = value.date;
const measure1 = value.measure;
const measure2 = array2Values.find((value2) => value2.date === date).measure;
values.push({ date, measure1, measure2 });
});
result.push({ id: `${array1[key].id}-${array2[key].id}`, values });
});
return result;
}
console.log(JSON.stringify(mergeArrays(array1, array2), null, 2));
I've got a data source:
const data = {
A: [{
value: 1
}, {
value: 2
}, {
value: 38
}],
B: [{
value: 46
}, {
value: 23
}, {
value: 32
}],
C: [{
value: 2345
}, {
value: 56
}, {
value: 3
}]
}
I need to transform this object in an array of objects like below:
[{
A: 1,
B: 46,
C: 2345
}, {
A: 2,
B: 23,
C: 56
}, {
A: 38,
B: 32,
C: 3
}]
I made some attempts but still not there:
a)
const result = Object.keys(data).map(key => data[key])
b)
const b = Object.keys(data).reduce((acc, curr, i) => {
const values = data[curr].map(el => el.value)
acc[curr] = values[i]
return acc
}, [])
EDIT:
All arrays (A, B, C) should have the same length. In case it doesn't, value should be "-" for the missing ones
eg:
[{
A: 1,
B: 46,
C: 2345
}, {
A: 2,
B: 23,
C: 56
}, {
A: 38,
B: 32,
C: -
}]
Assuming the number of objects in your value arrays is the same (or less than) the number of properties in your data object, you could map your data keys to new objects that you can create using Object.fromEntries(). You can pass an array of mapped values to this fromEntries call, which goes through all your keys a, b, c and uses the current index from the outer loop to determine which object to grab its value from:
const data = { A: [{ value: 1 }, { value: 2 }, { value: 38 }], B: [{ value: 46 }, { value: 23 }, { value: 32 }], C: [{ value: 2345 }, { value: 56 }, { value: 3 }] };
const res = Object.keys(data).map((_, i, arr) =>
Object.fromEntries(arr.map(key => [key, data[key][i]?.value ?? "-"]))
);
console.log(res);
If you need a more robust approach, I suggest you go with a solution such as T.J. Crowder's that can handle the case where there are more objects in your arrays than there are properties in your object.
I'm going to assume that the fact there are three properties (A, B, and C) and the fact there are three values for each of them is a coincidence and that we can't rely on that.
If so, see comments:
// Get the keys
const keys = Object.keys(data);
// A blank object to use as a template for a new array entry w/blank values
const blankEntry = Object.fromEntries(keys.map(key => [key, "-"]));
// Create the result array; since we don't know how long it'll need to
// be without reading through the array, just start with a blank one)
const result = [];
// Loop through the properties
for (const key of keys) {
// Loop through this property's values
const values = data[key];
for (let index = 0; index < values.length; ++index) {
// Get the object at this index, creating it if not there
let entry = result[index];
if (!entry) {
// Make a shallow copy of the template to create the entry
result[index] = entry = {...blankEntry};
}
// Set this key's value
entry[key] = values[index].value;
}
}
Live Example (I've added a fourth entry to A to show that the three and three thing isn't important to the solution, and to show the "-" thing working):
const data = {
A: [{
value: 1
}, {
value: 2
}, {
value: 38
}, {
value: 42
}],
B: [{
value: 46
}, {
value: 23
}, {
value: 32
}],
C: [{
value: 2345
}, {
value: 56
}, {
value: 3
}]
};
const keys = Object.keys(data);
const blankEntry = Object.fromEntries(keys.map(key => [key, "-"]));
const result = [];
for (const key of keys) {
const values = data[key];
for (let index = 0; index < values.length; ++index) {
let entry = result[index];
if (!entry) {
result[index] = entry = {...blankEntry};
}
entry[key] = values[index].value;
}
}
console.log(result);
.as-console-wrapper {
max-height: 100% !important;
}
You could get the max length first and then map the values.
const
data = { A: [{ value: 1 }, { value: 2 }, { value: 38 }, { value: 99 }], B: [{ value: 46 }, { value: 23 }, { value: 32 }], C: [{ value: 2345 }, { value: 56 }, { value: 3 }] },
entries = Object.entries(data),
length = entries.reduce((r, [, { length }]) => Math.max(r, length), 0),
result = entries.reduce(
(r, [k, a]) => r.map((o, i) => ({ ...o, ...(i in a && { [k]: a[i].value }) })),
Array.from(
{ length },
() => Object.fromEntries(entries.map(([k]) => [k, '-']))
)
);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You can create an empty object with - and assign value after with reduce() and forEach().
const data = { A: [ { value: 1, }, { value: 2, }, { value: 38, }, ], B: [ { value: 46, }, { value: 23, }, { value: 32, }, ], C: [ { value: 2345, }, { value: 56, }, { value: 3, }, ], };
const keys = Object.keys(data);
const emptyObj = keys.map(_ =>
keys.reduce((o, key) => ({ ...o, [key]: "-" }), {})
);
const o = keys.reduce((a, b) => {
data[b].forEach((el, i) => {
a[i][b] = el.value;
});
return a;
}, emptyObj);
console.log(o);
I think I'd try with something like:
// input
const data = {
A: [{ value: 1 }, { value: 2 }, { value: 38 }, { value: 57 }],
B: [{ value: 46 }, { value: 23 }, { value: 32 }, { value: 42 }],
C: [{ value: 2345 }, { value: 56 }, { value: 3 }],
D: [{ value: 345 }, { value: 684 }]
};
// I am using an IIFE to initialize `output` in order not to pollute the global scope
// with variables that are not re-used anywhere else but obviously it is not strictly necessary.
const output = (() => {
const keys = Object.keys(data);
const maxItemsNr = Math.max(...Object.values(data).map(item => item.length));
const newArr = [];
for (let i = 0; i < maxItemsNr; i++) {
const item = keys.reduce((outObj, key) => {
outObj[key] = data[key][i] ? data[key][i].value : '-';
return outObj;
}, {});
newArr.push(item);
}
return newArr
})();
// test
console.log(output);
And this is my take on it:
const data = { A: [{value: 1}, {value: 2}],
B: [{value: 46}, {value: 23}, {value: 32}, {value: 38}, {value: 42}],
C: [{value: 2345}, {value: 56}, {value: 3}] };
const ntrs = Object.entries(data),
blnk = ntrs.reduce((a,[key])=>(a[key]="-",a),{}),
rslt = [];
ntrs.forEach(([k,arr])=> arr.forEach(({value},i)=>(rslt[i]=rslt[i]||{...blnk})[k]=value) )
console.log(rslt);
.as-console-wrapper {
max-height: 100% !important;
}
My solution. But I think Nick Parson's solution is better.
const data = {A: [{value: 1}, {value: 2}, {value: 38}],B: [{value: 46}, {value: 23}, {value: 32}],C: [{value: 2345}, {value: 56}, {value: 3}]}
function transform(object) {
const res = [];
Object.keys(object).forEach((key)=> {
object[key].forEach(({value},i) => {
if(res[i]) {
res[i][key] = value
} else {
res[i] = {
[key]: value
}
}
})
})
return res
}
console.log(transform(data))
I have list of objects
[
{ regionName:'Alabama',code:'AL', value: 89 },
{ regionName:'Alaska',code:'AK', value: 112 },
{ regionName:'Arizona',code:'AZ', value: 101 },
{ regionName:'Arkansas',code:'AR', value: 123 }
]
which I want to convert to below format -
{
AL: { value: 89 },
AK: { value: 112 },
AZ: { value: 101 },
AR: { value: 123 }
}
How this can be achieved in JS?
You can use map method by passing a callback provided function as parameter which is applied for every item from the given array.
let array=[ { regionName:'Alabama',code:'AL', value: 89 }, { regionName:'Alaska',code:'AK', value: 112 }, { regionName:'Arizona',code:'AZ', value: 101 }, { regionName:'Arkansas',code:'AR', value: 123 } ];
array = Object.assign(...array.map(({ code, value }) => ({ [code]: {value:value }})));
console.log(array);
You can use array.prototype.reduce so on every iteration you append new key (AL, AK or AZ) into the final result.
const arr = [
{ regionName:'Alabama',code:'AL', value: 89 },
{ regionName:'Alaska',code:'AK', value: 112 },
{ regionName:'Arizona',code:'AZ', value: 101 },
{ regionName:'Arkansas',code:'AR', value: 123 }
]
const result = arr.reduce((prev, curr) => {
return {
...prev,
[curr.code]: { value: curr.value },
}
}, {})
console.log(result)
May be I am late but this should also work with simple Array.prototype.map()
var array_of_objects = [
{ regionName:'Alabama',code:'AL', value: 89 },
{ regionName:'Alaska',code:'AK', value: 112 },
{ regionName:'Arizona',code:'AZ', value: 101 },
{ regionName:'Arkansas',code:'AR', value: 123 }
];
var required = {};
var result = array_of_objects.map(function(obj){
return (required[obj.code] = {value:obj.value});
})
console.log(required);
Result:
{
"AL": {"value": 89},
"AK": {"value": 112},
"AZ": {"value": 101},
"AR": {"value": 123}
}
I have the following data structure:
const data = [
{
name: 'ABC',
salesData: [
{
timestamp: '2017-09-01',
value: 10
},
{
timestamp: '2017-09-02',
value: 2
}
]
},
{
name: 'DEF',
salesData: [
{
timestamp: '2017-09-01',
value: 8
},
{
timestamp: '2017-09-02',
value: 3
}
]
}
];
I would like to transform this to:
[
{
name: 'ABC',
'2017-09-01': 10,
'2017-09-02': 2
},
{
name: 'CDE',
'2017-09-01': 8,
'2017-09-02': 3
}
]
I'm trying to use Underscore's Chain and Map which I'm getting confused. So far I have the following, not sure how do I write the convertedSalesData to transform as per the need:
_.map(data, function(item) {
let name = item.name;
let salesData = item.salesData;
let convertedSalesData = ?
})
With ES6 you can use spread syntax ... to get this result.
const data = [{"name":"ABC","salesData":[{"timestamp":"2017-09-01","value":10},{"timestamp":"2017-09-02","value":2}]},{"name":"DEF","salesData":[{"timestamp":"2017-09-01","value":8},{"timestamp":"2017-09-02","value":3}]}]
var result = data.map(function({name, salesData}) {
return {name, ...Object.assign({}, ...salesData.map(({timestamp, value}) => ({[timestamp]: value})))}
})
console.log(result)
const data = [{
name: 'ABC',
salesData: [{
timestamp: '2017-09-01',
value: 10
},
{
timestamp: '2017-09-02',
value: 2
}
]
},
{
name: 'DEF',
salesData: [{
timestamp: '2017-09-01',
value: 8
},
{
timestamp: '2017-09-02',
value: 3
}
]
}
];
var res = data.map(function(a) {
var obj = {
name: a.name
};
a.salesData.forEach(function(x) {
obj[x.timestamp] = x.value;
})
return obj;
})
console.log(res);
Similar to #Nenad Vracar. I perfer to use 'reduce':
data.map(({ name, salesData }) => ({
name,
...salesData.reduce(
(record, { timestamp, value }) => {
record[timestamp] = value
return record
},
Object.create(null)
)
}))
I have a dynamically generated JavaScript object which consist of nested objects and arrays. I couldn't find a proper way to list all nested objects, since that particular object is created dynamically.
Here is the object:
var tagdata = {
"Sentence": [{
"NP": [{
"NMP": "cat"
}, {
"NMP": "dog"
}]
}, {
"VP": [{
"KPD": "tree"
}, {
"NP": [{
"NMP": "ball"
}, {
"NMP": "bat"
}]
},{
"NP": [{
"NMP": "ground"
}, {
"NMP": "time"
}]
}]
}]
};
The output I require looks like this:
[{ key: 1, text: 'Sentence' },
{ key: 2, text: 'NP', parent: 1 },
{ key: 3, text: 'VP', parent: 1 },
{ key: 4, text: 'NMP', parent: 2 },
{ key: 5, text: 'NMP', parent: 2 },
{ key: 6, text: 'KPD', parent: 3 },
{ key: 7, text: 'NP', parent: 3 },
{ key: 8, text: 'NP', parent: 3 },
{ key: 9, text: 'cat', parent: 4 },
{ key: 10, text: 'dog', parent: 5 },
{ key: 11, text: 'tree', parent: 6 },
{ key: 12, text: 'NMP', parent: 7 },
{ key: 13, text: 'NMP', parent: 7 },
{ key: 14, text: 'NMP', parent: 8 },
{ key: 15, text: 'NMP', parent: 8 },
{ key: 16, text: 'ball', parent: 12},
{ key: 17, text: 'bat', parent: 13},
{ key: 18, text: 'ground', parent: 14},
{ key: 19, text: 'time', parent: 15},]
This data is to be used in a tree, so the order might be different, but the key:parent relationship should be maintained. Here is the code I've tried with:
let newtags=[{key:1,text:'Sentence'}];
tagdata["Sentence"].map( (elem,x) => {
newtags.push({key:x,text:Object.keys(elem)[0],parent:x});
if(Object.keys(elem)[0].length !== 0){
var y=x+1;
newtags.push({key:y,text:Object.values(elem)[0][x],parent:y});
}
});
console.log(newtags);
Your code works for the first level, but in your attempt to go one level deeper you will assign duplicate key values (y=x+1 when x will have that value in the next iteration of the .map() method as well).
What you need here is a recursive function, one that calls itself to deal with nested levels in the same way as any other level.
You could use this ES6, functional programming solution for that:
function listItems(obj) {
var key = 0;
return (function recurse(obj, parent = undefined) {
return Object(obj) !== obj ? { key: ++key, text: obj, parent }
: Array.isArray(obj) ? Object.keys(obj).reduce( (acc, text) =>
acc.concat(recurse(obj[text], parent)), [])
: Object.keys(obj).reduce( (acc, text) =>
acc.concat({ key: ++key, text, parent },
recurse(obj[text], key)), []);
})(obj);
}
// Sample data
var tagdata = {
"Sentence": [{
"NP": [{ "NMP": "cat" }, { "NMP": "dog" }]
}, {
"VP": [{
"KPD": "tree"
}, {
"NP": [{ "NMP": "ball" }, { "NMP": "bat" }]
},{
"NP": [{ "NMP": "ground" }, { "NMP": "time" }]
}]
}]
};
// Extract the objects and output:
console.log(listItems(tagdata));
.as-console-wrapper { max-height: 100% !important; top: 0; }
I think this does what you want. It uses a recursive algorithm that handles the case where you have an array separately from the case where you have an object. The base case of just a string is handled in processChild.
let state = [];
function processChild(child, parent) {
if (Array.isArray(child)) {
return processArray(child, parent);
}
if (typeof(child) == 'object') {
return processObject(child, parent);
}
let tag = {
key: state.length + 1,
text: child,
};
if (parent) {
tag.parent = parent;
}
state.push(tag);
}
function processObject(object, parent) {
parent = parent || 0;
let keys = Object.keys(object);
for (let i = 0; i < keys.length; i++) {
//console.log(keys[i]);
let tagIndex = state.length + 1;
let text = keys[i];
let tag = {
key: tagIndex,
text: text,
};
if (parent) {
tag.parent = parent;
}
state.push(tag);
let child = object[keys[i]];
processChild(child, tagIndex);
}
}
function processArray(array, parent) {
parent = parent || 0;
for (let i = 0; i < array.length; i++) {
//console.log(array[i]);
let child = array[i];
//console.log('Child', child);
processChild(child, parent);
}
}
function process(){
let initialState = JSON.parse(input.innerHTML);
processChild(initialState);
code.innerHTML = JSON.stringify(state).replace(/},/g,'},\n');
}
#input{
width: 100%;
height: 200px;
}
<textarea id="input">
{
"Sentence": [{
"NP": [{
"NMP": "cat"
}, {
"NMP": "dog"
}]
}, {
"VP": [{
"KPD": "tree"
}, {
"NP": [{
"NMP": "ball"
}, {
"NMP": "bat"
}]
}, {
"NP": [{
"NMP": "ground"
}, {
"NMP": "time"
}]
}]
}]
}
</textarea>
<button onClick="process()">Process</button>
<pre id="code"></pre>
Just for fun, a non-recursive approach.
One array keeps track of the eventual results (results)
One array keeps track of data-to-process
When nested data is found, it is wrapped in a "to-do item" and added to the todo array
Repeat until no items left
var tagData = [{Sentence:[{NP:[{NMP:"cat"},{NMP:"dog"}]},{VP:[{KPD:"tree"},{NP:[{NMP:"ball"},{NMP:"bat"}]},{NP:[{NMP:"ground"},{NMP:"time"}]}]}]}];
var isNode = n => Array.isArray(n);
var isLeaf = n => !isNode(n);
var todoItem = parent => node => ({ parent, node });
var flatten = function(arr) {
var result = [],
todo = arr.map(todoItem(0)),
current, node, parent, key, innerNode;
while (todo.length) {
({node, parent} = todo.pop());
key = result.length + 1;
Object.keys(node).forEach(k => {
innerNode = node[k];
result.push({ key, parent, text: k });
if (isLeaf(innerNode)) {
result.push({
key: key + 1,
parent: key,
text: innerNode
});
} else {
todo = todo.concat(
innerNode.map(todoItem(key)));
}
});
};
return result;
};
// Print output
console.log(
flatten(tagData)
.map(o => [
"key: " + (o.key < 10 ? " " : "") + o.key,
"parent: " + (o.parent < 10 ? " " : "") + o.parent,
o.text
].join(" | "))
);
.as-console-wrapper {
min-height: 100%;
}