Given an object like this:
const spacing = {
xxs: "0.25rem",
xs: "0.5rem",
sm: "0.75rem",
md: "1rem",
lg: "1.5rem",
"2xl": "2rem",
"3xl": "3rem",
oggetto: {
"1": 1,
"2": 2,
},
}
I'm trying to write a function that loops through my object, and checks if the property of this object is another nested object, like oggetto. In that case, I'd like to recursively call the function. My main goal is to retrieve all the keys of the main and any nested object. I'd share some code but I don't really have an idea on how to approach this. Thank you!
function getObjectKeys(o, keys = []) {
Object.entries(o).forEach(([key, value]) => {
if (typeof value === "object") {
getObjectKeys(value, keys);
} else {
keys.push(key);
}
});
return keys;
}
getObjectKeys(spacing); // [ "xxs", "xs", "sm", "md", "lg", "2xl", "3xl", "1", "2" ]
first you must consider arrays when using typeof since typeof of an array and an object both return 'object'.
then you can have a recursive function to loop through the keys and return them if they hold an object.
for example something like this will return an array of arrays which hold the object key in the first index and full path to access the object in the second index:
const obj = {
n: 1,
s: 'test',
b: false,
a: [],
obj1: {
n: 2
},
obj2: {
innerObj: {
b: true,
otherInnerObj: {
s: 'hi'
},
},
a: []
},
obj3: {}
};
function findObjects(obj, parent, pathArr = []) {
for (k in obj) {
const v = obj[k];
if (typeof v === 'object' && !Array.isArray(v)) {
const parentName = `${parent}/${k}`;
pathArr.push([k, parentName]);
findObjects(v, parentName, pathArr);
}
}
return pathArr;
}
const result = findObjects(obj, 'obj');
const onlyKeys = result.map(x => x[0]);
const onlyPaths = result.map(x => x[1]);
console.log(onlyKeys);
console.log(onlyPaths);
console.log(result);
Related
I'm writing a function that takes in an object (A), compares it with another object (B) and creates a new object (C). Both A and B objects are symmetrical (same amount of properties, same keys) but the type of their values can differ. Eg: A.amount can be '11' while B.amount is a number. The end result should be an object C that has the values from A with the types from B.
In the function I came up with, I'm looping through all properties in object A, checking the type of the properties in object B with a switch/case statement, applying type conversion (like .toString()) and adding the corrected property to object C.
I would like the code to be recursive but as soon as there are nested objects involved I do not know how to reach the properties in B nor create the equivalent in C.
Example of an A object:
const data = {
a: "15",
b: "foo",
c: false,
d: {
da: 99,
db: [{
dba: "1.2",
dbb: true
}]
}
}
Example of a B object:
const targetType = {
a: 27,
b: "foofoo",
c: true,
d: {
da: "bar",
db: [{
dba: 4,
dbb: false
}]
}
}
Example of a C object -values from A, types from B:
const finalObject = {
a: 15,
b: "foo",
c: false,
d: {
da: "99",
db: [{
dba: 1.2,
dbb: true
}]
}
}
My function:
//data = object A
//targetType = object B
export function typeConversion(data: any, targetType: any) {
const resultObj = {};
Object.keys(data).forEach((key) => {
switch (typeof targetType[key]) {
case 'string':
resultObj[key] = data[key].toString();
break;
case 'boolean':
resultObj[key] = data[key] ? 'Yes' : 'No';
break;
case 'number':
resultObj[key] = Number(data[key]);
break;
//here is where it gets tricky:
case 'object':
if (Array.isArray(data[key])) {
const dataArray: any = [];
data[key].forEach((o) => {
//all objs in the array have the same shape, hence comparing to [0]
dataArray.push(typeConversion(o, targetType[key][0]));
});
resultObj[key] = dataArray;
} else {
const dataObj = {};
Object.keys(data[key]).forEach((subkey) => {
dataObj[subkey] = typeConversion(subkey, targetType[key][subkey]);
});
resultObj[key] = dataObj;
}
break;
}
});
return resultObj;
}
console.log(typeConversion(data, targetType))
The moment there is a nested object and typeConversion is called recursively, it will fail to find the path to the property in object B. I could add an optional parameter to the function for the 'parent property', but that would only work for one level of depth.
If there is a way to make this recursive, it can't be by coding the path to targetType[like][this].
TypeScript Playground example
All ideas welcome, perhaps I'm approaching the whole thing wrong.
I made some changes in your function to recursively create the final object. Although this is not Typescript specific problem, I wonder what is the use case for this :)
const transform = (data, targetType) => {
let finalObject = {};
const keys = Object.keys(data);
for (const key of keys) {
// If key is Array [], recursively add each element in Array
if (data[key] instanceof Array) {
finalObject[key] = [];
for (let i = 0; i < data[key].length; i++) {
const res = transform(data[key][i], targetType[key][i]);
finalObject[key].push(res);
}
}
// If key is Object {}, recursively add Object keys
else if (data[key] instanceof Object) {
finalObject[key] = transform(data[key], targetType[key]);
}
// If key is Primitive, we can directly add key
else {
switch (typeof targetType[key]) {
case 'string':
finalObject[key] = data[key].toString();
break;
case 'boolean':
finalObject[key] = data[key] ? 'Yes' : 'No';
break;
case 'number':
finalObject[key] = Number(data[key]);
break;
}
}
}
return finalObject;
};
const data = {
a: "15",
b: "foo",
c: false,
d: {
da: 99,
db: [{
dba: "1.2",
dbb: true
}]
}
}
const targetType = {
a: 27,
b: "foofoo",
c: true,
d: {
da: "bar",
db: [{
dba: 4,
dbb: false
}]
}
}
const finalObject = transform(data, targetType);
console.log('Final Object : ', finalObject);
I'm wanting to merge two array that carry objects with the same keys, but one object may have a value, but the other object, carrying the same key, may have a null value. I'm wanting to merge the two to try and replace all the null values and have one final object.
I've written the following, but at the moment, I'm only getting undefined as the final result.
const objectOne = [
{
one: null,
two: "Two",
three: null
}
];
const objectTwo = [
{
one: "One",
two: null,
three: "Three"
}
];
const compareObjects = (searchKey, searchValue) =>
objectTwo.map((retrieve) =>
Object.entries(retrieve).forEach(([retrieveKey, retrieveValue]) => {
if (!searchValue) {
objectOne.searchKey = retrieveValue;
console.log(objectOne);
}
})
);
const newObject = objectOne.map((search) =>
Object.entries(search).forEach(([searchKey, searchValue]) =>
compareObjects(searchKey, searchValue)
)
);
console.log(newObject);
Reduce seems more useful in this case than forEach. Reduce will build a new thing from the input and return that.
const objectOne = [
{
one: null,
two: "Two",
three: null
}
];
const objectTwo = [
{
one: "One",
two: null,
three: "Three"
}
];
const merged = objectOne.map((item, idx) => {
return Object.entries(item).reduce((acc, [key, value]) => {
acc[key] = value === null && objectTwo.length > idx ? objectTwo[idx][key] : value;
return acc;
}, {})
});
console.log(merged);
Your mapping of keys/entries is pretty close. It might be easiest to do something like this though:
const objectOnes = [
{
one: null,
two: "Two",
three: null
},
{
a: 123,
b: undefined,
},
];
const objectTwos = [
{
one: "One",
two: null,
three: "Three"
},
{
b: 456,
}
];
function merge(a, b) {
const result = { ...a }; // copy a
for (const key in result) {
// Let's say we want to pick the non-null/undefined value
if (result[key] !== null && result[key] !== undefined) continue;
result[key] = b[key]; // copy from b
}
return result;
}
const merged = objectOnes.map((obj, i) => merge(obj, objectTwos[i]));
console.log(merged);
One method you can use is to create a function that combines/merges the two dictionaries. First, we create our newObject:
const newObject = [
]
Then, we create our function using an O(n^2) approach and call it:
combine(objectOne,objectTwo)
console.log(newObject)
function combine(objectOne, objectTwo){
for (let [key1,value1] of Object.entries(objectOne[0])){
for (let [key2,value2] of Object.entries(objectTwo[0])){
if(key1 == key2){
if(value1 == null){
newObject.push({
key: key1,
value: value2
})
}
else{
newObject.push({
key: key1,
value: value1
})
}
}
}
}
}
This is the following output:
[
{ key: 'one', value: 'One' },
{ key: 'two', value: 'Two' },
{ key: 'three', value: 'Three' }
]
const obj={a:{b:{n:{d:1},m:{f:1}}}}
How to find and return {a:{b:{n:{d:1}}}}} by key 'n'.
JSON parse stringify
Here is a fairly concise recursive solution. It avoids recursing on properties whose values aren't objects (Arrays are objects, thus the additional .isArray() check, and null is a special case)* and terminates when it finds an object whose Object.keys() include the passed key, otherwise it returns an empty object.
const getNestedKeyPath = (obj, key) => {
if (Object.keys(obj).includes(key)) {
return { [key]: obj[key] };
}
for (const _key of Object.keys(obj)) {
if (obj[_key] !== null && typeof obj[_key] === 'object' && !Array.isArray(obj[_key])) {
const nextChild = getNestedKeyPath(obj[_key], key);
if (Object.keys(nextChild).length !== 0) {
return { [_key]: nextChild };
}
}
};
return {};
}
console.log(getNestedKeyPath(obj, 'n'))
<script>
const obj = {
c: {
x: new Date(),
y: undefined,
d: {
l: { w: [2, 3] },
k: { l: null }
}
},
a: {
b: {
n: { d: 1 },
m: { f: 1 }
}
},
};
</script>
*This is not a definitive check but meets our needs. see: JavaScript data types and data structures and Check if a value is an object in JavaScript
I ran into this problem, I was able to write solution which can handle array of object (not posted here) or one level deep nested object but i couldn't solve when the given object has nested structure like below. I am curious to know how we can solve this.
const source = {
a: 1,
b: {
c: true,
d: {
e: 'foo'
}
},
f: false,
g: ['red', 'green', 'blue'],
h: [{
i: 2,
j: 3
}]
};
solution should be
const solution = {
'a': 1,
'b.c': true,
'b.d.e': 'foo',
'f': false,
'g.0': 'red',
'g.1': 'green',
'g.2': 'blue',
'h.0.i': 2,
'h.0.j': 3
};
attempt for one deep nested object
let returnObj = {}
let nestetdObject = (source, parentKey) => {
let keys = keyFunction(source);
keys.forEach((key) => {
let values = source[key];
if( typeof values === 'object' && values !== null ) {
parentKey = keys[0]+'.'+keyFunction(values)[0]
nestetdObject(values, parentKey);
}else{
let key = parentKey.length > 0 ? parentKey : keys[0];
returnObj[key] = values;
}
})
return returnObj
};
// Key Extractor
let keyFunction = (obj) =>{
return Object.keys(obj);
}
calling the function
nestetdObject(source, '')
But my attempt will fail if the object is like { foo: { boo : { doo : 1 } } }.
You should be able to do it fairly simply with recursion. The way it works, is you just recursively call a parser on object children that prepend the correct key along the way down. For example (not tested very hard though):
const source = {
a: 1,
b: {
c: true,
d: {
e: 'foo'
}
},
f: false,
g: ['red', 'green', 'blue'],
h: [{
i: 2,
j: 3
}]
}
const flatten = (obj, prefix = '', res = {}) =>
Object.entries(obj).reduce((r, [key, val]) => {
const k = `${prefix}${key}`
if(typeof val === 'object'){
flatten(val, `${k}.`, r)
} else {
res[k] = val
}
return r
}, res)
console.log(flatten(source))
I am very late to the party but it can be easily achieved with a module like Flatify-obj.
Usage:
const flattenObject = require('flatify-obj');
flattenObject({foo: {bar: {unicorn: '🦄'}}})
//=> { 'foo.bar.unicorn': '🦄' }
flattenObject({foo: {unicorn: '🦄'}, bar: 'unicorn'}, {onlyLeaves: true});
//=> {unicorn: '🦄', bar: 'unicorn'}
// Licensed under CC0
// To the extent possible under law, the author(s) have dedicated all copyright
// and related and neighboring rights to this software to the public domain
// worldwide. This software is distributed without any warranty.
const source = {
a: 1,
b: { c: true, d: { e: "foo" } },
f: false,
g: ["red", "green", "blue"],
h: [{ i: 2, j: 3 }],
};
function flatten(source, parentKey, result = {}) {
if (source?.constructor == Object || source?.constructor == Array) {
for (const [key, value] of Object.entries(source)) {
flatten(
value,
parentKey != undefined ? parentKey + "." + key : key,
result
);
}
} else {
result[parentKey] = source;
}
return result;
}
console.log(flatten(source));
one more simple example with Object.keys
const apple = { foo: { boo : { doo : 1 } } }
let newObj = {}
const format = (obj,str) => {
Object.keys(obj).forEach((item)=>{
if(typeof obj[item] ==='object'){
const s = !!str? str+'.'+item:item
format(obj[item],s)
} else {
const m = !!str?`${str}.`:''
newObj[m+item]= obj[item]
}
})
}
format(apple,'')
console.log(newObj)
Let's say I have nested objects, like:
var obj = {
"items":[
{
"name":"Item 1",
"value": "500",
"options": [{...},{...}]
},
{
"name":"Item 2",
"value": "300",
"options": [{...},{...}]
}
],
"name": "Category",
"options": [{...},{...}]
};
I want to remove the options property from any level deep from all the objects. Objects can be nested within objects, and arrays as well.
We're currently using Lodash in the project, but I'm curious about any solutions.
There is no straight forward way to achieve this, however you can use this below function to remove a key from JSON.
function filterObject(obj, key) {
for (var i in obj) {
if (!obj.hasOwnProperty(i)) continue;
if (typeof obj[i] == 'object') {
filterObject(obj[i], key);
} else if (i == key) {
delete obj[key];
}
}
return obj;
}
and use it like
var newObject = filterObject(old_json, "option");
Modifying the above solution, To delete "dataID" which appears multiple times in my JSON . mentioned below code works fine.
var candidate = {
"__dataID__": "Y2FuZGlkYXRlOjkuOTI3NDE5MDExMDU0Mjc2",
"identity": {
"__dataID__": "aWRlbnRpdHk6NjRmcDR2cnhneGE3NGNoZA==",
"name": "Sumanth Suvarnas"
},
};
candidate = removeProp(candidate, "__dataID__")
console.log(JSON.stringify(candidate, undefined, 2));
function removeProp(obj, propToDelete) {
for (var property in obj) {
if (typeof obj[property] == "object") {
delete obj.property
let newJsonData= this.removeProp(obj[property], propToDelete);
obj[property]= newJsonData
} else {
if (property === propToDelete) {
delete obj[property];
}
}
}
return obj
}
A little modification of void's answer that allows for deletion of propertise which are also objects
function filterObject(obj, key) {
for (var i in obj) {
if (!obj.hasOwnProperty(i)) continue;
if (i == key) {
delete obj[key];
} else if (typeof obj[i] == 'object') {
filterObject(obj[i], key);
}
}
return obj;
}
We now use object-scan for data processing tasks like this. It's very powerful once you wrap your head around it. Here is how you'd answer your questions
// const objectScan = require('object-scan');
const prune = (input) => objectScan(['**.options'], {
rtn: 'count',
filterFn: ({ parent, property }) => {
delete parent[property];
}
})(input);
const obj = { items: [{ name: 'Item 1', value: '500', options: [{}, {}] }, { name: 'Item 2', value: '300', options: [{}, {}] }], name: 'Category', options: [{}, {}] };
console.log(prune(obj));
// => 3
console.log(obj);
// => { items: [ { name: 'Item 1', value: '500' }, { name: 'Item 2', value: '300' } ], name: 'Category' }
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://bundle.run/object-scan#13.8.0"></script>
Disclaimer: I'm the author of object-scan
I had similar issues. So, I developed the following library. Please see the source code of the library on GitHub, and you can download it using npm.
You can use the function removePropertiesDeeply together with isInitialized as below to remove all non-initialized properties (such as an empty object, empty array, empty string, or non-finite number).
const {removePropertiesDeeply, isInitialized} = require("#thedolphinos/utility4js");
const object = {
a: null,
b: "",
x: {
a: null,
b: ""
},
y: [],
z: [
null,
"",
{a: null, b: ""},
[[{a: {b: null, c: ""}}]],
"abc"
]
};
removePropertiesDeeply(object, (x) => !isInitialized(x));
console.log(JSON.stringify(object)); // See that the object becomes {"z":["abc"]}.
I had a similar issue and I got it resolved. I hope my solution might be helpful to someone.
I use Es6 ... spread operator to do a shallow copy of an object and made null to property I was not interested.
const newObject = {
...obj.items,
...obj.name,
options: null // option property will be null.
}
function omit(source) {
return isArray(source)
? source.map(omit)
: isObject(source)
? (({ options, ...rst }) => mapValues(rst, omit))(source)
: source;
}
as with lodash, that's an easy thing, also you can specify the key via an param like this
function omit(source, omitKey) {
return isArray(source)
? source.map(partialRight(omit,omitKey)))
: isObject(source)
? (({[omitKey]: _, ...rst }) => mapValues(rst, partialRight(omit,omitKey)))(source)
: source;
}
You can remove properties given a condition using following function:
// Warning: this function mutates original object
const removeProperties = (obj, condition = (key, value) => false) => {
for (var key in obj) {
const value = obj[key]
if (!obj.hasOwnProperty(key)) continue
if (typeof obj[key] === "object") {
removeProperties(obj[key], condition)
} else if (condition(key, value)) {
delete obj[key]
}
}
return obj
}
Examples:
// Remove all properties where key is equal to 'options'
removeProperties(someObject, (key, value) => key === 'options'))
// Remove all properties where key starts with 'ignore_'
removeProperties(someObject, (key, value) => key.startsWith('ignore_'))
// Remove all properties where value is null
removeProperties(someObject, (key, value) => value === null))