Convert a dictionary into an array keeping the dictionary keys - javascript

I'm trying to find a more elegant way of converting a dictionary of dictionaries to an array of dictionaries and keeping the information of the keys of the original dictionary.
In the case of
let data= { boss: { name:"Peter", phone:"123"},
minion: { name:"Bob", phone:"456"},
slave: { name:"Pat", phone: "789"}
}
I want to come up with something that gives me
output = [ { role:"boss", name:"Peter", phone:"123"},
{ role:"minion", name:"Bob", phone:"456"},
{ role:"slave", name:"Pat", phone:"789"}
]
My solution goes by using the Object.keys method, but I think it's not very efficient. Something tells me I'm going the complex-all-around way and there must be an easier path, but I can't get it:
Object.keys(data)
.map((elem, i) => {
return Object.assign({role: e}, Object.values(data)[i]);
})
Is this the cleanest way to do what I intend to?
Thanks

You could map the entries and pick the key/role for a new object.
let data = { boss: { name: "Peter", phone: "123" }, minion: { name: "Bob", phone: "456" }, slave: { name: "Pat", phone: "789" } },
result = Object.entries(data).map(([role, values]) => ({ role, ...values }));
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Using Object.entries, you can get [key, value] pairs as array and can get teh result you want using Array.map function.
let data = {
boss: { name:"Peter", phone:"123"},
minion: { name:"Bob", phone:"456"},
slave: { name:"Pat", phone: "789"}
};
const result = Object.entries(data).map(([key, value]) => ({
role: key,
...value
}));
console.log(result);

You can use Array.prototype.reduce and Object.entries to loop over every entry in the dictionary and reduce it to an array:
const data = { boss: { name: "Peter", phone: "123" }, minion: { name: "Bob", phone: "456" }, slave: { name: "Pat", phone: "789" } };
function getArray(data) {
return Object.entries(data).reduce((a, [k, v]) => {
a.push({role: k, ...v});
return a;
}, []);
}
console.log(getArray(data));

Related

Applying both replace and filter to an object

I need to filter an object by only taking in results that have ".com" in the string whilst also removing a string from it. I can remove the string and the results go into my DB perfectly however I am struggling with the filter. Is there a way I can call both the replace and filter functions inside the same variable being companyFilter?
Assume the object is:
[{
company: 'amazon'
companyurl:'amazon.com'
}
{
company: '400 prize money'
companyurl:'400 prize money'
}
{
company: 'facebook'
companyurl:'facebook.com'
}
{
company: 'google'
companyurl:'google.com'
}
]
const newObject = data.map((item) => {
const companyNoCom = item['companyurl'].replace(/hackerone.com\//g, "")
//const companyFilter = data.filter((item) => item['company'].includes('.com'))
newItem = {...item,
company: companyNoCom,
}
return newItem
})
console.log(newObject)
The required output would be:
[{
company: 'amazon'
companyurl:'amazon.com'
}
{
company: 'facebook'
companyurl:'facebook.com'
}
company: 'google'
companyurl:'google.com'
}
]
Array.map() ALWAYS returns ALL results but allows changes to each object within the array.
Array.filter() on the other hand ONLY filters and cannot change individual objects within the array and should only return a boolean.
If you need to first make changes to the data in order to determine if it should be included, run .map first, then .filter on the results of map(). The original data will be unchanged and newObject will contain your results:
var newObject = data
.map((item) => {
item['companyurl'] = item['companyurl'].replace(/hackerone.com\//g, "");
return item;
})
.filter((item) => item['companyurl'].includes('.com'));
// Note. the original data array will not be changed.
const data = [
{ company: "amazon", companyurl: "amazon.com" },
{ company: "400 prize money", companyurl: "400 prize money" },
{ company: "facebook", companyurl: "facebook.com" },
{ company: "google", companyurl: "google.com" },
];
const newObject = data.filter((item) => item.companyurl.includes(".com"));
console.log(newObject);
const data = [{
company: 'amazon',
companyurl:'amazon.com'
},
{
company: '400 prize money',
companyurl:'400 prize money'
},
{
company: 'facebook',
companyurl:'facebook.com'
},
{
company: 'google',
companyurl:'google.com'
}
]
const result = data.filter(item => item.companyurl.includes(".com")).map(item => ({...item, company: item.companyurl.split(".com")[0]}))
console.log(result)
EDIT: I made an adjustment to match desired output, so with this one you would always get company value coming from companyurl after striping .com part
you can use String.prototype.endsWith() to check if the end of the string is .com and filter by this:
const arr = [{
company: 'amazon',
companyurl:'amazon.com'
},
{
company: '400 prize money',
companyurl:'400 prize money'
},
{
company: 'facebook',
companyurl:'facebook.com'
},
{
company: 'google',
companyurl:'google.com'
}
];
const res = arr.filter(({ companyurl }) => companyurl.endsWith('.com'));
console.log(res);

Filtering an array of objects with multiple filter conditions

Lets assume I have an array of objects:
let users = [{
name: "Mark",
location: "US",
job: "engineer"
},
{
name: "Mark",
location: "US",
job: "clerk"
},
{
name: "Angela",
location: "Europe",
job: "pilot"
},
{
name: "Matthew",
location: "US",
job: "engineer"
}]
and I have a filter object with all categories I want to filter data against (there can be multiple values per key):
const filters = {
name: ["Mark", "Matthew"],
location: ["US"],
job: ["Engineer"]
}
Based on these filters and data the expected result would return:
[{name: "Mark", location: "US", job: "Engineer"}, {name: "Matthew", location: "US", job: "Engineer"}]
I have tried filtering with:
users.filter(user => {
for(let k in filters) {
if(user[k] === filters[k]) {
return true;
}
}
})
however, this method doesn't take into account that a filter category might contain more than one value which I can handle by doing like:
filters[k][0] or filters[k][1]
but it isn't dynamic.
If anyone has any input that would be much appreciated! Thank you.
Use Object.entries() on filters to get an array of [key, values] pairs. Iterate the pairs with Array.every(), and check that each pair includes the value of the current object.
const fn = (arr, filters) => {
const filtersArr = Object.entries(filters)
return arr.filter(o =>
filtersArr.every(([key, values]) =>
values.includes(o[key])
)
)
}
const users = [{"name":"Mark","location":"US","job":"engineer"},{"name":"Mark","location":"US","job":"clerk"},{"name":"Angela","location":"Europe","job":"pilot"},{"name":"Matthew","location":"US","job":"engineer"}]
const filters = {
name: ["Mark", "Matthew"],
location: ["US"],
job: ["engineer"]
}
const result = fn(users, filters)
console.log(result)
One caveat of using Array.includes() is that differences in case would provide a false answer (Engineer and engineer in this case). To solve that convert the current word to a RegExp, with the Case-insensitive search flag (i), and check using Array.some() if it fits any of the words in the array.
const fn = (arr, filters) => {
const filtersArr = Object.entries(filters)
return arr.filter(o =>
filtersArr.every(([key, values]) => {
const pattern = new RegExp(`^${o[key]}$`, 'i')
return values.some(v => pattern.test(v))
})
)
}
const users = [{"name":"Mark","location":"US","job":"engineer"},{"name":"Mark","location":"US","job":"clerk"},{"name":"Angela","location":"Europe","job":"pilot"},{"name":"Matthew","location":"US","job":"engineer"}]
const filters = {
name: ["Mark", "Matthew"],
location: ["US"],
job: ["Engineer"]
}
const result = fn(users, filters)
console.log(result)
You can loop over the entries of the filters object and ensure that the value of each key is one of the allowed ones.
let users = [{name:"Mark",location:"US",job:"Engineer"},{name:"Mark",location:"US",job:"clerk"},{name:"Angela",location:"Europe",job:"pilot"},{name:"Matthew",location:"US",job:"Engineer"}];
const filters = {
name: ["Mark", "Matthew"],
location: ["US"],
job: ["Engineer"]
};
const res = users.filter(o =>
Object.entries(filters).every(([k,v])=>v.includes(o[k])));
console.log(res);

Iterating through an array of objects and creating a new object with the iterate values

First time posting on here and was hoping to get some help I can't seem to figure out how to do this problem. It's basically to create a function that receives an array of objects that returns a new object.
For some reason, push won't go through and returns the error property of push is undefined.
const organizeInstructors = function(instructors) {
let output = {}; // so the obvious which is to create the object
for (let i = 0; i < instructors.length; i++) {
if (!output[instructors[course]]) {
output[instructors[course]] = instructors[course];
} else {
output[instructors[course]].push(instructors[name]);
}
}
return output;
};
console.log(organizeInstructors([{
name: "Samuel",
course: "iOS"
},
{
name: "Victoria",
course: "Web"
},
{
name: "Karim",
course: "Web"
},
{
name: "Donald",
course: "Web"
}
]));
expected output
{
iOS: ["Samuel"],
Web: ["Victoria", "Karim", "Donald"]
}
Thanks for any advice or hints you guys can give
Looks like you were getting a bit confused iterating over arrays vs keying into objects.
let organizeInstructors = function(instructors) {
let output = {}; // so the obvious which is to create the object
for(let i = 0; i < instructors.length; i++) {
const instructor = instructors[i]
if(!output[instructor.course]) {
output[instructor.course] = []
}
output[instructor.course].push(instructor.name)
}
return output;
}
console.log(organizeInstructors([
{name: "Samuel", course: "iOS"},
{name: "Victoria", course: "Web"},
{name: "Karim", course: "Web"},
{name: "Donald", course: "Web"}
]))
Adding the const instructor makes it much easier to read as well
This uses the Array.prototype.reduce method.
Please note that this will not check if the value is already present in the course array and will just blindly add in. This could mean that you get multiple instances of the same name in the same course.
const organizeInstructors = function(instructors) {
return instructors.reduce((cumulative, current) => {
// if we don't have a course in cumulative object, add it.
if (!cumulative[current.course]) cumulative[current.course] = [];
// add in the current name.
cumulative[current.course].push(current.name);
// return the cumulative object for the next iteration.
return cumulative;
}, {});
}
console.log(organizeInstructors([{
name: "Samuel",
course: "iOS"
},
{
name: "Victoria",
course: "Web"
},
{
name: "Karim",
course: "Web"
},
{
name: "Donald",
course: "Web"
}
]));
Using reduce
data = [ { name: "Samuel", course: "iOS" }, { name: "Victoria", course: "Web" }, { name: "Karim", course: "Web" }, { name: "Donald", course: "Web" }, ];
getObj = (data) =>
data.reduce(
(r, c) => (
!r[c.course] // checks if accumulator doesn't have c.course as key
? ((r[c.course] = []), r[c.course].push(c.name)) // then make an array that corresponds the key then push c.name
: r[c.course].push(c.name), // else push c.name to the corresponding array
r
),
{}
);
console.log(getObj(data));

Given an array of objects, how do you create a new array of objects with different key names while also omitting unwanted data (ES6 way)?

Sorry for the title, It's limited to 150 characters.
Full code example:
https://jsfiddle.net/c81zw30m/
Data:
Let's say I make an API request and I get this JSON object returned:
[
{
id: 123,
person: {
data: {
name: 'John',
language: 'Javascript'
}
},
details: {
age: 25
},
has_experience: true
},
{
id: 456,
person: {
data: {
name: 'Peter',
language: null // here we have null as a value.
}
},
details: {
age: 40
},
has_experience: false
},
{
id: 789,
person: {
data: {
name: 'Paul',
language: 'Python'
}
},
details: {
age: 30
},
has_experience: null // and here we also don't know if the person is available
},
];
Goal:
The end goal here is to iterate over the array and end up with new array of objects with different key names. Say for example I want to replace the key of person with human or the key of available with availability.
Additionally (optionally) we want to skip adding keys which value is equal to null.
Current solution:
let results = [];
for (let i=0; i< json.length; i++) {
results.push({
user_id: json[i].id,
name: json[i].person.data.name,
age: json[i].details.age,
has_experience: json[i].available ? json[i].available : false // here we are assigning a value no matter what using a ternary operator, what if we want no key:value pair here, just skip that pair
});
if (json[i].person.data.language) { results[i].language = json[i].person.data.language }
}
console.log(results);
Problem:
Now the example and solution I provided works, but imagine if the original API request had hundreds of key:value pairs, and many of them might be of null value.
Question:
Using modern javascript, is there any less verbose and more clean looking/elegant way to handle this problem?
Overall I am looking to create a brand new array of objects based on the original one, but with new key names where necessary. Additionally, we want to skip adding some of them if the value of the key is null for example.
Cheers.
EDIT:
Changed the key name from the example originally provided from available to has_experience because it was a bit misleading. I am not looking to filter out the original array of objects based on the value of a given key. If I wanted to do that I'd start with filter and then chain on.
What I want to do is to omit adding a key:value pair in the newly formed array if the value of the key is null for example.
Using lodash (or similar), you could get your mapping definition out of the mapping loop.
I find the following reasonably concise, though it can probably be shortened a little further.
import { get, set } from "lodash";
let json = [ ... ];
let mapping = new Map([
["user_id", "id"],
["name", "person.data.name"],
["age", "details.age"],
["availability", "available"],
["language", "person.data.language"],
["some.nested.property", "person.data.language"]
]);
var results = json.map(element => {
var mappedElement = {};
mapping.forEach((path, field, map) => {
var value = get(element, path);
if (value) {
set(mappedElement, field, value);
}
});
return mappedElement;
});
console.log(results);
Running this on your data yields
[Object, Object, Object]
0: Object
user_id: 123
name: "John"
age: 25
availability: true
language: "Javascript"
some: Object
nested: Object
property: "Javascript"
1: Object
user_id: 456
name: "Peter"
age: 40
2: Object
user_id: 789
name: "Paul"
age: 30
language: "Python"
some: Object
nested: Object
property: "Python"
Working example: https://codesandbox.io/s/mnkp79668
You can try something like this
You can achieve with map()
let json = [{id: 123, person: { data: { name: 'John', language: 'Javascript' } }, details: { age: 25 }, has_experience: true },
{id: 456, person: { data: { name: 'Peter',language: null } }, details: { age: 40 }, has_experience: false},
{id: 789, person: { data: { name: 'Paul', language: 'Python' } }, details: { age: 30 }, has_experience: null },];
let results = [];
results = json.map(current => {
let temp = {
user_id: current.id,
name: current.person.data.name,
age: current.details.age,
}
if (current.has_experience) {
temp.availablity = current.has_experience
}
if (current.person.data.language)
{ temp.language = current.person.data.language }
return temp;
})
console.log(results);
You have two separate problems to resolve. The first appears to be a requirement for generic flattening of the nested data structures within the input with out specifying every possible key that might exist.
This function will recursively flatten a nested object, along the way omitting any null values. However, this function might overwrite any values where the same key exists at multiple levels, so see below.
function flatten(obj, dest) {
for (let key in obj) {
if (typeof obj[key] === 'object') {
flatten(obj[key], dest);
} else if (obj[key] !== null) {
dest[key] = obj[key];
}
}
return dest;
}
You also want to re-map some of the keys in your data, where the below function can be used both as a pre-processor to convert known duplicate keys into unique keys, and can also be used as a post-processor to convert particular keys back into nested objects. NB: requires "lodash".
function remap(obj, keys) {
for (let [in_key, out_key] of keys) {
let val = _.get(obj, in_key, null);
if (val !== null) {
_.unset(obj, in_key);
_.set(obj, out_key, val);
}
}
return obj;
}
The functions can be chained together like this:
let in_map = new Map([
['user.id', 'user_id']
]);
let out_map = new Map([
['available', 'test.availability']
]);
let out = data.map(obj => remap(obj, in_map))
.map(obj => flatten(obj, {}))
.map(obj => remap(obj, out_map));
I think what you want is to first filter the list, then map over the filtered results to create the new structure. This may not be especially performant however if the list is quite large.
const list = [
{
id: 123,
person: {
data: {
name: 'John',
language: 'Javascript'
}
},
details: {
age: 25
},
available: true
},
{
id: 456,
person: {
data: {
name: 'Peter',
language: null // here we have null as a value.
}
},
details: {
age: 40
},
available: false
},
{
id: 789,
person: {
data: {
name: 'Paul',
language: 'Python'
}
},
details: {
age: 30
},
available: null // and here we also don't know if the person is available
},
];
const newList = list.filter(listItem => listItem.available).map(filteredItem => {
return {
user_id: filteredItem.id,
name: filteredItem.person.data.name,
age: filteredItem.details.age,
availability: !!filteredItem.available
}
})
document.getElementById('list').innerText = JSON.stringify(list, null, 2);
document.getElementById('newList').innerText = JSON.stringify(newList, null, 2);
.container {
display: flex;
}
.container pre {
flex: 0 0 50%;
}
<div class="container">
<pre id="list"></pre>
<pre id="newList"></pre>
</div>

How would I write this reportCandidates' function better?

This is the input data in array named candidatesArray:
[
{"name":"george","languages":["php","javascript","java"],"age":19,"graduate_date":1044064800000,"phone":"32-991-511"},
{"name":"anna","languages":["java","javascript"],"age":23,"graduate_date":1391220000000,"phone":"32-991-512"},
{"name":"hailee","languages":["regex","javascript","perl","go","java"],"age":31,"graduate_date":1296525600000,"phone":"32-991-513"}
]
I need to transform in this collection as a result of the function:
{candidates: [
{name: "George", age: 19, phone: "32-991-511"},
{name: "Hailee", age: 31, phone: "32-991-513"},
{name: "Anna", age: 23, phone: "32-991-512"}
],
languages: [
{lang:"javascript",count:1},
{lang:"java", count:2},
{lang:"php", count:2},
{lang:"regex", count:1}
]}
The function repCandidates:
const reportCandidates = (candidatesArray) => {
return repObject}
I need to write it in javascript ES6
I shouldn't use loops(for, while, repeat) but foreach is allowed and it could be better if I use "reduce" function
The candidates should be return by their name, age and phone organized by their graduate_date.
The languages should be returned with their counter in alphabetic order .
Visit https://codepen.io/rillervincci/pen/NEyMoV?editors=0010 to see my code, please.
One option would be to first reduce into the candidates subobject, while pushing the langauges of each to an array.
After iterating, sort the candidates and remove the graduate_date property from each candidate, then use reduce again to transform the languages array into one indexed by language, incrementing the count property each time:
const input = [{
"name": "george",
"languages": ["php", "javascript", "java"],
"age": 19,
"graduate_date": 1044064800000,
"phone": "32-991-511"
}, {
"name": "anna",
"languages": ["java", "javascript"],
"age": 23,
"graduate_date": 1391220000000,
"phone": "32-991-512"
}, {
"name": "hailee",
"languages": ["regex", "javascript", "perl", "go", "java"],
"age": 31,
"graduate_date": 1296525600000,
"phone": "32-991-513"
}];
const output = input.reduce((a, { languages, ...rest }) => {
a.candidates.push(rest);
a.languages.push(...languages);
return a;
}, { candidates: [], languages: [] });
output.candidates.sort((a, b) => a.graduate_date - b.graduate_date);
output.candidates.forEach(candidate => delete candidate.graduate_date);
output.languages = Object.values(
output.languages.reduce((a, lang) => {
if (!a[lang]) a[lang] = { lang, count: 0 };
a[lang].count++;
return a;
}, {})
);
output.languages.sort((a, b) => a.lang.localeCompare(b.lang));
console.log(output);
It's common practice to do everything in a reduce(), but sometimes it's easier to read if you break it up a bit. This creates a counter object as a helper to to track the language counts. map()s over the array to pull out the languages and personal info and then puts it all together:
let arr = [ {"name":"george","languages":["php","javascript","java"],"age":19,"graduate_date":1044064800000,"phone":"32-991-511"},{"name":"anna","languages":["java","javascript"],"age":23,"graduate_date":1391220000000,"phone":"32-991-512"},{"name":"hailee","languages":["regex","javascript","perl","go","java"],"age":31,"graduate_date":1296525600000,"phone":"32-991-513"}]
let lang_counter = {
// helper keeps counts of unique items
counts:{},
add(arr){
arr.forEach(item => this.counts[item] = this.counts[item] ? this.counts[item] + 1 : 1)
},
toarray(){
return Object.entries(this.counts).map(([key, val]) => ({[key]: val}))
}
}
// iterate over object to create candidates
let candidates = arr.map(row => {
let {languages, ...person} = row
lang_counter.add(languages) // side effect
return person
})
// put them together
console.log({candidates, languages:lang_counter.toarray()})
You can use Array.reduce and Object.values like below
let arr = [{"name":"george","languages":["php","javascript","java"],"age":19,"graduate_date":1044064800000,"phone":"32-991-511"},{"name":"anna","languages":["java","javascript"],"age":23,"graduate_date":1391220000000,"phone":"32-991-512"},{"name":"hailee","languages":["regex","javascript","perl","go","java"],"age":31,"graduate_date":1296525600000,"phone":"32-991-513"}]
let res = arr.reduce((o, {name, age, phone, graduate_date, languages}) => {
o.candidates.push({name, age, phone, graduate_date})
languages.forEach(l => {
o.languages[l] = o.languages[l] || { lang:l, count: 0 }
o.languages[l].count++
})
return o
}
, { candidates: [], languages: {}})
res.candidates = res.candidates.sort((a,b) => a.graduate_date - b.graduate_date)
.map(({ graduate_date, ...rest }) => rest)
res.languages = Object.values(res.languages).sort((a,b) => a.lang.localeCompare(b.lang))
console.log(res)

Categories