How to deep find values in the array? [duplicate] - javascript

This question already has answers here:
Accessing nested JavaScript objects and arrays by string path
(44 answers)
Closed 2 years ago.
This is how attributes are defined. The value can be at any level deep, that is defined in element variable. Is there any way I can access deeper values?
const configurations = {
id: "1",
name: {
first: "firstName",
last: "lastName",
},
}
const element = {
attribute: "name.first",
}
const temp = configurations[element.attribute]; // how can I get firstName here
console.log("temp..,",temp); // now temp is undefined

Here you go:
const configurations = {
id: "1",
name: {
first: "firstName",
last: "lastName",
},
}
const element = "name.first";
function getDeep( haystack, needle )
{
if ( typeof needle === 'string' )
needle = needle.split( '.' );
const currentNeedle = needle.shift();
if ( typeof haystack[currentNeedle] !== 'undefined' )
{
if ( needle.length === 0 )
return haystack[currentNeedle];
else
return getDeep( haystack[currentNeedle], needle )
}
else
{
throw new Error( 'Missing key' );
}
}
try
{
console.log( getDeep( configurations, element ) );
}
catch (e) {
console.log( e );
}
Unfortunately there is no easy way to do this using a string like you wanted.

You could do it with a recursive function like so:
const configurations = {
id: "1",
name: {
first: "firstName",
last: "lastName",
},
}
const element = {
attribute: "name.first",
}
const getKey = (el, key) => {
const keyParts = key.split('.');
const currentKey = keyParts.shift();
if(keyParts.length == 0) {
return el[currentKey];
}
return getKey(el[currentKey], keyParts.join('.'));
}
const temp = getKey(configurations, element.attribute); // how can I get firstName here
console.log("temp..,",temp); // now temp is undefined

Related

How can I make a function with two parameters to find if a node exists?

I don't know if there is a method like: object or a map to check in nested objects.
The problem I have is that I have objects inside other objects. :/
I think the deeper the code should go would be:
node.children[1].children[0].children[0].children[0].name;
But I haven't been able to create a function to be recursive in that way.
const rootNode = {
name: "node1",
children: [
{
name: "node2",
children: [
{
name: "node3",
tag: 251,
},
]
},
{
name: "node4",
children: [
{
name: 'node5',
children: [
{
name: "node7",
children: [
{
name: 'node8',
children: [
{
name: "node6"
},
],
},
],
},
],
},
]
},
]
}
// YOU CAN MODIFY THIS PART ONLY
/**
* Search a node by name inside a node
* #param node Any node to start the search in
* #param nodeName The name to search
* #returns undefined when no node is found or the founded node
*/
const searchInNodeByName = (node, nodeName) => {
}
// HERE ENDS WHAT YOU CAN MODIFY
const valueIsRecord = (value) => value !== null
&& typeof value === 'object'
&& !Array.isArray(value)
const node6 = searchInNodeByName(rootNode, 'node6')
if (
node6 === undefined
|| !valueIsRecord(node6)
|| node6.name !== 'node6'
) {
throw new Error('node6 should be found')
}
const node10 = searchInNodeByName(rootNode, 'node10')
if (node10 !== undefined) {
throw new Error('node10 should not be found')
}
const node3 = searchInNodeByName(rootNode, 'node3')
if (
node3 === undefined
|| !valueIsRecord(node3)
|| node3.name !== 'node3'
|| node3.tag !== 251
) {
throw new Error('node3 should be found')
}
console.log('IF YOU SEE THIS ON YOUR CONSOLE, YOU ARE DONE!')
const searchInNodeByName = (node, nodeName) => {
if (node['name'] == nodeName) {
return node;
}
else if (node['children'] != undefined) {
for (let i = 0; i < node['children'].length; i++) {
var checkChild = searchInNodeByName(node['children'][i], nodeName);
if (checkChild !== undefined) {
return checkChild;
}
}
}
return undefined;
}
This function will check the first node given, and return the node if the name equals the nodeName param. Otherwise, if there are children in that node, it will iterate through all children, and perform a recursive function to reach all nodes within the node param, prioritizing the node object.

React extracting a nested json object

How can I extract the 'jobs' object from a nested json list like this:
result:
{
person:
[
{
name: ""
address: ""
jobs: [
{
company:""
},
{
company:""
}
]
}
]
}
Thank you
Write a generic method to extract object properties.
function onExtract(key, data) {
if (isObject(data)) {
for (let item in data) {
if (key === item) {
return data[item];
}
const res = onExtract(key, data[item]);
if (res !== null) return res;
}
}
if (isArray(data)) {
for (let item of data) {
const res = onExtract(key, item);
if (res !== null) return res;
}
}
return null;
}
function isObject(obj) {
return Object.prototype.toString.call(obj) === "[object Object]";
}
function isArray(arr) {
return Object.prototype.toString.call(arr) === "[object Array]";
}
// test
const data = {
person: [
{
name: "",
address: "",
jobs: [
{
company: ""
},
{
company: ""
}
]
}
]
};
console.log(onExtract("jobs", data));
let's say you have a return var that contains this json value
let mappedCompanies = return.person.map(person =>
person.jobs.map(job => job.company)
).flatMap(m => m)
mappedCompanies would contain an array with all the companies names for each one of the registers in "person", all as one array of strings
you can read more about Array.map() here: https://developer.mozilla.org/pt-BR/docs/Web/JavaScript/Reference/Global_Objects/Array/map
A dynamic way to query the person[] and find jobs, is to use the javascript map() method.
Here is the code without comments.
const personsJobs = (personName, personAddress) => {
const jobs = result.person.map((el) => {
if (el.name === personName && el.address === personAddress) {
return el.jobs;
} else {
return null;
}
})
.filter((el) => el !== null);
return jobs;
};
console.log(personsJobs("wyatt", "1234 test ln"));
Here is the code with comments to explain how the personsJob function works.
// Blow is an ES6 arrow function with the parameters 'personName' and 'personAddress',
// which represents the person in which you are querying for jobs (using both a persons
// name and address so in the case of persons with the same name, you only find the jobs
// of the person you want).
const personsJobs = (personName, personAddress) => {
// Since 'person' is an array, we can use the 'map' method as stated before, which
// will create a new array (jobs) that will store the jobs a specific person has.
const jobs = result.person.map((el) => {
// el stands for the current position in the person array.
// if el's (the current person) name and address values are equal to that of the
// parameters personName and personAddress, then that persons jobs are added to the jobs // array, however, if el does not satisfy the two parameters, null is added to the jobs
// array.
// The array, upon completion, will look something like this: ["programmer", null, null]
if (el.name === personName && el.address === personAddress) {
return el.jobs;
} else {
return null;
}
})
// Finally, the filter method is called to remove all null values so that you will
// only have the persons job in the jobs array.
// After filtering, the array will look like this: ["programmer"]
.filter((el) => el !== null);
return jobs;
};
// Prints the array of wyatt's jobs
console.log(personsJobs("wyatt", "1234 test ln"));
So, following the conclusion of the function, you will have dynamically found the jobs of a specific person.
you can use flatMap function like:
const jobsData = result.person.flatMap(item => item.jobs);
Here is a flexible solution using object-scan
// const objectScan = require('object-scan');
const data = { person: [{ name: '', address: '', jobs: [{ company: '' }, { company: '' }] }] };
console.log(objectScan(['person[*].jobs'], { reverse: false, rtn: 'value' })(data));
// => [ [ { company: '' }, { company: '' } ] ]
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://bundle.run/object-scan#14.0.0"></script>
Disclaimer: I'm the author of object-scan

How to output nested object keys separated by a dot - JS [duplicate]

This question already has answers here:
Javascript reflection: Get nested objects path
(3 answers)
Get all paths to a specific key in a deeply nested object
(1 answer)
Get nested objects key as joined string
(2 answers)
Closed 2 years ago.
Code:
const obj = {
client: {
id: 1,
personal: {
name: "Mike"
}
},
address: {
street: "streetname"
}
};
function recursiveKeys(obj) {
Object.keys(obj).forEach((key) => {
if (typeof obj === "object") {
Object.keys(obj[key]).forEach((innerKey) => {
console.log(`${key}.${innerKey}`);
});
}
});
}
recursiveKeys(obj);
Desired output:
client.id
client.personal.name
address.street
This code works only for a 2 level object, but it won't work for a 3rd-4th level and deeper, is there a clean way to achieve this?
You need to make your recursiveKeys actually recursive. Pass along the partial property string from the parent object on each recursive call.
const obj = {
client: {
id: 1,
personal: {
name: "Mike"
}
},
address: {
street: "streetname"
}
};
function recursiveKeys(obj, propStr = '') {
Object.entries(obj).forEach(([key, val]) => {
const nestedPropStr = propStr + (propStr ? '.' : '') + key;
if (typeof val === 'object') recursiveKeys(val, nestedPropStr);
else console.log(nestedPropStr);
});
}
recursiveKeys(obj);
This code works only for a 2 level object, but it won't work for a
3rd-4th level and deeper, is there a clean way to achieve this?
The problem is you should make your recursiveKeys as it is with 3 steps:
Determine the key result named keyRes
Check if the inner content is an object, then recursive it.
Print the keyRes along with getting out of the recursive, important to avoid infinite loop !!!
const obj = {
client: {
id: 1,
personal: {
name: "Mike"
}
},
address: {
street: "streetname"
}
};
function recursiveKeys(obj, previousKey = '') {
Object.entries(obj).forEach(([key, values]) => {
let keyRes = previousKey ? `${previousKey}.${key}` : key; // Step 1
if (typeof values === 'object') // Step 2
recursiveKeys(values, keyRes);
else // Step 3
console.log(keyRes);
});
}
recursiveKeys(obj);
The answer is: recursion!
const obj = {
client: {
id: 1,
personal: {
name: "Mike"
}
},
address: {
street: "streetname"
}
};
function recursiveKeys(obj) {
const keys = []
Object.keys(obj).forEach((key) => {
if (typeof obj[key] === "object" && obj[key]) {
//vvvvvvvvvvvvvvvvvvvvvvv--- the function calls itself
recursiveKeys(obj[key]).forEach(innerKey => {
keys.push(`${key}.${innerKey}`)
})
}else{
keys.push(key)
}
});
return keys
}
console.log(recursiveKeys(obj));
If you have access to the new Array#flatMap() method, you can use it to make this even more elegant:
const obj = {
client: {
id: 1,
personal: {
name: "Mike"
}
},
address: {
street: "streetname"
}
};
function recursiveKeys(obj) {
return Object.keys(obj).flatMap(key =>
typeof obj[key] === "object" && obj[key]
? recursiveKeys(obj[key]).map(innerKey => `${key}.${innerKey}`)
: key
);
}
console.log(recursiveKeys(obj));

Cannot access value of object property

I am looping though an object to retrieve some properties but for some reason I cannot access the value of a nested object property.
This is my looping function:
parseContacts = (contacts) => {
return contacts.map(contact => {
let parsedContact = {};
Object.keys(contact).forEach(key => {
if (key === 'givenName') {
parsedContact.firstName = contact[key];
} if (key === 'familyName') {
parsedContact.surname = contact[key];
} if (key === 'phoneNumbers') {
parsedContact.phoneNumber = contact[key][0].number;
}
})
return parsedContact;
})
}
firstName and surname work fine, but in the last if statement I get undefined. the property with key phoneNumbers it is an array of objects, and this is item 0 in the array:
{id: "302", label: "mobile", number: "+44 7X7X 50XX72"}
When I use this code instead:
} if (key === 'phoneNumbers') {
parsedContact.phoneNumber = contact[key][0];
}
without .number on the end I get the whole object back fine, I just can't get back only the number property from the object.
Update
On closer inspection, the array which has over 800 large objects in, some of the lengths of the phoneNumbers arrays were 0 so the property did not exist. This was causing the whole function to fail. Thanks for the comments below.
My solution was to add to the if statement:
if (key === 'phoneNumbers' && contact[key].length)
You can prevent the empty phoneNumbers array issue like this:
contact[key] && contact[key].length ? contact[key][0].number : ''
const parseContacts = contacts => {
return contacts.map(contact => {
let parsedContact = {}
Object.keys(contact).forEach(key => {
switch (key) {
case 'givenName':
parsedContact.firstName = contact[key]
break
case 'familyName':
parsedContact.surname = contact[key]
break
case 'phoneNumbers':
parsedContact.phoneNumber = contact[key] && contact[key].length ? contact[key][0].number : ''
}
})
return parsedContact
})
}
const contacts = []
for (let i = 0; i < 10; i++) {
contacts.push({
givenName: faker.name.firstName(),
familyName: faker.name.lastName(),
phoneNumbers: [
{
id: faker.random.uuid(),
label: 'mobile',
number: faker.phone.phoneNumber()
}, {
id: faker.random.uuid(),
label: 'mobile',
number: faker.phone.phoneNumber()
}
]
})
}
contacts.push({
givenName: faker.name.firstName(),
familyName: faker.name.lastName(),
phoneNumbers: []
})
contacts.push({
givenName: faker.name.firstName(),
familyName: faker.name.lastName(),
phoneNumbers: null
})
console.log('RESULT ' + JSON.stringify(parseContacts(contacts)))
<script src="https://rawgit.com/Marak/faker.js/master/examples/browser/js/faker.js"></script>

Removing object properties with Lodash

I have to remove unwanted object properties that do not match my model. How can I achieve it with Lodash?
My model is:
var model = {
fname: null,
lname: null
}
My controller output before sending to the server will be:
var credentials = {
fname: "xyz",
lname: "abc",
age: 23
}
I am aware I can use
delete credentials.age
but what if I have lots of unwanted properties? Can I achieve it with Lodash?
You can approach it from either an "allow list" or a "block list" way:
// Block list
// Remove the values you don't want
var result = _.omit(credentials, ['age']);
// Allow list
// Only allow certain values
var result = _.pick(credentials, ['fname', 'lname']);
If it's reusable business logic, you can partial it out as well:
// Partial out a "block list" version
var clean = _.partial(_.omit, _, ['age']);
// and later
var result = clean(credentials);
Note that Lodash 5 will drop support for omit
A similar approach can be achieved without Lodash:
const transform = (obj, predicate) => {
return Object.keys(obj).reduce((memo, key) => {
if(predicate(obj[key], key)) {
memo[key] = obj[key]
}
return memo
}, {})
}
const omit = (obj, items) => transform(obj, (value, key) => !items.includes(key))
const pick = (obj, items) => transform(obj, (value, key) => items.includes(key))
// Partials
// Lazy clean
const cleanL = (obj) => omit(obj, ['age'])
// Guarded clean
const cleanG = (obj) => pick(obj, ['fname', 'lname'])
// "App"
const credentials = {
fname:"xyz",
lname:"abc",
age:23
}
const omitted = omit(credentials, ['age'])
const picked = pick(credentials, ['age'])
const cleanedL = cleanL(credentials)
const cleanedG = cleanG(credentials)
Get a list of properties from model using _.keys(), and use _.pick() to extract the properties from credentials to a new object:
var model = {
fname:null,
lname:null
};
var credentials = {
fname:"xyz",
lname:"abc",
age:23
};
var result = _.pick(credentials, _.keys(model));
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.16.4/lodash.min.js"></script>
If you don't want to use Lodash, you can use Object.keys(), and Array.prototype.reduce():
var model = {
fname:null,
lname:null
};
var credentials = {
fname:"xyz",
lname:"abc",
age:23
};
var result = Object.keys(model).reduce(function(obj, key) {
obj[key] = credentials[key];
return obj;
}, {});
console.log(result);
You can easily do this using _.pick:
var model = {
fname: null,
lname: null
};
var credentials = {
fname: 'abc',
lname: 'xyz',
age: 2
};
var result = _.pick(credentials, _.keys(model));
console.log('result =', result);
<script src="https://cdn.jsdelivr.net/lodash/4.16.4/lodash.min.js"></script>
But you can simply use pure JavaScript (specially if you use ECMAScript 6), like this:
const model = {
fname: null,
lname: null
};
const credentials = {
fname: 'abc',
lname: 'xyz',
age: 2
};
const newModel = {};
Object.keys(model).forEach(key => newModel[key] = credentials[key]);
console.log('newModel =', newModel);
Lodash unset is suitable for removing a few unwanted keys.
const myObj = {
keyOne: "hello",
keyTwo: "world"
}
unset(myObj, "keyTwo");
console.log(myObj); /// myObj = { keyOne: "hello" }
Here I have used omit() for the respective 'key' which you want to remove... by using the Lodash library:
var credentials = [{
fname: "xyz",
lname: "abc",
age: 23
}]
let result = _.map(credentials, object => {
return _.omit(object, ['fname', 'lname'])
})
console.log('result', result)
You can use _.omit() for emitting the key from a JSON array if you have fewer objects:
_.forEach(data, (d) => {
_.omit(d, ['keyToEmit1', 'keyToEmit2'])
});
If you have more objects, you can use the reverse of it which is _.pick():
_.forEach(data, (d) => {
_.pick(d, ['keyToPick1', 'keyToPick2'])
});
To select (or remove) object properties that satisfy a given condition deeply, you can use something like this:
function pickByDeep(object, condition, arraysToo=false) {
return _.transform(object, (acc, val, key) => {
if (_.isPlainObject(val) || arraysToo && _.isArray(val)) {
acc[key] = pickByDeep(val, condition, arraysToo);
} else if (condition(val, key, object)) {
acc[key] = val;
}
});
}
https://codepen.io/aercolino/pen/MWgjyjm
This is my solution to deep remove empty properties with Lodash:
const compactDeep = obj => {
const emptyFields = [];
function calculateEmpty(prefix, source) {
_.each(source, (val, key) => {
if (_.isObject(val) && !_.isEmpty(val)) {
calculateEmpty(`${prefix}${key}.`, val);
} else if ((!_.isBoolean(val) && !_.isNumber(val) && !val) || (_.isObject(val) && _.isEmpty(val))) {
emptyFields.push(`${prefix}${key}`);
}
});
}
calculateEmpty('', obj);
return _.omit(obj, emptyFields);
};
For array of objects
model = _.filter(model, a => {
if (!a.age) { return a }
})
Recursively removing paths.
I just needed something similar, not removing just keys, but keys by with paths recursively.
Thought I'd share.
Simple readable example, no dependencies
/**
* Removes path from an object recursively.
* A full path to the key is not required.
* The original object is not modified.
*
* Example:
* const original = { a: { b: { c: 'value' } }, c: 'value' }
*
* omitPathRecursively(original, 'a') // outputs: { c: 'value' }
* omitPathRecursively(original, 'c') // outputs: { a: { b: {} } }
* omitPathRecursively(original, 'b.c') // { a: { b: {} }, c: 'value' }
*/
export const omitPathRecursively = (original, path, depth = 1) => {
const segments = path.split('.')
const final = depth === segments.length
return JSON.parse(
JSON.stringify(original, (key, value) => {
const match = key === segments[depth - 1]
if (!match) return value
if (!final) return omitPathRecursively(value, path, depth + 1)
return undefined
})
)
}
Working example: https://jsfiddle.net/webbertakken/60thvguc/1/
While looking for a solution that would work for both arrays and objects, I didn't find one and so I created it.
/**
* Recursively ignore keys from array or object
*/
const ignoreKeysRecursively = (obj, keys = []) => {
const keyIsToIgnore = (key) => {
return keys.map((a) => a.toLowerCase()).includes(key)
}
const serializeObject = (item) => {
return Object.fromEntries(
Object.entries(item)
.filter(([key, value]) => key && value)
.reduce((prev, curr, currIndex) => {
if (!keyIsToIgnore(curr[0]))
prev[currIndex] =
[
curr[0],
// serialize array
Array.isArray(curr[1])
? // eslint-disable-next-line
serializeArray(curr[1])
: // serialize object
!Array.isArray(curr[1]) && typeof curr[1] === 'object'
? serializeObject(curr[1])
: curr[1],
] || []
return prev
}, []),
)
}
const serializeArray = (item) => {
const serialized = []
for (const entry of item) {
if (typeof entry === 'string') serialized.push(entry)
if (typeof entry === 'object' && !Array.isArray(entry)) serialized.push(serializeObject(entry))
if (Array.isArray(entry)) serialized.push(serializeArray(entry))
}
return serialized
}
if (Array.isArray(obj)) return serializeArray(obj)
return serializeObject(obj)
}
// usage
const refObject = [{name: "Jessica", password: "ygd6g46"}]
// ignore password
const obj = ignoreKeysRecursively(refObject, ["password"])
// expects returned array to only have name attribute
console.log(obj)
let asdf = [{"asd": 12, "asdf": 123}, {"asd": 121, "asdf": 1231}, {"asd": 142, "asdf": 1243}]
asdf = _.map(asdf, function (row) {
return _.omit(row, ['asd'])
})

Categories

Resources