Javascript - Set Object null if all its properties are null - javascript

I have an object like this:
var user = {
id: 101,
email: 'jack#dev.com',
personalInfo: {
name: null,
address: {
line1: null,
line2: null,
city: null,
state: null
}
}
}
I would like to iterate the properties and check if its properties are null. If they are all null I want to set that property null. This means starting from address whose values are all null so set address = null, then name and address are both null so personalInfo should be set null etc...
So my Object ends ups like this:
var user = {
id: 101,
email: 'jack#dev.com',
personalInfo: null
}

You could iterate all key/value pairs and decrement a count for all null values for this level. If all values are null, return null, otherwise the changed object.
For checking the nested objects, take the value and the type and call the function again and assign the result to the key.
function setNull(object) {
var entries = Object.entries(object),
count = entries.length;
entries.forEach(([k, v]) => {
if (null === (v && typeof v === 'object' && (object[k] = setNull(v)))) {
count--;
}
});
return count
? object
: null;
}
var user = { id: 101, email: 'jack#dev.com', personalInfo: { name: null, address: { line1: null, line2: null, city: null, state: null } } };
console.log(setNull(user));

You could do this recursively like so:
function isAllNull(obj) { // this function checks if an object contain only null values, i.e. checks if it is a null object or not
for(var key in obj) {
if(obj[key] !== null) { // if there is at least one property that is not null
return false; // then the object doesn't qualify
}
}
return true; // otherwise if all properties are null
}
function nullify(obj) { // this function takes an object and replace all its null child objects with null
for(var key in obj) {
if(obj[key] && typeof obj[key] === "object") { // if the current value is an object
nullify(obj[key]); // nullfiy it first (turn its null children objects to null)
if(isAllNull(obj[key])) { // then check if it is a null object
obj[key] = null; // if so replace it with null
}
}
}
}
Example:
function isAllNull(obj) {
for(var key in obj) {
if(obj[key] !== null) {
return false;
}
}
return true;
}
function nullify(obj) {
for(var key in obj) {
if(obj[key] && typeof obj[key] === "object") {
nullify(obj[key]);
if(isAllNull(obj[key])) {
obj[key] = null;
}
}
}
}
var user = {
id: 101,
email: 'jack#dev.com',
personalInfo: {
name: null,
address: {
line1: null,
line2: null,
city: null,
state: null
}
}
};
nullify(user);
console.log(user);

Call this function by passing a object and it will return object as you need,
function setNulls(obj) {
var flag = false;
for (var key in obj) {
if (obj[key] != null && typeof obj[key] == "object") {
obj[key] = setNulls(obj[key]);
}
if (obj[key] != null) {
flag = true;
}
}
if (flag)
return obj;
else
return null;
}

Related

Return old and new value from comparing models function

I have a function that compare two models and return the difference as the following example:
function getPropertyDifferences(obj1, obj2, type) {
return Object.entries(obj1).reduce((diff, [key, value]) => {
// Check if the property exists in obj2.
if (obj2.hasOwnProperty(key)) {
const val = obj2[key];
if (typeof val === "string") {
const obj1Date = new Date(value.toString());
const obj2Date = new Date(val);
// check if the values are valid dates. if not, we go through regular comparison
if (!isNaN(Number(obj1Date)) && !isNaN(Number(obj2Date))) {
return obj1Date.toDateString() !== obj2Date.toDateString() ? { ...diff,
[key]: val
} : diff;
}
}
if((val === null || undefined) && value === 0){
return;
}
// Check if obj1's property's value is different from obj2's.
if (val !== value) {
return {
...diff,
[key]: val,
};
}
}
// Otherwise, just return the previous diff object.
return diff;
}, {});
}
const a = {
dateOfBirth: "Wed Jan 06 2021 12:00:05 GMT-0700 (Mexican Pacific Standard Time)",
name: "test",
testValue: "this is the old value",
testValue2: "value2"
};
const b = {
dateOfBirth: "2021-01-06T12:00:05.357",
name: "test",
testValue: "this is the new one",
testValue2: "new value2"
};
console.log(getPropertyDifferences(a, b));
Now I have a new model that has the following properties:
export class ProfileActivity {
oldValue: string;
newValue: string;
description: string;
}
So, in the TS I use it as:
profileActivity: ProfileActivity[]
Now, in the function, I want to return this model instead of only the new value in the following format:
[{
oldValue: oldValueName
newValue: newValueName
description: Updated or Deleted(type)
}],
[{
oldValue: oldValueName
newValue: newValueName
description: Updated or Deleted(type)
}]
...etc
How can I achieve that?
Update
I achieve this as following:
getPropertyDifferences(obj1, obj2) {
let newProfileActivity: ProfileActivity;
return Object.entries(obj1).reduce((diff, [key, value]) => {
// Check if the property exists in obj2.
if (obj2.hasOwnProperty(key)) {
const val = obj2[key];
if(((val === null || undefined) && value === 0) || key === "clientIds"){
return;
}
if (typeof val === "string") {
const obj1Date = new Date(value.toString());
const obj2Date = new Date(val);
// check if the values are valid dates. if not, we go through regular comparison
if (!isNaN(Number(obj1Date)) && !isNaN(Number(obj2Date))) {
if(obj1Date.toDateString() !== obj2Date.toDateString()){
debugger;
let dateProfileActivity: ProfileActivity = {
newValue: obj1Date.toDateString(),
oldValue: obj2Date.toDateString(),
description: 'Update'
}
this.profileActivity.push(dateProfileActivity)
}
return obj1Date.toDateString() !== obj2Date.toDateString() ? { ...diff,
[key]: val
} : diff;
}
}
It would be appreciated if there is a better approach to the cleanest way to do this.

Javascript - Recursively looking for keys in array with empty values

I have created a function to recursively find empty values in a nested array.
The function returns the correct value at first but seems to reset it to the input value before returning the result.
What am I missing?
Here is my code:
const obj = [
{
mainContact: true,
contactName: "",
emailId: "abc#gmail.com",
contactAddress: [
{
addressType: "",
county: "U.K.",
postCode: "MK7 6BZ",
houseFlatNumber: 1
},
{
addressType: "def",
county: "France",
postCode: "123MKO",
houseFlatNumber: "223"
}
],
phoneDetails: [
{
notes: "",
phoneNumber: "1234567899",
countryCode: "44",
priority: "1"
},
{
notes: "Lorem ipsum",
phoneNumber: "1112223331",
countryCode: "48",
priority: "2"
}
]
}
];
function validObject(obj, isOk) {
for (var propName in obj) {
if (typeof obj[propName] === "object") {
this.validObject(obj[propName], isOk);
} else if (
obj[propName] === null ||
obj[propName] === undefined ||
obj[propName] === ""
) {
isOk = false;
break;
}
}
return isOk;
}
console.log(validObject(obj), true);
// This should return false but it returns true although it first hit the return isOk line
// with a false value then set it back to true
Any help would be much appreciated.
Thanks.
The thing about recursion is that you have to return the value from your recursive call. That means that you call the function from inside itself, then handle the return value accordingly.
Here's my interpretation of your function, where I add a simple base case to check if the passed value is null, undefined, or empty string.
function isValidObject(obj) {
// Base case
if (obj[propName] === null || obj[propName] === undefined || obj[propName] === '') {
return false;
}
// Recurse through each item in object
if (typeof obj === "object") {
for (var propName in obj) {
if (!isValidObject(obj[propName]) {
return false;
}
}
}
return true;
}
Notice that this allows you to get rid of the boolean parameter that you pass into your function and instead just return false when the first is found.
The basic issue is that you are not returning the value of the recursion call. So what you are actually doing is performing an inline recursive loop, then returning the value from the parent call.
Also, because isOk is a primitive boolean, it is pass by value rather than pass by reference. Thus modifying isOk inside a child function call doesn't modify the variable in the parent scope.
NOTE: this doesn't apply to obj[propName] (which is pass by reference), so any modifications to the data inside the function will get persisted outside the function.
function validObject(obj) {
for (var propName in obj) {
if( typeof obj[propName] === "object" ) {
if( validObject(obj[propName]) === false ) {
// This will propergate back through the call stack
return false;
}
}
else if (
obj[propName] === null ||
obj[propName] === undefined ||
obj[propName] === ''
) {
return false; // we don't need a variable here
}
}
return true; // calculatus eliminatus, nothing is false, so it must be true
}
An alternate approach is to write a more generic function to recursively test validity of object properties given a predicate to test with.
It's not difficult; to my mind it's often simpler than writing the specific function directly. And applying it to a specific case is also simple.
It might look like this:
const isValid = (pred) => (obj) =>
Object .values (obj) .every (
v => (v && typeof v == 'object') ? isValid (pred) (v) : pred (v)
)
const noEmptyProps = isValid (v => v !== '' && v != null)
const obj = [{mainContact: true, contactName: "", emailId: "abc#gmail.com", contactAddress: [{addressType: "", county: "U.K.", postCode: "MK7 6BZ", houseFlatNumber: 1}, {addressType: "def", county: "France", postCode: "123MKO", houseFlatNumber: "223"}], phoneDetails: [{notes: "", phoneNumber: "1234567899", countryCode: "44", priority: "1"}, {notes: "Lorem ipsum", phoneNumber: "1112223331", countryCode: "48", priority: "2"}]}]
console.log (noEmptyProps (obj))
Thanks to James McGuigan, my edited version is as below:
isValidObject(obj) {
for (var propName in obj) {
if( typeof obj[propName] === "object" ) {
if( this.isValidObject(obj[propName]) === false ||
obj[propName] === null ||
obj[propName].length === 0 )
{
return false;
}
}
else if (
obj[propName] === null ||
obj[propName] === undefined ||
obj[propName] === ''
) {
return false;
}
}
return true;
}

Best way to remove empty key/value pairs from objects recursively

let's say that I have the current data object:
const human = {
name: '',
age: 0,
head: {
rightEye: '',
leftEye: '',
}
limbs: {
arms: '',
legs: '',
}
somethingElse: '',
}
I want to remove every empty key/value pairs. And this part I got it working with the following code:
const removeFalsy = (obj) => {
const newObj = {};
Object.keys(obj).forEach((prop) => {
if (obj[prop]) {
if (typeof obj[prop] === 'object') {
newObj[prop] = removeFalsy(obj[prop]);
} else {
newObj[prop] = obj[prop];
}
}
});
return newObj;
};
The thing is empty objects still stay in the main object. I want to remove every empty object from inside the main object in order to get the following result:
const human = {};
In place of my current results:
const human = {
head: {},
limbs: {},
}
What are my best options?
When recursively calling removeFalsy, only assign to newObj if the result of the recursive call has an object with at least one key:
const removeFalsy = (obj) => {
const newObj = {};
Object.keys(obj).forEach((prop) => {
if (obj[prop]) {
if (typeof obj[prop] === 'object') {
// change below:
const nonFalseyVal = removeFalsy(obj[prop]);
if (Object.keys(nonFalseyVal).length !== 0) {
newObj[prop] = nonFalseyVal;
}
// change above
} else {
newObj[prop] = obj[prop];
}
}
});
return newObj;
};
const human = {
name: '',
age: 0,
head: {
rightEye: '',
leftEye: '',
},
limbs: {
arms: '',
legs: '',
},
somethingElse: '',
}
const nonFalsyHuman = removeFalsy(human);
console.log(nonFalsyHuman);
Note that it might be more appropriate to use .reduce, and you can use Object.entries to get the key and the value immediately:
const removeFalsy = (obj) => {
return Object.entries(obj).reduce((a, [key, val]) => {
if (!val) return a;
if (typeof val === 'object') {
const nonFalseyVal = removeFalsy(val);
if (Object.keys(nonFalseyVal).length !== 0) {
a[key] = nonFalseyVal;
}
} else {
a[key] = obj[key];
}
return a;
}, {});
};
const human = {
name: '',
age: 0,
head: {
rightEye: '',
leftEye: '',
},
limbs: {
arms: '',
legs: '',
},
somethingElse: '',
}
const nonFalsyHuman = removeFalsy(human);
console.log(nonFalsyHuman);
You need to make following changes
Update if (typeof obj[prop] === 'object') { condition where you set value only if the object has some valid keys.
Update if (obj[prop]) { condition to allow other non-falsy values to enter the loop e.g. 0, etc.
const human = {name: '',age: 0,head: {rightEye: '',leftEye: ''},limbs: {arms: '',legs: ''},somethingElse: ''};
const removeFalsy = (obj) => {
const newObj = {};
Object.keys(obj).forEach((prop) => {
if (obj[prop] !== "") {
if (typeof obj[prop] === 'object') {
const temp = removeFalsy(obj[prop]);
if(Object.keys(temp).length) newObj[prop] = temp;
} else {
newObj[prop] = obj[prop];
}
}
});
return newObj;
};
console.log(removeFalsy(human));

search for a particular key in a nested object in nodejs

I have a nested object which looks like this :
let obj = {
_id:{}
person:{
$search:{fname:true}
_id:{},
fname:{}
},
code:{},
vnvEmpName:{}
}
I have to search for a $search keyword in this and get the key which is inside it that is fname in this case, it can contain multiple keys as well and I have to retrieve all of it.
I tried something like this :
function findById(obj, id) {
var result;
for (var p in obj) {
if (obj.id === id) {
return obj;
} else {
if (typeof obj[p] === 'object') {
result = findById(obj[p], id);
if (result) {
return result;
}
}
}
}
return result;
}
If the object is in this way :
let obj = {
_id: {},
person: {
$search: {
lname: true
},
_id: {},
fname: {},
something:{
$search: {
fname: true
},
}
},
code: {},
$search: {
mname: true
},
vnvEmpName: {}
}
I want to retrieve all the attributes inside the $search of every block.
but I don't know how to get the keys inside a particular key as I am so new to the javascript.
To just get the keys you can simply do it using Object.keys(yourObject) MDN Object.keys
You can also use lodash to obtain the same result
Need to recursively search through the object
let obj = {
_id: {},
person: {
$search: {
fname: true
},
_id: {},
fname: {}
},
code: {},
vnvEmpName: {}
}
function findById(obj, id) {
var result = "";
// iterate the object using for..in
for (var keys in obj) {
// check if the object has any property by that name
if (obj.hasOwnProperty(keys) && typeof obj[keys] === 'object') {
// if the key is not undefined get it's value
if (obj[keys][id] !== undefined) {
result = (obj[keys][id])
} else {
// else again call the same function using the new obj value
findById(obj[keys], id)
}
}
}
return result;
}
console.log(findById(obj, 'fname'))
You can use the following function:
const objectifier = function (splits, create, context) {
let result = context;
for (let i = 0, key; result && (key = splits[i]); i += 1) {
if (key in result) { result = result[key]; } else {
result = create
? result[key] = {}
: undefined;
}
}
return result;
};
Have a look at the example below:
let obj = {
'_id': {aa: 'aa'},
'person': {
'$search': {
'fname': true
},
'_id': {'bb': 'bb'},
'fname': {'cc': 'cc'}
},
'code': {'dd': 'dd'},
'vnvEmpName': {'name': 'sdsdd'}
}
const objectifier = function (splits, create, context) {
let result = context;
for (let i = 0, key; result && (key = splits[i]); i += 1) {
if (key in result) { result = result[key]; } else {
result = create
? result[key] = {}
: undefined;
}
}
return result;
};
console.log(objectifier('person.$search'.split('.'), false, obj));
// { fname: true }

How to get the path from javascript object from key and value

I have a javascript object width depth.
I need to know the exact path from this key within the object ex: "obj1.obj2.data1"
I already know the key is data1, the value is 123.
My javascript object look like this
{
obj1: {
obj2: {
data1: 213,
data2: "1231",
obj3: {
data: "milf"
}
}
},
obj4: {
description: "toto"
}
}
How could I achieve that ?
here is a jsfiddle : http://jsfiddle.net/3hvav8xf/8/
I am trying to implement getPath.
I think recursive function can help to you (Updated version, to check value)
function path(c, name, v, currentPath, t){
var currentPath = currentPath || "root";
for(var i in c){
if(i == name && c[i] == v){
t = currentPath;
}
else if(typeof c[i] == "object"){
return path(c[i], name, v, currentPath + "." + i);
}
}
return t + "." + name;
};
console.log(path({1: 2, s: 5, 2: {3: {2: {s: 1, p: 2}}}}, "s", 1));
The following finds the path in any level of nested objects. Also with arrays.
It returns all the paths found, which is something you want if you have keys with the same name.
I like this approach because it works with lodash methods get and set out-of-the-box.
function findPathsToKey(options) {
let results = [];
(function findKey({
key,
obj,
pathToKey,
}) {
const oldPath = `${pathToKey ? pathToKey + "." : ""}`;
if (obj.hasOwnProperty(key)) {
results.push(`${oldPath}${key}`);
return;
}
if (obj !== null && typeof obj === "object" && !Array.isArray(obj)) {
for (const k in obj) {
if (obj.hasOwnProperty(k)) {
if (Array.isArray(obj[k])) {
for (let j = 0; j < obj[k].length; j++) {
findKey({
obj: obj[k][j],
key,
pathToKey: `${oldPath}${k}[${j}]`,
});
}
}
if (obj[k] !== null && typeof obj[k] === "object") {
findKey({
obj: obj[k],
key,
pathToKey: `${oldPath}${k}`,
});
}
}
}
}
})(options);
return results;
}
findPathsToKey({ obj: objWithDuplicates, key: "d" })
// ["parentKey.arr[0].c.d", "parentKey.arr[1].c.d", "parentKey.arr[2].c.d"]
Try it here - https://jsfiddle.net/spuhb8v7/1/
If you want the result to be a single key (first encountered), you can change the results to be a string and if defined, then return the function with it.
I ended up with the following function, that works with nested objects/arrays :
function findPath (obj, name, val, currentPath) {
currentPath = currentPath || ''
let matchingPath
if (!obj || typeof obj !== 'object') return
if (obj[name] === val) return `${currentPath}['${name}']`
for (const key of Object.keys(obj)) {
if (key === name && obj[key] === val) {
matchingPath = currentPath
} else {
matchingPath = findPath(obj[key], name, val, `${currentPath}['${key}']`)
}
if (matchingPath) break
}
return matchingPath
}
const treeData = [{
id: 1,
children: [{
id: 2
}]
}, {
id: 3,
children: [{
id: 4,
children: [{
id: 5
}]
}]
}]
console.log(findPath (treeData, 'id', 5))
Here you go!
function getPath(obj, value, path) {
if(typeof obj !== 'object') {
return;
}
for(var key in obj) {
if(obj.hasOwnProperty(key)) {
console.log(key);
var t = path;
var v = obj[key];
if(!path) {
path = key;
}
else {
path = path + '.' + key;
}
if(v === value) {
return path;
}
else if(typeof v !== 'object'){
path = t;
}
var res = getPath(v, value, path);
if(res) {
return res;
}
}
}
}
getPath(yourObject, valueYouWantToFindPath);
Rerutns path if found, else returns undefined.
I have only tested it with objects & comparison is very strict(ie: used ===).
Update:
Updated version that takes key as an argument.
function getPath(obj, key, value, path) {
if(typeof obj !== 'object') {
return;
}
for(var k in obj) {
if(obj.hasOwnProperty(k)) {
console.log(k);
var t = path;
var v = obj[k];
if(!path) {
path = k;
}
else {
path = path + '.' + k;
}
if(v === value) {
if(key === k) {
return path;
}
else {
path = t;
}
}
else if(typeof v !== 'object'){
path = t;
}
var res = getPath(v, key, value, path);
if(res) {
return res;
}
}
}
}
getPath(yourObject, key, valueYouWantToFindPath);
JSON Object can be handled in JavaScript as associative array.
So You can cycle through and store indexes of "parents" in some variables.
Assume the whole object to be stored in variable called obj.
for( var p1 in obj )
{
for( var p2 in obj[ p1 ] )
{
for( var p3 in obj[ p1 ][ p2 ] )
{
// obj[ p1 ][ p2 ][ p3 ] is current node
// so for Your example it is obj.obj1.obj2.data1
}
}
}
Hope answer was helpful.
I would do this job as follows;
Object.prototype.paths = function(root = [], result = {}) {
var ok = Object.keys(this);
return ok.reduce((res,key) => { var path = root.concat(key);
typeof this[key] === "object" &&
this[key] !== null ? this[key].paths(path,res)
: res[this[key]] == 0 || res[this[key]] ? res[this[key]].push(path)
: res[this[key]] = [path];
return res;
},result);
};
var myObj = {
obj1: {
obj2: {
data1: 213,
data2: "1231",
obj3: {
data: "milf"
}
}
},
obj4: {
description: "toto",
cougars: "Jodi",
category: "milf"
}
},
value = "milf",
milfPath = myObj.paths()[value]; // the value can be set dynamically and if exists it's path will be listed.
console.log(milfPath);
A few words of warning: We should be cautious when playing with the Object prototype. Our modification should have the descriptor enumerable = false or it will list in the for in loops and for instance jQuery will not work. (this is how silly jQuery is, since apparently they are not making a hasOwnProperty check in their for in loops) Some good reads are here and here So we have to add this Object method with Object.defineProperty() to make it enumerable = false;. But for the sake of simplicity and to stay in the scope of the question i haven't included that part in the code.
Here is a pretty short, and relatively easy to understand function I wrote for retrieving the JSON Path for every property/field on an Object (no matter how deeply nested, or not).
The getPaths(object) function just takes the Object you'd like the JSON Paths for and returns an array of paths. OR, if you would like the initial object to be denoted with a symbol that is different from the standard JSON Path symbol, $, you can call getPaths(object, path), and each JSON Path will begin with the specified path.
For Example: getPaths({prop: "string"}, 'obj'); would return the following JSON Path: obj.prop, rather than $.prop.
See below for a more detailed, in depth example of what getPaths returns, and how it is used.
object = {
"firstName": "John",
"lastName": "doe",
"age": 26,
"fakeData": true,
"address": {
"streetAddress": "fake street",
"city": "fake city",
"postalCode": "12345"
},
"phoneNumbers": [{
"type": "iPhone",
"number": "0123-4567-8888"
}, {
"type": "home",
"number": "0123-4567-8910"
}]
};
function getPaths(object, path = "$") {
return Object.entries(object).flatMap(function(o, i) {
if (typeof o[1] === "object" && !o[1].length) {
return `${getPaths(o[1], path + '.' + o[0])}`.split(',');
} else if (typeof o[1] === "object" && o[1].length) {
return Object.entries(o[1]).flatMap((no, i) => getPaths(no[1], `${path}.${o[0]}[${i}]`));
} else {
return `${path}.${o[0]}`;
}
});
}
console.log(`%o`, getPaths(object));
I really liked Roland Jegorov's answer, but I had a very complex object that I needed to search through and that answer could not account for it.
If you were in a situation like mine you may want to first make sure you have no circular references (or else you'll run into an infinite search). There are a few ways to do this, but I was having to stringify my object to copy it into other windows, so I ended up using this circular replacer: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Cyclic_object_value
(Update here - I made a small change to the getCircularReplacer function from MDN so it no longer leaves out function references since that is what I was looking for!)
(Update 3 - I also wanted to check on methods of any instances of classes, but I was returning just 'function' too early, so I have adjusted it to include instance methods. I think it finally works as I intended!)
const getCircularReplacer = () => {
const seen = new WeakSet();
return (key, value) => {
if (typeof value === "function") {
if (value?.prototype) {
if (seen.has(value.prototype)) {
return;
}
seen.add(value.prototype)
return value.prototype
}
return "function";
}
if (typeof value === "object" && value !== null) {
if (seen.has(value)) {
return;
}
seen.add(value);
}
return value;
};
};
const nonCyclicObject = JSON.parse(JSON.stringify(myComplexObject, getCircularReplacer()));
Then I used this modified version of Roland's answer:
(Update 2: I had to make sure not to return after the key was found as it would always simply return after only calling the function once if the first level of the object had that key)
function findPathsToKey(options) {
let count = 0;
let results = [];
(function findKey({
key,
obj,
pathToKey,
}) {
count += 1;
if (obj === null) return;
const oldPath = `${pathToKey ? pathToKey + "." : ""}`;
if (Object.hasOwnProperty.call(obj, key)) {
results.push(`${oldPath}${key}`);
}
if (typeof obj === "object" && !Array.isArray(obj)) {
for (const k in obj) {
if (Object.hasOwnProperty.call(obj, k)) {
if (Array.isArray(obj[k])) {
for (let j = 0; j < obj[k].length; j++) {
findKey({
obj: obj[k][j],
key,
pathToKey: `${oldPath}${k}[${j}]`,
});
}
}
if (typeof obj[k] === "object") {
findKey({
obj: obj[k],
key,
pathToKey: `${oldPath}${k}`,
});
}
}
}
}
})(options);
return { count, results };
};
The count was just to troubleshoot a little bit and make sure it was actually running through the amount of keys I thought it was. Hope this helps any others looking for a solution!
⚠️ This code doesn't answer the question but does related: transforms nested object to query object with dot.divided.path as keys and non-object values; compatible with URlSearchParams & qs. Maybe will be useful for someone.
const isPlainObject = (v) => {
if (Object.prototype.toString.call(v) !== '[object Object]') return false;
const prototype = Object.getPrototypeOf(v);
return prototype === null || prototype === Object.prototype;
};
const objectToQueryObject = (obj, path) => {
return Object.entries(obj).reduce((acc, [key, value]) => {
const newPath = path ? `${path}.${key}` : key;
if (isPlainObject(value)) {
return {
...acc,
...objectToQueryObject(value, newPath)
};
}
acc[newPath] = value;
return acc;
}, {})
};
const queryObjectRaw = {
value: {
field: {
array: {
'[*]': {
field2: {
eq: 'foo',
ne: 'bar',
}
}
},
someOtherProp: { in: [1, 2, 3],
ne: 'baz',
}
},
someOtherField: {
gt: 123
},
},
otherValue: {
eq: 2
},
};
const result = objectToQueryObject(queryObjectRaw);
console.log('result', result);
const queryString = new URLSearchParams(result).toString();
console.log('queryString', queryString);
If you know only the value and not the key, and want to find all paths with this value use this.
It will find all property with that value, and print the complete path for every founded value.
const createArrayOfKeys = (obj, value) => {
const result = []
function iter(o) {
Object.keys(o).forEach(function(k) {
if (o[k] !== null && typeof o[k] === 'object') {
iter(o[k])
return
}
if (o[k]=== value) {
result.push(k)
return
}
})
}
iter(obj)
return result
}
function findPath (obj, name, val, currentPath) {
currentPath = currentPath || ''
let matchingPath
if (!obj || typeof obj !== 'object') return
if (obj[name] === val) return `${currentPath}/${name}/${val}`
for (const key of Object.keys(obj)) {
if (key === name && obj[key] === val) {
matchingPath = currentPath
} else {
matchingPath = findPath(obj[key], name, val, `${currentPath}/${key}`)
}
if (matchingPath) break
}
return matchingPath
}
const searchMultiplePaths = (obj, value) => {
const keys = createArrayOfKeys(obj, value)
console.log(keys);
keys.forEach(key => {
console.log(findPath(obj, key, value))
})
}
var data = { ffs: false, customer: { customer_id: 1544248, z_cx_id: '123456' }, selected_items: { '3600196': [{ id: 4122652, name: 'Essential Large (up to 8\'x10\')', selected: true }] }, service_partner: { id: 3486, name: 'Some String', street: '1234 King St.', hop: '123456' }, subject: 'Project-2810191 - Orange Juice Stain (Rug)', description: 'Product Type: \n\nIssue: (copy/paste service request details here)\n\nAction Required:', yes: '123456' };
searchMultiplePaths(data, '123456')
I know the post is old but the answers don't really satisfy me.
A simple solution is to add the object path to each object in the structure. Then you can easily read the path when you need it.
let myObject = {
name: 'abc',
arrayWithObject: [
{
name: "def"
},
{
name: "ghi",
obj: {
name: "jkl"
}
}
],
array: [15, 'mno'],
arrayArrayObject: [
[
{
name: '...'
}
]
]
}
function addPath(obj, path = [], objectPathKey = '_path') {
if (Array.isArray(obj)) {
obj.map((item, idx) => addPath(item, [...path, idx]))
} else if (typeof obj === "object") {
obj[objectPathKey] = path;
for (const key in obj) {
obj[key] = addPath(obj[key], [...path, key])
}
}
return obj
}
myObject = addPath(myObject);
let changeMe = _.cloneDeep(myObject.arrayWithObject[0])
changeMe.newProp = "NEW"
changeMe.newNested = {name: "new", deeper: {name: "asdasda"}}
changeMe = addPath(changeMe, changeMe._path)
_.set(myObject, changeMe._path, changeMe);
When your updates are done sanitize your object and remove your _path property.
Advantages of this solution:
You do the work once
you keep your code simple
no need for own property checks
no cognitive overload
I can highly suggest you to use lodash for this problem.
In their documentation this should help you out
// using "_.where" callback shorthand
_.find(characters, { 'age': 1 });
// → { 'name': 'pebbles', 'age': 1, 'blocked': false }

Categories

Resources