How to find a property in a nested object - javascript

I'm trying to figure out how to extract result based on an object property value that is inside another object e.g
{
title: 'The Book',
author: {
name: 'Merlin Bridge',
category: 'Fiction'
}
}
Here are my tests:
// this works
const test1 = _.find(existingBooks, {
title: uploadedBooks[i].title,
});
// this doesn't work and returns nothing
const test2 = _.find(existingBooks, {
'author.name': uploadedBooks[i].author.name,
});
I want to get the result wherein the author.name is the identifier. Thanks a lot.

As you are using Lodash/Underscore find, I took the liberty to also use the get helper. Please find below an answer which should help you.
const books = [{
title: 'The Book',
author: {
name: 'Merlin Bridge',
category: 'Fiction'
}
},
{
title: 'Another Book',
author: {
name: 'Merlin Window',
category: 'Science'
}
}
];
const uploadedBooks = [{
title: 'Another Book',
author: {
name: 'Merlin Window',
category: 'Science'
}
}];
const i = 0;
const foundBook = _.find(books, (book, index) => {
return _.get(book, 'author.name') === _.get(uploadedBooks, [i, 'author', 'name'])
});
console.log(foundBook);
<script src="https://cdn.jsdelivr.net/npm/lodash#4.17.10/lodash.min.js"></script>

You can Use below code to iterate your json object.
var myObj = {
title: 'The Book',
author: {
name: 'Merlin Bridge',
category: 'Fiction'
}
}
Object.keys(myObj).forEach(key => {
console.log(JSON.stringify(myObj[key]));
});

It's possible to add dynamically named properties to JavaScript objects.
Assuming you are looping an array and not an object:
var a = _.map(existingBooksArray, function(existingBook) {
return {[existingBook.author.name]: existingBook.title}
});
Result of a:
[
{
Merlin Bridge: "The Book"
}
]

use .key method
Object.keys(myObj).forEach(key => {
console.log(myObj[key]);});
var myObj = {
title: 'The Book',
author: {
name: 'Merlin Bridge',
category: 'Fiction'
}
}
Object.keys(myObj).forEach(key => {
console.log(JSON.stringify(myObj[key]));
});

var myObj = {
title: 'The Book',
author: {
name: 'Merlin Bridge',
category: 'Fiction'
}
}
Object.keys(myObj).forEach(key => {
console.log(JSON.stringify(myObj[key]));
});

Related

Destructuring object consist of deep nested object of arrays

I'm trying to destructure setup shown below. I need to get text: Zelena The Wicked Witch is an enemy to Emma Swan in Once Upon a Time.
OK, it's easy to get properties from objects info and protagonist, but I can't get data from an array of objects (enemies) for particular object, for instance for row number 3.
I tried many different expressions, with no luck.
Any help would be highly appreciated.
function nestedArrayAndObject() {
// refactor this to a single line of destructuring...
const info = {
title: 'Once Upon a Time',
protagonist: {
name: 'Emma Swan',
enemies: [
{name: 'Regina Mills', title: 'Evil Queen'},
{name: 'Cora Mills', title: 'Queen of Hearts'},
{name: 'Peter Pan', title: `The boy who wouldn't grow up`},
{name: 'Zelena', title: 'The Wicked Witch'},
],
},
}
const {
protagonist: {
enemies[3]: {name: enemyName}
},
protagonist: {
enemies: {title: enemyTitle}
},
protagonist: {name: protagonistName},
title: title
} = info;
return `${enemyName} (${enemyTitle}) is an enemy to ${protagonistName} in "${title}"`
}
nestedArrayAndObject();
It's not that hard. Take a look at this.
const info = {title: 'Once Upon a Time', protagonist: {name: 'Emma Swan', enemies: [ {name: 'Regina Mills', title: 'Evil Queen'}, {name: 'Cora Mills', title: 'Queen of Hearts'}, {name: 'Peter Pan', title: 'The boy who wouldn\'t grow up'}, {name: 'Zelena', title: 'The Wicked Witch'} ]}};
const {protagonist: {enemies: [,, {name, title}]}} = info;
console.log(name, title)
You can achieve as:
const {
title,
protagonist: {
name: protagonistName,
enemies: [, , , { name: enemyName, title: enemyTitle }],
},
} = info;
function nestedArrayAndObject() {
// refactor this to a single line of destructuring...
const info = {
title: 'Once Upon a Time',
protagonist: {
name: 'Emma Swan',
enemies: [
{ name: 'Regina Mills', title: 'Evil Queen' },
{ name: 'Cora Mills', title: 'Queen of Hearts' },
{ name: 'Peter Pan', title: `The boy who wouldn't grow up` },
{ name: 'Zelena', title: 'The Wicked Witch' },
],
},
};
const {
title,
protagonist: {
name: protagonistName,
enemies: [, , , { name: enemyName, title: enemyTitle }],
},
} = info;
return `${enemyName} (${enemyTitle}) is an enemy to ${protagonistName} in "${title}"`;
}
nestedArrayAndObject();

forEach loop returns undefined value

I've dummy data (books) which I want see from graphiql gui. But when I use a forEach loop to iterate through the books, looking for a specific id, it's returning undefined values, but if I use a normal for loop it works fine.
This is my code:
let books = [
{ name: 'Name of the Wind', genre: 'Horror', id: '1', authorID: '3' },
{ name: 'The Final Empire', genre: 'Fantasy', id: '2', authorID: '1' },
{ name: 'The Long Earth', genre: 'Sci-Fi', id: '3', authorID: '2' },
];
const RootQuery = new GraphQLObjectType({
name: 'RootQueryType',
fields: {
book: {
type: BookType,
args: { id: { type: GraphQLString } },
//this forEach is not working
resolve(parent, args){
books.forEach( function(book) {
if(book.id == args.id) {
console.log(book);
return book;
}
});
}
}
}
});
When I print out the book data, it's showing that particular book in the console but not in the GUI response:
request:
{
book(id: "2") {
name
genre
}
}
response:
{
"data": {
"book": null
}
}
A return <value> in a forEach callback is meaningless. The returned value goes nowhere, and the loop is not interrupted either.
Instead use .find:
return books.find(function(book) {
return book.id == args.id;
});
When performance is important, and you have lots of books, then it is better to first preprocess the books and create a Set:
let books = [
{ name: 'Name of the Wind', genre: 'Horror', id: '1', authorID: '3' },
{ name: 'The Final Empire', genre: 'Fantasy', id: '2', authorID: '1' },
{ name: 'The Long Earth', genre: 'Sci-Fi', id: '3', authorID: '2' },
];
let bookIds = new Set(books.map(({id}) => id));
... and then no loop is needed to know whether a book ID is valid or not:
return bookIds.has(args.id);

How to group an array based on a key and create a dynamic object with grouped array as values?

I've got multiple items, some of them with the same title. I want to create an multidimensional array with the title as the first key and a unique number as the second key. So it's possible to categorize the items by title.
Example:
itemArray['title1'][0] = item1
itemArray['title1'][1] = item2
itemArray['title2'][0] = item3
My example is working with this code, but in my opinion it's to complicated and I hope there is an other way with JavaScript.
let itemArray = {}
items.forEach(item => {
let title = item['title']
if (itemArray[title] == null) {
itemArray[title] = {}
}
let index = Object.keys(itemArray[title]).length
itemArray[title][index] = item
},
)
The Input:
[ RowDataPacket {
uid: 1,
title: 'booktitle',
description: 'description' },
RowDataPacket {
uid: 2,
title: 'booktitle',
description: 'description 2' },
RowDataPacket {
uid: 1,
title: 'booktitle 2',
description: 'description' } ]
Expected output (Since it's the result of a sql query the item is a RowDataPacket):
{ 'booktitle':
{ '0':
RowDataPacket {
uid: 1,
title: 'booktitle',
description: 'description' } },
{ '1':
RowDataPacket {
uid: 2,
title: 'booktitle',
description: 'description 2' } },
'booktitle 2':
{ '0':
RowDataPacket {
uid: 1,
title: 'booktitle 2',
description: 'description' } }
}
It's easy with PHP, there it's working like this:
$itemArray = array();
foreach ($items as $key => $item) {
$itemArray[$item['title']][] = $item;
}
Thanks in advance!
You could reduce the array by taking a default array and push the item.
var items = [{ title: 'title1' }, { title: 'title1' }, { title: 'title2' }],
result = items.reduce((r, item) => {
(r[item.title] = r[item.title] || []).push(item);
return r;
}, {});
console.log(result);
You've got the correct idea. The itemArray you created is not a multidimensional array. It's an object with each title as key and an array of items which share the same title as their value. You could probably simplify your forEach like this:
var items = [{
uid: 1,
title: 'booktitle',
description: 'description'
}, {
uid: 2,
title: 'booktitle',
description: 'description 2'
}, {
uid: 1,
title: 'booktitle 2',
description: 'description'
}]
let itemArray = {}
items.forEach(item => {
itemArray[item.title] = itemArray[item.title] || [];
itemArray[item.title].push(item)
})
console.log(itemArray)
Checck if itemArray already has the title as a key. If yes, use it. Else, point it to an empty array []. Then just push the current item to that property.
With reduce, you can even simplify it to:
var items=[{uid:1,title:'booktitle',description:'description'},{uid:2,title:'booktitle',description:'description 2'},{uid:1,title:'booktitle 2',description:'description'}]
let itemArray = items.reduce((acc,i) =>
((acc[i.title] = acc[i.title] || []).push(i), acc)
,{})
console.log(itemArray)
Expected output
{ 'booktitle':
{ '0':
RowDataPacket {
uid: 1,
title: 'booktitle',
description: 'description' } },
{ '1':
RowDataPacket {
uid: 2,
title: 'booktitle',
description: 'description 2' } },
'booktitle 2':
{ '0':
RowDataPacket {
uid: 1,
title: 'booktitle 2',
description: 'description' } }
}
That's a bad practice. Don't use enumerated properties on Objects. You see how cumbersome they are. Unlike PHP, JS has no associative Arrays; so using Objects for that is the right equivalent. But for indexed Arrays you should use Arrays in JS, not Objects.
var data = [{
uid: 1,
title: 'booktitle',
description: 'description'
},
{
uid: 2,
title: 'booktitle',
description: 'description 2'
},
{
uid: 1,
title: 'booktitle 2',
description: 'description'
}
];
const booksByTitle = {};
for (const item of data) {
const {
title
} = item;
if (!(title in booksByTitle)) booksByTitle[title] = [];
booksByTitle[title].push(item);
}
console.log(booksByTitle);
.as-console-wrapper{top:0;max-height:100%!important}
but there are also plenty of frameworks in JS that offer some groupBy method, something like this quick implementation:
var data = [{
uid: 1,
title: 'booktitle',
description: 'description'
},
{
uid: 2,
title: 'booktitle',
description: 'description 2'
},
{
uid: 1,
title: 'booktitle 2',
description: 'description'
}
];
function groupBy(iterable, keyOrFn) {
const fn = typeof keyOrFn === "function" ?
keyOrFn :
(item) => item == null ? undefined : item[keyOrFn];
var result = Object.create(null);
for (const item of iterable) {
const key = fn(item);
if (key in result) {
result[key].push(item);
} else {
result[key] = [item];
}
}
return result;
}
const booksByTitle = groupBy(data, "title");
console.log(booksByTitle);
.as-console-wrapper{top:0;max-height:100%!important}

Assigning the key of the object as a value inside that object

In lodash we have the _mapKeys method that I'm using like this:
Here is an array of objects:
const posts = [
{
id: 123,
title: 'Hello',
},
{
id: 321,
title: 'World',
},
];
From there I use the method like so:
const postsObjWithKeys = _.mapKeys(posts, 'id');
console.log(postsObjWithKeys);
And the result is:
{
123: {id: 123, title: 'Hello'},
321: {id: 321, title: 'World'}
}
Perfect! Exactly what I want!
The question is how would I achieve this in 'reverse'? Meaning if I had an object like this:
const posts = {
123: {
title: 'Hello'
},
321: {
title: 'World'
}
}
Is it possible to take the key and assign it as a value if id? The end result being:
const posts = {
123: {
id: 123,
title: 'Hello'
},
321: {
id: 321,
title: 'World'
}
}
With lodash you can achieve the reverse using _.mapValues():
const posts = {
123: {
title: 'Hello'
},
321: {
title: 'World'
}
}
const result = _.mapValues(posts, (value, id) => _.assign({}, value, { id }));
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>
Just iterate all the keys and add them as id on the Object:
const posts = {
123: {
title: 'Hello'
},
321: {
title: 'World'
}
}
Object.keys(posts).forEach(k=>{
posts[k].id = k;
});
console.log(posts);
/*
const posts = {
123: {
id: 123,
title: 'Hello'
},
321: {
id: 321,
title: 'World'
}
}
*/

Lodash - Group Children using N (repeated) keys for parent

I have a select from database that basically joins a master entity and a child entity, like the example below (Cars vs Parts) as snippet
And I'd like to group by all the keys for the Car part, and have an array of the parts, but including all the keys for the car and the parts. For the groupBy examples I could find, generally it uses groupBy, but it only groups one key only. I was able to achieve using a lot of workarounds, but I'm sure it is manageable (and achieve more performance) to do the same using either es6 or lodash.
Could someone help me in this matter? I've tried multiple groupBy and reduce combinations, but was not able to chain those correctly.
var data = [{id: 'car1',
name: 'name for car 1',
description: 'description for car1',
partId: 'partId1',
partName: 'partName1'},
{id: 'car1',
name: 'name for car 1',
description: 'description for car1',
partId: 'partId2',
partName: 'partName2'},
{id: 'car2',
name: 'name for car 2',
description: 'description for car2',
partId: 'partId3',
partName: 'partName3'},
{id: 'car2',
name: 'name for car 2',
description: 'description for car2',
partId: 'partId4',
partName: 'partName4'}
];
var dictionary = {};
data.forEach(function(item, index, array)
{
var masterDocument = null;
if (typeof dictionary[item.id] === 'undefined')
{
masterDocument = {
id: item.id,
name: item.name,
description: item.description,
parts: []
};
dictionary[item.id] = masterDocument;
}
else {
var masterDocument = dictionary[item.id];
}
masterDocument.parts.push({
partId: item.partId,
partName: item.partName
})
})
var asList = [];
Object.keys(dictionary).forEach((item) => {
asList.push(dictionary[item])
});
console.log(asList);
.as-console-wrapper {
min-height: 100%;
top: 0;
}
Here's the snippet with just the result I want to achieve.
[
{
"id": "car1",
"name": "name for car 1",
"description": "description for car1",
"parts": [
{
"partId": "partId1",
"partName": "partName1"
},
{
"partId": "partId2",
"partName": "partName2"
}
]
},
{
"id": "car2",
"name": "name for car 2",
"description": "description for car2",
"parts": [
{
"partId": "partId3",
"partName": "partName3"
},
{
"partId": "partId4",
"partName": "partName4"
}
]
}
]
The code below should solve your problem using Lodash. Basically what you want to do is:
Group the cars by id
Once you have the cars grouped by their IDs, iterate over that top-level array with a map call, and grab the id, name, and description from the first entry (since you know these are all the same for all cars in this group). Save these for later for your return object
Then, while still in this top-level map iteration, also iterate over the individual cars in each carGrouping (a nested map) to get their partId and partName, and put those into a parts array
Finally, get all of your object attributes, put them all into a return object in your top-level map call, and return them all back
Don't forget to call valueOf() at the end of your chain to get the Lodash sequence to fire
let data = [{id: 'car1',
name: 'name for car 1',
description: 'description for car1',
partId: 'partId1',
partName: 'partName1'},
{id: 'car1',
name: 'name for car 1',
description: 'description for car1',
partId: 'partId2',
partName: 'partName2'},
{id: 'car2',
name: 'name for car 2',
description: 'description for car2',
partId: 'partId3',
partName: 'partName3'},
{id: 'car2',
name: 'name for car 2',
description: 'description for car2',
partId: 'partId4',
partName: 'partName4'}
];
const carsInfo = _(data)
.groupBy('id')
.map(carGrouping => {
// all cars in this array have the same id, name, description, so just grab them from the first one
const firstCarInGroup = _.first(carGrouping);
const id = firstCarInGroup.id;
const name = firstCarInGroup.name;
const description = firstCarInGroup.description;
// do a nested map call to iterate over each car in the carGrouping, and grab their partId and partName, and return it in an object
const parts = _.map(carGrouping, car => {
return {
partId: car.partId,
partName: car.partName
}
});
return {
id,
name,
description,
parts
}
})
.valueOf();
console.log(carsInfo);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.js"></script>
This one uses no dependencies. Just plain ES6+.
const data = [{
id: 'car1',
name: 'name for car 1',
description: 'description for car1',
partId: 'partId1',
partName: 'partName1'
},
{
id: 'car1',
name: 'name for car 1',
description: 'description for car1',
partId: 'partId2',
partName: 'partName2'
},
{
id: 'car2',
name: 'name for car 2',
description: 'description for car2',
partId: 'partId3',
partName: 'partName3'
},
{
id: 'car2',
name: 'name for car 2',
description: 'description for car2',
partId: 'partId4',
partName: 'partName4'
}
];
const nested = data.reduce((acc, part) => {
let index = acc.findIndex(car => car.id === part.id)
const { partId, partName, ...car } = part
if (index === -1) {
acc.push({
...car,
parts: [],
})
index = acc.length - 1
}
acc[index].parts.push({
partId,
partName,
})
return acc
}, [])
console.log(JSON.stringify(nested, null, ' '));

Categories