Input:
{
"mobile": "Mob # Required",
"client": [
undefined,
null,
{
"usergroup": "Required"
},
{
"id": "Required",
"usergroup": "Required"
},
{
"id": "Required",
"usergroup": "Required"
}
]
}
Expected Output:
[
"mobile",
"client.2.usergroup",
"client.3.id",
"client.3.usergroup",
"client.4.id",
"client.4.usergroup"
]
I am using Formiks FieldArray in my project & the field name in error object is not what is expected.
Object.Keys() doesn't work well for such scenario.
You can flatMap the keys of the object. If the current key's value is an object, recursively call getKeys function with the updated prefix. If not, return the current key with the given provided prefix. Use flatMap to get a flattened array of keys instead of nested arrays
const input={mobile:"Mob # Required",client:[{usergroup:"Required"},{id:"Required",usergroup:"Required"},{id:"Required",usergroup:"Required"}]};
const getKeys = (o, prefix = '') =>
Object.keys(o).flatMap(k =>
Object(o[k]) === o[k] ? getKeys(o[k], `${prefix}${k}.`) : [prefix + k]
)
console.log(getKeys(input))
If flatMap is not supported, you can reduce the keys of the object with similar logic
const input={mobile:"Mob # Required",client:[{usergroup:"Required"},{id:"Required",usergroup:"Required"},{id:"Required",usergroup:"Required"}]};
function getKeys(o, prefix = '') {
return Object.keys(o).reduce((acc, k) => {
if (Object(o[k]) === o[k])
acc.push(...getKeys(o[k], `${prefix}${k}.`))
else
acc.push(prefix + k)
return acc;
}, [])
}
console.log(getKeys(input))
I'm sure there are libraries out there to do this for you, but here is what I came up with:
function flattenNestedObject(input, path) {
path = path || [];
return Object.keys(input).reduce(function(arr, key) {
if (input[key] && typeof input[key] === "object") {
return arr.concat(flattenNestedObject(input[key], path.concat(key)));
}
if (typeof input[key] !== 'undefined' && input[key] !== null) {
return arr.concat((path.concat(key).join(".")));
}
return arr;
}, []);
}
https://codesandbox.io/s/sweet-jang-j51dh
The reason Object.keys will not work for you is that it doesn't recursively obtain all keys of an object. Also, in your expected output, you don't want all the keys of an object if it contains nested Arrays or Objects.
Related
I'm trying to search an array of objects with objects that are nested in, so for example i have this array:
[
{
website: 'Stackoverflow',
info: {
"extension": "com",
"ssl": true
}
},
{
website: 'Faceoobok',
info: {
"extension": "com",
"ssl": true
}
}
]
So I want to search all fields, and then also search the object inside and return an array with the method filter, also the char cases won't matter, it needs to return the object in the array even for example Stackoverflow is not the same as stackoverflow with the casing methods that come with JS.
Here is what I've tried, and It searches the objects and returns them but It doesn't search the object inside, what I mean is for example it searchs the website, but not the .info:
const searchMachine = (arr, query) => {
let queryFormatted = query.toLowerCase();
return arr.filter((obj) =>
Object.keys(obj).some((key) => {
if (typeof obj[key] === 'string') {
return obj[key]
.toLowerCase()
.includes(queryFormatted);
}
return false;
})
);
You could take a closure over the wanted string and use a recursive approach for objects.
const
searchMachine = (array, query) => {
const
check = (query => object => Object
.values(object)
.some(value =>
typeof value === 'string' && value.toLowerCase().includes(query) ||
value && typeof value === 'object' && check(value)
))(query.toLowerCase());
return array.filter(check);
},
data = [{ website: 'Stackoverflow', info: { extension: 'com', ssl: true } }, { website: 'Faceoobok', info: { extension: 'com', ssl: true } }];
console.log(searchMachine(data, 'stack'));
console.log(searchMachine(data, 'com'));
You can split the task in two step. The first one is to get all string in the object.
function getAllStrings(obj) {
if(typeof obj === 'object'){
return Object.values(obj).flatMap(v => getAllStrings(v));
}else if (typeof obj === 'string'){
return [obj];
}
return [];
}
And the second one is to filter.
const searchMachine = (arr, query) => {
const queryFormatted= query.toLowerCase();
return getAllStrings(arr).filter(s => s.toLowerCase().includes(queryFormatted));
}
You can reuse the Object.keys.some(...) code you used to search in the object, to search in object.info.
First make a function of it that lets us pass in the object:
const findInObject = (obj) =>
Object.keys(obj).some((key) => {
if (typeof obj[key] === 'string') {
return obj[key]
.toLowerCase()
.includes(queryFormatted);
}
return false;
});
Then call it within arr.filter. findInObject(obj) is your original logic, and check for the presence of obj.info and then call findInObject on obj.info
...
return arr.filter((obj) =>
findInObject(obj) || obj.info && findInObject(obj.info)
);
...
I have data that looks like the following:
[
{
"_sourceAddresses":[
{
"_street1":"957 Heathcote Unions",
"_city":"Matteoside",
"_state":"Hawaii",
"_postalCode":"69680",
"_postalCodePlusFour":"7715",
"_country":"USA",
"_type":0,
"_updatedAt":"1991-03-10T22:34:27.000Z",
"_createdAt":"1970-07-24T09:34:12.000Z"
}
],
"_emails":[
{
"_address":"labadie.gwendolyn#gmail.com",
"_primary":true
}
],
"_phoneNumbers":[
{
"_number":"4612902836",
"_type":0,
"_carrier":"AT&T"
}
],
"_customFields":{
},
"_active":true,
"_firstName":"Haven",
"_lastName":"Runolfsdottir",
"_gender":"M",
"_sourceIndividualId":"c1126d05-0e5b-4da1-8535-e1061d4163ee",
"_sourceCampusId":"ae1e70d5-d8bf-4942-b9ea-3da5765e055f",
"_primaryContact":true,
"_salutation":"Mrs.",
"_suffix":"DDS",
"_birthDate":"1989-02-16T10:06:25.000Z"
},
{
"_sourceAddresses":[
{
"_street1":"5910 Langosh Burgs Apt. 281",
"_city":"West Katheryn",
"_state":"Arkansas",
"_postalCode":"49571",
"_postalCodePlusFour":null,
"_country":"USA",
"_type":0,
"_updatedAt":"1984-01-09T09:34:02.000Z",
"_createdAt":"1986-01-13T17:36:41.000Z"
}
],
"_emails":[
{
"_address":"labadie_cristopher#yahoo.com",
"_primary":true
}
],
"_phoneNumbers":[
{
"_number":"0608405498",
"_type":0,
"_carrier":"Verizon"
}
],
"_customFields":{
},
"_active":true,
"_firstName":"Andreane",
"_lastName":"Kerluke",
"_gender":"F",
"_sourceIndividualId":"0726bfc2-56af-4e46-90ef-c0a286404334",
"_sourceCampusId":"86fdb656-7e29-4ace-a1c7-149db81c7f5e",
"_primaryContact":true,
"_salutation":"Mrs.",
"_suffix":null,
"_birthDate":"1979-11-14T10:07:02.000Z"
}
]
When it is saved as JSON, I'd like to remove the underscores in the keys. Is there an easy way to do this?
I have tried unsuccessfully to adapt this code to accomplish it:
Replace dot to underscore in js object keys names
function removeLeadingUnderscores(obj) {
_.forOwn(obj, (value, key) => {
if (_.startsWith("_")) {
const cleanKey = _.substring(1)
obj[cleanKey] = value;
delete obj[key];
}
// continue recursively looping through if we have an object or array
if (_.isObject(value)) {
return removeLeadingUnderscores(value);
}
});
return obj;
}
Since you're planning to save as JSON already, you can use its naturally recursive nature with its reviver parameter to return objects without the underscores. Map the entries of the object to a new object without the leading _.
const arr=[{_sourceAddresses:[{_street1:"957 Heathcote Unions",_city:"Matteoside",_state:"Hawaii",_postalCode:"69680",_postalCodePlusFour:"7715",_country:"USA",_type:0,_updatedAt:"1991-03-10T22:34:27.000Z",_createdAt:"1970-07-24T09:34:12.000Z"}],_emails:[{_address:"labadie.gwendolyn#gmail.com",_primary:!0}],_phoneNumbers:[{_number:"4612902836",_type:0,_carrier:"AT&T"}],_customFields:{},_active:!0,_firstName:"Haven",_lastName:"Runolfsdottir",_gender:"M",_sourceIndividualId:"c1126d05-0e5b-4da1-8535-e1061d4163ee",_sourceCampusId:"ae1e70d5-d8bf-4942-b9ea-3da5765e055f",_primaryContact:!0,_salutation:"Mrs.",_suffix:"DDS",_birthDate:"1989-02-16T10:06:25.000Z"},{_sourceAddresses:[{_street1:"5910 Langosh Burgs Apt. 281",_city:"West Katheryn",_state:"Arkansas",_postalCode:"49571",_postalCodePlusFour:null,_country:"USA",_type:0,_updatedAt:"1984-01-09T09:34:02.000Z",_createdAt:"1986-01-13T17:36:41.000Z"}],_emails:[{_address:"labadie_cristopher#yahoo.com",_primary:!0}],_phoneNumbers:[{_number:"0608405498",_type:0,_carrier:"Verizon"}],_customFields:{},_active:!0,_firstName:"Andreane",_lastName:"Kerluke",_gender:"F",_sourceIndividualId:"0726bfc2-56af-4e46-90ef-c0a286404334",_sourceCampusId:"86fdb656-7e29-4ace-a1c7-149db81c7f5e",_primaryContact:!0,_salutation:"Mrs.",_suffix:null,_birthDate:"1979-11-14T10:07:02.000Z"}];
const stringified = JSON.stringify(
arr,
(_, value) => {
return value && typeof value === 'object' && !Array.isArray(value)
? Object.fromEntries(
Object.entries(value)
.map(([key, value]) => [key.slice(1), value])
)
: value;
}
);
console.log(stringified);
If some properties don't start with _, you can change .slice(1) to .replace(/^_/, '').
Here's a simplified version of saving the object with removed underscores through simple recursive logic:
let savedJson: any = [];
renamingArray(obj); // obj is your object
function renamingArray(element: any){
for(let element of obj)
if (Object.prototype.toString.call(element) === '[object Array]') {
renamingArray(element);
else
renamingObject(element);
}
}
function renamingObject(obj: any){
let keys = Object.keys(obj)
for(let objectKey of keys){
savedJson.push({ [objectKey.substring(1)]: obj[objectKey] });
}
}
console.log(savedJson)
Lets just prefix this for the moderators this question is not about nested Key's. This is about an array in an object and how to see if its a simple array with only values or if it is an array which holds objects and how to get the keys of these objects. I included code snipped which parses the sample and detects the 2 Array's. What i am looking for is to return only name of array if the array is simple array with only a list of values. If the array is array of objects i would like to get the keys of that object in array.
obj = {
DocId: "email_campaign::3ed76589-4063-49f6-a21e-9ca16981d102",
history: {
created_by: "",
created_on: "",
update_on: "",
updated_by: ""
},
librarys :[{id: 1, name : 'Lib 1'},{ id: 2, name: 'Lib 2'}],
status: "Active",
subject: "Test 1 Subject",
summary: "",
tags: ['one', 'two'],
template_id: ""
};
const keyify = (obj, prefix = '') =>
Object.keys(obj).reduce((res, el) => {
if( Array.isArray(obj[el]) ) {
// Here needs to go the Array Part
console.log(el + ' is Array')
return [...res, el];
} else if( typeof obj[el] === 'object' && obj[el] !== null ) {
return [...res, ...keyify(obj[el], prefix + el + '.')];
} else {
return [...res, el];
}
}, []);
const output = keyify(obj);
console.log(output);
Assuming you want the names of arrays prefixed similarly to the existing prefix for objects, you could try this:
const keyify = (obj, prefix = "") =>
Object.keys(obj).reduce((res, el) => {
const elDisplayName = `${prefix}${el}`;
if (Array.isArray(obj[el])) {
const objectsInArray = obj[el].filter(el => typeof el === "object");
if (objectsInArray.length > 0) {
let objectKeys = [];
objectsInArray.map(object => {
objectKeys = objectKeys.concat(keyify(object, prefix + el + "."))
});
return [...res, ...new Set(objectKeys)];
}
return [...res, elDisplayName];
} else if (typeof obj[el] === "object" && obj[el] !== null) {
return [...res, ...keyify(obj[el], prefix + el + ".")];
} else {
return [...res, elDisplayName];
}
}, []);
There are many caveats to this solution, like assuming if an array has a single object, it will contain only objects. But this should give you a start on detecting the presence of objects in arrays.
You can use .every() on an array to check every item against a case.
To check for an array you can use Array.isArray().
To check for an object you can use typeof variable === 'object' however, arrays are also classed as object types so you will need to check it is also not an array with the above method.
Example using your use case
const isObject = (arrayItem) => {
if (typeof arrayItem === 'object' && !Array.isArray(arrayItem) && arrayItem !== null) {
return true;
}
return false;
}
const array1 = [
[1, 2],
[1, 2],
[1, 2]
];
const array2 = [{
key: 'value'
}, {
key2: 'value'
}];
console.log('array1', array1.every(isObject)); // false
console.log('array2', array2.every(isObject)); // true
I have a basic json question that is giving me headache since a couple of hours, I am trying to dynamically add keys to a Json object using an array of string.
Here is my array of string:
let key = ['session', 'view_state', 'footer', 'config', 'items']
I have another variable which is jsonValue and is my whole json object. I want to end up with one of those options:
jsonValue.session.view_state.footer.config.items
jsonValue['session']['view_state']['footer']['config']['items']
This is my best attempt using a forEach.
forEach(jsonKeys, (el) => {
jsonCollection += jsonCollection !== undefined ? '."' +[el + '"'] : [ '"' + el + '"' ];
})
But I have this result:
undefined"session"."view_state"."footer"."config"."items"
Any help will be appreciated!
To get a value using the keys array
Iterate with Array#reduce, check the type of the current value, if it's an object, return the value of the key, if not return undefined:
const obj = {
"demo": true,
"session": {
"view_state": {
"footer": {
"config": {
"items": [
1,
2
]
}
}
}
}
};
const keys = ['session', 'view_state', 'footer', 'config', 'items'];
const value = keys.reduce((val, key) => val && typeof val === 'object' ?
val[key] : undefind, obj);
console.log(value);
To add a value using the keys array
Use Array#reduceRight to create a chain of objects with the value you want. Use Object#assign to update the original object with the results:
const keys = ['session', 'view_state', 'footer', 'config', 'items'];
const obj = { demo: true };
Object.assign(obj, keys.reduceRight((val, key) => ({ [key]: val }), [1, 2])); // replace [1, 2] with the actual items
console.log(obj);
I'm writing template software for publishing node.js modules to GitHub and NPM. A small snippet of an example template:
{{module.name}}
{{module.description}}
Purpose
{{module.code.purpose}}
I have an object built up with all these properties, ex.
{
"module" : {
"name" : "test",
"description" : "Test module"
...
The problem is that I need to merge this object's sub-objects so the final output looks something like this (for replacing the placeholders in the templates):
{
"module.name" : "test",
"module.description" : "Test module"
...
So I created my own function to do that:
/**
Takes an object like
{
"type" : "meal",
"pizza" : {
"toppings" : ["pepperoni", "cheese"],
"radius" : 6,
"metadata" : {
"id" : 24553
}
}
}
and changes it to
{
"type" : "meal",
"pizza.toppings" : ["pepperoni", "cheese"],
"pizza.radius" : 6,
"pizza.metadata.id" : 244553
}
*/
const mergeObjectsToKeys = object => {
// Loop through the passed in object's properties
return Object.entries(object).reduce((obj, [key, value]) => {
// Check if it is a sub-object or not
if (typeof value === "object") {
// If it is a sub-object, merge its properties with recursion and then put those keys into the master object
const subObject = mergeObjectsToKeys(value);
Object.entries(subObject).forEach(([key2, value2]) => {
obj[key + "." + key2] = value2;
});
} else {
// Otherwise, just put the key into the object to return
obj[key] = value;
}
}, { });
};
Two questions
Is this the correct way to write the software?
If so, is there a built-in function to merge the sub-objects, like shown above?
One built-in function to handle the requirement is Object.assign(); you can use spread element, Object.entries(), .map() to set property names of object which is property of object.
To handler objects where value is not nested object
let p = Object.keys(o).pop();
let res = Object.assign({}, ...Object.entries(o.module).map(([key, prop]) =>
({[`${p}.${key}`]: prop})));
To handle value which is nested object
let o = {
"type": "meal",
"pizza": {
"toppings": ["pepperoni", "cheese"],
"radius": 6,
"metadata": {
"id": 24553
}
}
}
let res = Object.assign({}, ...Object.entries(o).map(([prop, value]) =>
typeof value === "object" && !Array.isArray(value)
? Object.assign({}, ...Object.entries(value)
.map(([key,val]) => ({[`${prop}.${key}`]: key})))
: {[prop]: value})
);
console.log(res);