Splitting js object based on new property - javascript

I'm trying to split this object based on a common new property 'subject'.
There could be multiple subjects in a single object in the original data. But in the final data, it must have a single subject and the marks associated with it.
let x = {
'Name': 'Ajay',
'Maths': 0,
'English': 26,
}
and the expected output is
let y = [{
'Name' : 'Ajay',
'Marks' : 0,
'Subject' : 'Maths'
},{
'Name' : 'Ajay',
'Marks' : 26,
'Subject' : 'English'
}]
It's basically splitting each object into multiple objects.
Could anyone help me out with an approach to this?
I went with this approach in the end of iterating through the keys and skipping 'name' property.
for (let i of Object.keys(x)) {
let temp = {};
if (i === "Name") {
continue;
} else {
temp = {
Name: x["Name"],
Subject: i,
Marks: x[i],
};
}

You could destructure the common property and map the other entries.
const
convert = ({ Name, ...o }) => Object
.entries(o)
.map(([Subject, Marks]) => ({ Name, Subject, Marks })),
data = { Name: 'Ajay', Maths: 0, English: 26 },
result = convert(data);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

You can use object destructuring to extract name and then process remaining properties. Object.entries gives you key-value pairs:
let x = {
'Name': 'Ajay',
'Maths': 0,
'English': 26,
};
const { Name, ...subjects } = x;
const result = Object.entries(subjects).map(([Subject, Marks]) => ({Name, Subject, Marks}));
console.log(result);

Related

Create an array of objects based on an object if one or more properties have multiple values differentiated by a comma

i'm trying to duplicate objects based on two properties that have multiple values differentiated by a comma.
For example:
I have an object
const obj = {
id: 1
date: "2021"
tst1: "111, 222"
tst2: "AAA, BBB"
}
And I would like the result to be an array of 2 objects in this case (because there are 2 values in tst1 OR tst2, these 2 properties will always have the same nr of values differentiated by a comma)
[{
id: 1,
date: "2021",
tst1: "111",
tst2: "AAA",
},
{
id: 1,
date: "2021",
tst1: "222",
tst2: "BBB",
}]
What I tried is this:
I created a temporary object
const tempObject = {
id: obj.id,
date: obj.date,
}
And then I would split and map the property that has multiple values, like this:
cont newObj = obj.tst1.split(",").map(function(value) {
let finalObj = {}
return finalObj = {
id: tempObject.id,
date: tempObject.date,
tst1: value,
})
And now, the newObj is an array of objects and each object contains a value of tst1.
The problem is I still have to do the same for the tst2...
And I was wondering if there is a simpler method to do this...
Thank you!
Here is an example that accepts an array of duplicate keys to differentiate. It first maps them to arrays of entries by splitting on ',' and then trimming the entries, then zips them by index to create sub-arrays of each specified property, finally it returns a result of the original object spread against an Object.fromEntries of the zipped properties.
const mapDuplicateProps = (obj, props) => {
const splitProps = props.map((p) =>
obj[p].split(',').map((s) => [p, s.trim()])
);
// [ [[ 'tst1', '111' ], [ 'tst1', '222' ]], [[ 'tst2', 'AAA' ], [ 'tst2', 'BBB' ]] ]
const dupeEntries = splitProps[0].map((_, i) => splitProps.map((p) => p[i]));
// [ [[ 'tst1', '111' ], [ 'tst2', 'AAA' ]], [[ 'tst1', '222' ], [ 'tst2', 'BBB' ]] ]
return dupeEntries.map((d) => ({ ...obj, ...Object.fromEntries(d) }));
};
const obj = {
id: 1,
date: '2021',
tst1: '111, 222',
tst2: 'AAA, BBB',
};
console.log(mapDuplicateProps(obj, ['tst1', 'tst2']));
Not sure if that's what you're searching for, but I tried making a more general use of what you try to do:
const duplicateProperties = obj => {
const properties = Object.entries(obj);
let acc = [{}];
properties.forEach(([key, value]) => {
if (typeof value === 'string' && value.includes(',')) {
const values = value.split(',');
values.forEach((v, i) => {
if (!acc[i]) {
acc[i] = {};
}
acc[i][key] = v.trim();
});
} else {
acc.forEach(o => o[key] = value);
}
});
return acc;
};
const obj = {
id: 1,
date: '2021',
tst1: '111, 222',
tst2: 'AAA, BBB',
};
console.log(duplicateProperties(obj));
You could start by determining the length of the result using Math.max(), String.split() etc.
Then you'd create an Array using Array.from(), returning the correct object for each value of the output index.
const obj = {
id: 1,
date: "2021",
tst1: "111, 222",
tst2: "AAA, BBB",
}
// Determine the length of our output array...
const length = Math.max(...Object.values(obj).map(s => (s + '').split(',').length))
// Map the object using the relevant index...
const result = Array.from({ length }, (_, idx) => {
return Object.fromEntries(Object.entries(obj).map(([key, value]) => {
const a = (value + '').split(/,\s*/);
return [key, a.length > 1 ? a[idx] : value ]
}))
})
console.log(result)
.as-console-wrapper { max-height: 100% !important; }

How to group objects by their name

I receive a response like
[
{id:1,name:"type1-something"},
{id:2,name:"something-type2"},
{id:3,name:"type3-something"},
{id:4,name:"something-type1"}
]
and I have an Enum that contains all names from the response
enum E{
E1 = 'type1-something',
E2 = 'something-type2',
E3 = 'type3-something',
E4 = 'something-type1',
}
I need to group response by their names.
For example from the response above I need to transform it in
{
"Type1" : [{id:1,name:"type1-something"},{id:4,name:"something-type1"}],
"Type2" : [{id:2,name:"something-type2"}],
"Type3" : [{id:3,name:"type3-something"}],
}
What approach can be taken? I think of a map and a for loop
if (object.name == E1 || object.name == E4)
MAP['Type1'].push(object)
But I have over 30 entries in the enum and this will approach will become very big and hard to understand. Obviously I can reduce the amount of code by adding some smaller enums
that will include only their type, but I wonder if there is a more obvious way that I do not see
You could get the type and group by this value.
const
getType = ({ name }) => name
.match(/type\d+/g)
?.map(s => s[0].toUpperCase() + s.slice(1))
.join('-') || '',
data = [{ id: 1, name: "type1-something" }, { id: 2, name: "something-type2" }, { id: 3, name: "type3-something" }, { id: 4, name: "something-type1" }, { id: 5, name: "type3-type1-something" }],
result = data.reduce((r, o) => {
(r[getType(o)] ??= []).push(o);
return r;
}, {});
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Reduce Array by combining objects with Matching Key

I have an array of contact cards that hold names & addresses etc for users. What I want to do is create another array that removes any duplicate addresses (ie people in the same household) and creates a name that is a combination of the two. For example:
{flatNum: 1, Name: "ken"},{flatNum: 1, Name: "bob"}, {flatNum: 2, Name: "emma"}
would become:
{flatNum: 1, Name: "ken & bob"}, {flatNum: 2, Name: "emma"}
I know how I can achieve this with a long for loop type thing but was hoping to find a more concise method. I am assuming that reduce would be the key and have been playing around. Currently got this:
let contactCardsComb = contactCards.reduce(function(a,b){
if (a.flatNum == b.flatNum){
return a.Name = a.Name+b.Name;
}
});
Which is obviously horribly wrong but any pointers would be great
You could group by flatNum and get the values from the object.
const
data = [{ flatNum: 1, Name: "ken" }, { flatNum: 1, Name: "bob" }, { flatNum: 2, Name: "emma" }],
result = Object.values(data.reduce((r, { flatNum, Name }) => {
if (r[flatNum]) r[flatNum].Name += ' & ' + Name;
else r[flatNum] = { flatNum, Name };
return r;
}, {}));
console.log(result);
I take the OP's Q. as a chance for proofing that an abstract but (highly) configurable reduce task is in no time (less than a minute, here for the OP's problem) suitable for a variety of problems that at first sight do have nothing in common, due to the different environments and the naming of variables etc.
If one takes e.g. the following approach of this answer to another Q. ... "Segregate an array based on same name") ... some days ago, one ...
just needs to change the implementation (line 24) of how to merge the properties of two to be grouped items, and
just has to provide the correct key name (line 42) of the target value one wants to group around all the other list items.
... code ...
// reduce function that groups
// any data item generically by key.
function groupByKeyAndMergeProperties(collector, item) {
const { merge, key, index, list } = collector;
const groupKey = item[key];
let groupItem = index[groupKey];
if (!groupItem) {
// use `Object.assign` initially in order
// to not mutate the original (list's) reference.
groupItem = index[groupKey] = Object.assign({}, item);
list.push(groupItem);
merge(groupItem, null);
} else {
merge(groupItem, item);
}
return collector;
}
// task specific merge function,
// here, according to the OP's goal.
function mergeItemNames(targetItem, sourceItem) {
if (sourceItem !== null) {
targetItem.Name = `${ targetItem.Name } & ${ sourceItem.Name }`;
}
}
const sampleList = [
{ flatNum: 1, Name: "ken" },
{ flatNum: 1, Name: "bob" },
{ flatNum: 2, Name: "emma" },
{ flatNum: 2, Name: "april" },
{ flatNum: 2, Name: "june" },
{ flatNum: 3, Name: "john" }
];
console.log(
sampleList.reduce(groupByKeyAndMergeProperties, {
// task specific reduce configuration.
merge: mergeItemNames,
key: 'flatNum',
index: {},
list: []
}).list
);
.as-console-wrapper { min-height: 100%!important; top: 0; }
this can be easily accomplished by using lodash library,
let contactCards = [
{flatNum: 1, Name: "ken"},
{flatNum: 1, Name: "bob"},
{flatNum: 2, Name: "emma"},
{flatNum: 2, Name: "april"},
{flatNum: 2, Name: "june"}
];
let tempGroup = _.groupBy(contactCards, contact => {
return contact.flatNum;
});
console.log('tempGroup :', tempGroup);
let contactCardsComb = Object
.values(tempGroup)
.map( e => e.reduce( (a,b) =>
({...a, Name: `${ a.Name } & ${ b.Name }` })
));
console.log('result : ', contactCardsComb);
.as-console-wrapper { min-height: 100%!important; top: 0; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash-compat/3.10.2/lodash.min.js"></script>
// a vanilla approach would be as follows
let flatNums = Array.from(new Set(contactCards.map( e => e.flatNum)))
let groupedContactCards = flatNums
.map( e => contactCards
.filter(contact => contact.flatNum === e))
let reducedContactCards = groupedContactCards
.map( e => e
.reduce((a,b) => ({...a,Name:`${a.Name} & ${b.Name}`})))
console.log(reducedContactCards)
or we can wrap this as below
let reducedContactCards = Array.from(new Set(contactCards.map( e => e.flatNum)))
.map( e => contactCards
.filter(contact => contact.flatNum === e))
.map( e => e.reduce((a,b) => ({...a,Name:`${a.Name} & ${b.Name}`})))
console.log(reducedContactCards)

How to dedupe an array of objects by a key value pair?

// This is a large array of objects, e.g.:
let totalArray = [
{"id":"rec01dTDP9T4ZtHL4","fields":
{"user_id":170180717,"user_name":"abcdefg","event_id":516575,
}]
let uniqueArray = [];
let dupeArray = [];
let itemIndex = 0
totalArray.forEach(x => {
if(!uniqueArray.some(y => JSON.stringify(y) === JSON.stringify(x))){
uniqueArray.push(x)
} else(dupeArray.push(x))
})
node.warn(totalArray);
node.warn(uniqueArray);
node.warn(dupeArray);
return msg;
I need my code to identify duplicates in the array by a key value of user_id within the objects in the array. Right now, my code works to identify identical objects in the array, but I need it to identify dupes based on a key value inside the objects instead. How do I do this? I am struggling to figure out how to path the for each loop to identify the dupe based on the key value instead of the entire object.
Right now, my code works to identify identical objects in the array, but I need it to identify dupes based on a key value inside the objects instead. How do I do this?
Don’t compare the JSON representation of the whole objects then, but only their user_id property specifically.
totalArray.forEach(x => {
if(!uniqueArray.some(y => y.fields.user_id === x.fields.user_id)){
uniqueArray.push(x)
} else(dupeArray.push(x))
})
You could take a Set and push to either uniques or duplicates.
var array = [
{ id: 1, data: 0 },
{ id: 2, data: 1 },
{ id: 2, data: 2 },
{ id: 3, data: 3 },
{ id: 3, data: 4 },
{ id: 3, data: 5 },
],
uniques = [],
duplicates = [];
array.forEach(
(s => o => s.has(o.id) ? duplicates.push(o) : (s.add(o.id), uniques.push(o)))
(new Set)
);
console.log(uniques);
console.log(duplicates);
.as-console-wrapper { max-height: 100% !important; top: 0; }
One way is to keep a list of ids you found so far and act accordingly:
totalArray = [
{ id: 1, val: 10 },
{ id: 2, val: 20 },
{ id: 3, val: 30 },
{ id: 2, val: 15 },
{ id: 1, val: 50 }
]
const uniqueArray = []
const dupeArray = []
const ids = {}
totalArray.forEach( x => {
if (ids[x.id]) {
dupeArray.push(x)
} else {
uniqueArray.push(x)
ids[x.id] = true
}
})
for (const obj of uniqueArray) console.log("unique:",JSON.stringify(obj))
for (const obj of dupeArray) console.log("dupes: ",JSON.stringify(obj))

Turn an object to a square bracket string (not using JSON.stringify)

I have this Javascript object (that is created on-the-fly by my plugin code):
{
"field": {
"name": "Name",
"surname": "Surname"
},
"address": {
"street": "Street",
"number": 0,
"postcode": 0,
"geo": {
"city": "City",
"country": "Country",
"state": "State"
}
},
"options": [1,4,6,8,11]
}
I don't want to turn this object to a JSON string, but I want to turn this object into another object, but with each field represented by a string, like this:
{
"field[name]": "Name",
"field[surname]": "Surname",
"address[street]": "Street",
"address[number]": 0,
"address[postcode]": 0,
"address[geo][city]": "City",
"address[geo][country]": "Country",
"address[geo][state]": "State",
"options[0]":1,
"options[1]":4,
"options[2]":6,
"options[3]":8,
"options[4]":11
}
Scenario:
I dont know how the original object will look like (or how deep it'll be), since it's part of a plugin and I have no idea how people will build their forms
I'm going to put this new object inside a FormData object, if it would only accept objects, it would be easier, because JSON can't upload files, but FormData object can
As I said in the comments, you need a for...in [MDN] loop to iterate over the properties of the object and can use recursion to subsequently convert nested objects:
function convert(obj, prefix, result) {
result = result || {};
// iterate over all properties
for (var prop in obj) {
if (obj.hasOwnProperty(prop)) {
var value = obj[prop];
// build the property name for the result object
// first level is without square brackets
var name = prefix ? prefix + '[' + prop + ']' : prop;
if (typeof value !== 'object') {
// not an object, add value to final result
result[name] = value;
}
else {
// object, go deeper
convert(value, name, result);
}
}
}
return result;
}
// Usage:
var converted_data = convert(data);
DEMO
Still, I would recommend using JSON.
If you want to handle files as well, you might have to add an additional check for File objects. You'd want them raw in the result object:
else if (window.File && value instanceof File) {
result[name] = value;
}
// and for file lists
else if (window.FileList && value instanceof FileList) {
for (var i = 0, l = value.length; i < l; i++) {
result[name + '[' + i + ']'] = value.item(i);
}
}
It could be that the File (FileList) constructor is named differently in IE, but it should give you a start.
Not a big fan of reinventing the wheel, so here is how you could answer your question using object-scan. It's a great tool for data processing - once you wrap your head around it that is.
// const objectScan = require('object-scan');
const convert = (haystack) => objectScan(['**'], {
filterFn: ({ key, value, isLeaf, context }) => {
if (isLeaf) {
const k = key.map((e, idx) => (idx === 0 ? e : `[${e}]`)).join('');
context[k] = value;
}
}
})(haystack, {});
const data = { field: { name: 'Name', surname: 'Surname' }, address: { street: 'Street', number: 0, postcode: 0, geo: { city: 'City', country: 'Country', state: 'State' } }, options: [1, 4, 6, 8, 11] };
console.log(convert(data));
/* =>
{ 'options[4]': 11,
'options[3]': 8,
'options[2]': 6,
'options[1]': 4,
'options[0]': 1,
'address[geo][state]': 'State',
'address[geo][country]': 'Country',
'address[geo][city]': 'City',
'address[postcode]': 0,
'address[number]': 0,
'address[street]': 'Street',
'field[surname]': 'Surname',
'field[name]': 'Name' }
*/
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://bundle.run/object-scan#13.7.1"></script>
Disclaimer: I'm the author of object-scan

Categories