How to grab deepest nested object in json JS - javascript

Is it possible to get the deepest level object without knowing path names?
for example I have this json
const response = {
level1: {
level2: {
level3: {responseObject}
}
}
}
the levels will always only contain 1 value
but the there may be varying levels of nesting(5max)
is there anyway to alway get to the response object without using something such as response.level1.level2.level3?
note: the response object will always have the same keys

Use a recursive search
Check if the object matches the key template for a response object ("the response object will always have the same keys")
If so, you've found the response object so return it
If not, search again with the first object value (since "the levels will always only contain 1 value")
If any level search is on a non-object, bail out
const response = {
level1: {
level2: {
level3: {
I: "am",
a: "response object"
}
}
}
}
const responseObjectKeyTemplate = (["I", "a"]).sort().join(",")
const isResponseObject = (obj) =>
Object.keys(obj).sort().join(",") === responseObjectKeyTemplate
const searchLevel = (obj) => {
if (typeof obj !== "object") {
return null // not an object, bail
}
if (isResponseObject(obj)) {
return obj // found it
}
// BWAAAAAAA (Inception pun)
return searchLevel(Object.values(obj)[0])
}
console.log(searchLevel(response))

Related

How to dynamically ignore certain fields in a javascript object when converting it to json?

Currently, I am using the toJSON() object on a method to ignore any fields that are underscored e.g.
toJSON() {
const properties = Object.getOwnPropertyNames(this);
const publicProperties = properties.filter(property => {
return property.charAt(0) !== '_'
})
const json = publicProperties.reduce((obj, key) => {
obj[key] = this[key]
return obj
}, {})
return json
}
This was fine. But I have the concept of roles in my API and I would like to return private fields if the user is an admin.
This led me to the idea of doing:
toJSON(role='user') {
const properties = Object.getOwnPropertyNames(this);
const publicProperties = properties.filter(property => {
return property.charAt(0) !== '_' || role === 'admin'
})
const json = publicProperties.reduce((obj, key) => {
key = key.charAt(0) === '_' ? key.substring(1) : key
obj[key] = this[key]
return obj
}, {})
return json
}
But then the issue becomes how do I get the role argument passed to the toJSON() method, especially when JSON.stringify() is being called and the method calling JSON.stringify() I might not have access to.
I could set on my object a role property before returning a json response e.g.
const getCurrentProject = async (c) => {
const project = await projectService.getCurrentProject(c.get('projectId'));
project._role = c.get('payload').role
return c.json(project, httpStatus.OK);
};
But that doesn't seem ideal and then there are more issues when JSON.stringify() is called on an array of object as I would have to set that for each object.
My next idea was to use my own json response function that would have a replacer function for JSON.stringify()
const jsonResponse = (context, object, status) => {
const role = c.get('payload').role
const body = JSON.stringify(object, (key, value) => {
// function to set private vars to null based on role
})
headers = 'application/json; charset=UTF-8'
return c.body(body, status, headers)
}
The issue with this is that the replacer function will just set them to null and not hide them and I can't just blindly remove keys with null values as I might need them. I could set them to 'remove' or another placeholder and remove them after but again, it doesn't seem like the best way.
So currently I am confused on what I should do. Is there a way to globally override JSON.stringify() and add the role parameter as an argument, is there a better approach I am missing? Or should I just stick to the _role property and for lists of objects set it for each one.
Thanks!
You can use a replacer function. If you return a Function, Symbol, or undefined, the property is not included in the output. See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#the_replacer_parameter
I would use Object.entries, and then Array.filter the keys you want, finally Object.fromEntries to get back to an object.
example ->
const obj = {
_adminOnly: 'Lets have a party!',
name: 'bob',
age: 22,
_hideme: 'Hide unless admin',
toJSON: function (role='user') {
return Object.fromEntries(
Object.entries(this).
filter(([k]) => {
if (k === 'toJSON') return false;
return role === 'admin'
? true
: k.charAt(0) !== '_'
}
)
);
}
}
console.log(obj.toJSON());
console.log(obj.toJSON('admin'));

Checking against an object property in an array javascript

var array = [];
Now, I am creating an object with post requests.
var object = {
property 1: req.body.x;
property 2: req.body.y
};
if the property 1 value(i.e. req.body.x) already exists in any of the objects in the array, it should giving a warning. Else we need to do array.push(object).
How to check for the property 1 value every time with the post request.
You can use array.Find in order to check if an object exists in the array with the same property.
const arr = [{
prop1: 'foo', // prop1 of `foo` already in array
}]
// small function so we can avoid code duplication
const existsInArray = (obj) => {
return arr.find((a) => a.prop1 === obj.prop1)
}
const obj1 = {
prop1: 'foo',
}
const obj2 = {
prop1: 'bar',
}
if (existsInArray(obj1)) {
console.warn('obj1 already in array')
} else {
arr.push(obj1)
}
if (existsInArray(obj2)) {
console.warn('obj2 already in array')
} else {
arr.push(obj2)
}
console.log(arr)

Navigating multi-dimensional object to match key with only ES5, multiple Object.key and .map

I am attempting to interrogate a multi-dimensional object and return part of the object based on a variable available to me. I do not have access to many of ES6's object and array methods.
My object to interrogate looks like:
const myObj = {
blue: {
three: {
star: {
foo: "this object is what I want",
foo2: "this object is what I want"
}
}
}
}
As you can see there are three layers to this. I do not know what any of the keys may be with the exception of star. I know I want to return the value of this key if it's available.
In order to return the object for the key star I am currently using Object.keys().map() three times, which works but I feel like there must be a simpler solution. Below is what I have:
return Object.keys(myObj).map(function(colour) {
Object.keys(myObj[colour]).map(function(number) {
Object.keys(myObj[colour][number]).map(function(shape){
if (myObj[colour][number][shape] === "star") {
return myObj[colour][number][shape];
}
});
});
});
Is there something else I can use to step through this object until I hit my matching key? And then return the value of that key?
const myObj = {
blue: {
three: {
star: {
foo: "this object is what I want",
foo2: "this object is what I want"
}
}
}
}
You can iterate over all properties and check if that property is, what you want.. than you can build array from them or whatever you like. :)
const seeker = function (object) {
for (let property in object)
{
if (property === 'star')
{
console.log(property, object[property])
}
if (object[property] instanceof Object)
seeker(object[property])
}
}
Maybe you could try this:
function recursive(myObj) {
const result = [];
for (const key in myObj) {
if (key === 'star') {
result.push(myObj[key]);
}
if (typeof myObj[key] === 'object') {
result.push(recursive(myObj[key]).reduce((acc, val) => acc.concat(val), []));
}
}
return result.reduce((acc, val) => acc.concat(val), []);
}

JavaScript - Replace the values of a Nested Object, without affecting the whole object

The Problem:
I have this function. Which removes all KeyValue Pairs that have an Empty String as value from a Payload.
The problem is, that I want to apply it in an Object that is Nested. Not the whole Payload Object. Example:
configuration: {
conf1: "",
conf2: "Something",
conf3: ""
},
resourceName: "Name"
In this case I want to apply my UtilityFunction, in the configurationObject. Which would result in this:
configuration: {
conf2: "Something",
},
resourceName: "Name"
So, I used a few Methods. Object.assign, rest, in order to supply an object with all the outside parameters, but also, the output of the utility applied to just the configuration object.
I tried:
Object.assign(formValues, removeEmptyKeysUtil(formValues.configuration));
// Results in printing the values in the main object.
Also:
{ formValues, ...removeEmptyKeysUtil(formValues.configuration) };
// which does not do anything
Can you please help me, and explain what am I doing wrong?
The stripEmptyStrings function takes the original object and the target key.
this function can also handle if the target property of the object is "" and will delete that property regardless of if it is an Object or not.
const stripEmptyStrings = (object, target) => {
const _target = object[target];
if (_target === "") {
delete object[target]
return object;
}
else if (typeof _target === "object") {
Object.keys(_target).forEach((k) => {
if (_target[k] === "") delete _target[k];
})
}
return {
...object,
[target]: _target,
}
}
const obj1 = {
configuration: {
conf1: "",
conf2: "Something",
conf3: ""
},
resourceName: "Name",
}
const result1 = stripEmptyStrings(obj1, "configuration");
console.log(result1)
const obj2 = {
removeMe: "",
resourceName: "Name2",
}
const result2 = stripEmptyStrings(obj2, "removeMe");
console.log(result2)

Remove value from object without mutation

What's a good and short way to remove a value from an object at a specific key without mutating the original object?
I'd like to do something like:
let o = {firstname: 'Jane', lastname: 'Doe'};
let o2 = doSomething(o, 'lastname');
console.log(o.lastname); // 'Doe'
console.log(o2.lastname); // undefined
I know there are a lot of immutability libraries for such tasks, but I'd like to get away without a library. But to do this, a requirement would be to have an easy and short way that can be used throughout the code, without abstracting the method away as a utility function.
E.g. for adding a value I do the following:
let o2 = {...o1, age: 31};
This is quite short, easy to remember and doesn't need a utility function.
Is there something like this for removing a value? ES6 is very welcome.
Thank you very much!
Update:
You could remove a property from an object with a tricky Destructuring assignment:
const doSomething = (obj, prop) => {
let {[prop]: omit, ...res} = obj
return res
}
Though, if property name you want to remove is static, then you could remove it with a simple one-liner:
let {lastname, ...o2} = o
The easiest way is simply to Or you could clone your object before mutating it:
const doSomething = (obj, prop) => {
let res = Object.assign({}, obj)
delete res[prop]
return res
}
Alternatively you could use omit function from lodash utility library:
let o2 = _.omit(o, 'lastname')
It's available as a part of lodash package, or as a standalone lodash.omit package.
With ES7 object destructuring:
const myObject = {
a: 1,
b: 2,
c: 3
};
const { a, ...noA } = myObject;
console.log(noA); // => { b: 2, c: 3 }
one line solution
const removeKey = (key, {[key]: _, ...rest}) => rest;
Explanations:
This is a generic arrow function to remove a specific key. The first argument is the name of the key to remove, the second is the object from where you want to remove the key. Note that by restructuring it, we generate the curated result, then return it.
Example:
let example = {
first:"frefrze",
second:"gergerge",
third: "gfgfg"
}
console.log(removeKey('third', example))
/*
Object {
first: "frefrze",
second: "gergerge"
}
*/
To add some spice bringing in Performance. Check this thread bellow
https://github.com/googleapis/google-api-nodejs-client/issues/375
The use of the delete operator has performance negative effects for
the V8 hidden classes pattern. In general it's recommended do not use
it.
Alternatively, to remove object own enumerable properties, we could
create a new object copy without those properties (example using
lodash):
_.omit(o, 'prop', 'prop2')
Or even define the property value to null or undefined (which is
implicitly ignored when serializing to JSON):
o.prop = undefined
You can use too the destructing way
const {remov1, remov2, ...new} = old;
old = new;
And a more practical exmple:
this._volumes[this._minCandle] = undefined;
{
const {[this._minCandle]: remove, ...rest} = this._volumes;
this._volumes = rest;
}
As you can see you can use [somePropsVarForDynamicName]: scopeVarName syntax for dynamic names. And you can put all in brackets (new block) so the rest will be garbage collected after it.
Here a test:
exec:
Or we can go with some function like
function deleteProps(obj, props) {
if (!Array.isArray(props)) props = [props];
return Object.keys(obj).reduce((newObj, prop) => {
if (!props.includes(prop)) {
newObj[prop] = obj[prop];
}
return newObj;
}, {});
}
for typescript
function deleteProps(obj: Object, props: string[]) {
if (!Array.isArray(props)) props = [props];
return Object.keys(obj).reduce((newObj, prop) => {
if (!props.includes(prop)) {
newObj[prop] = obj[prop];
}
return newObj;
}, {});
}
Usage:
let a = {propH: 'hi', propB: 'bye', propO: 'ok'};
a = deleteProps(a, 'propB');
// or
a = deleteProps(a, ['propB', 'propO']);
This way a new object is created. And the fast property of the object is kept. Which can be important or matter. If the mapping and the object will be accessed many many times.
Also associating undefined can be a good way to go with. When you can afford it. And for the keys you can too check the value. For instance to get all the active keys you do something like:
const allActiveKeys = Object.keys(myObj).filter(k => myObj[k] !== undefined);
//or
const allActiveKeys = Object.keys(myObj).filter(k => myObj[k]); // if any false evaluated value is to be stripped.
Undefined is not suited though for big list. Or development over time with many props to come in. As the memory usage will keep growing and will never get cleaned. So it depend on the usage. And just creating a new object seem to be the good way.
Then the Premature optimization is the root of all evil will kick in. So you need to be aware of the trade off. And what is needed and what's not.
Note about _.omit() from lodash
It's removed from version 5. You can't find it in the repo. And here an issue that talk about it.
https://github.com/lodash/lodash/issues/2930
v8
You can check this which is a good reading https://v8.dev/blog/fast-properties
As suggested in the comments above if you want to extend this to remove more than one item from your object I like to use filter. and reduce
eg
const o = {
"firstname": "Jane",
"lastname": "Doe",
"middlename": "Kate",
"age": 23,
"_id": "599ad9f8ebe5183011f70835",
"index": 0,
"guid": "1dbb6a4e-f82d-4e32-bb4c-15ed783c70ca",
"isActive": true,
"balance": "$1,510.89",
"picture": "http://placehold.it/32x32",
"eyeColor": "green",
"registered": "2014-08-17T09:21:18 -10:00",
"tags": [
"consequat",
"ut",
"qui",
"nulla",
"do",
"sunt",
"anim"
]
};
const removeItems = ['balance', 'picture', 'tags']
console.log(formatObj(o, removeItems))
function formatObj(obj, removeItems) {
return {
...Object.keys(obj)
.filter(item => !isInArray(item, removeItems))
.reduce((newObj, item) => {
return {
...newObj, [item]: obj[item]
}
}, {})
}
}
function isInArray(value, array) {
return array.indexOf(value) > -1;
}
My issue with the accepted answer, from an ESLint rule standard, if you try to destructure:
const { notNeeded, alsoNotNeeded, ...rest } = { ...ogObject };
the 2 new variables, notNeeded and alsoNotNeeded may throw a warning or error depending on your setup since they are now unused. So why create new vars if unused?
I think you need to use the delete function truly.
export function deleteKeyFromObject(obj, key) {
return Object.fromEntries(Object.entries(obj).filter(el => el[0] !== key))
}
with lodash cloneDeep and delete
(note: lodash clone can be used instead for shallow objects)
const obj = {a: 1, b: 2, c: 3}
const unwantedKey = 'a'
const _ = require('lodash')
const objCopy = _.cloneDeep(obj)
delete objCopy[unwantedKey]
// objCopy = {b: 2, c: 3}
For my code I wanted a short version for the return value of map() but the multiline/mutli operations solutions were "ugly". The key feature is the old void(0) which resolve to undefined.
let o2 = {...o, age: 31, lastname: void(0)};
The property stays in the object:
console.log(o2) // {firstname: "Jane", lastname: undefined, age: 31}
but the transmit framework kills it for me (b.c. stringify):
console.log(JSON.stringify(o2)) // {"firstname":"Jane","age":31}
I wrote big function about issue for me. The function clear all values of props (not itself, only value), arrays etc. as multidimensional.
NOTE: The function clear elements in arrays and arrays become an empty array. Maybe this case can be added to function as optional.
https://gist.github.com/semihkeskindev/d979b169e4ee157503a76b06573ae868
function clearAllValues(data, byTypeOf = false) {
let clearValuesTypeOf = {
boolean: false,
number: 0,
string: '',
}
// clears array if data is array
if (Array.isArray(data)) {
data = [];
} else if (typeof data === 'object' && data !== null) {
// loops object if data is object
Object.keys(data).forEach((key, index) => {
// clears array if property value is array
if (Array.isArray(data[key])) {
data[key] = [];
} else if (typeof data[key] === 'object' && data !== null) {
data[key] = this.clearAllValues(data[key], byTypeOf);
} else {
// clears value by typeof value if second parameter is true
if (byTypeOf) {
data[key] = clearValuesTypeOf[typeof data[key]];
} else {
// value changes as null if second parameter is false
data[key] = null;
}
}
});
} else {
if (byTypeOf) {
data = clearValuesTypeOf[typeof data];
} else {
data = null;
}
}
return data;
}
Here is an example that clear all values without delete props
let object = {
name: 'Semih',
lastname: 'Keskin',
brothers: [
{
name: 'Melih Kayra',
age: 9,
}
],
sisters: [],
hobbies: {
cycling: true,
listeningMusic: true,
running: false,
}
}
console.log(object);
// output before changed: {"name":"Semih","lastname":"Keskin","brothers":[{"name":"Melih Kayra","age":9}],"sisters":[],"hobbies":{"cycling":true,"listeningMusic":true,"running":false}}
let clearObject = clearAllValues(object);
console.log(clearObject);
// output after changed: {"name":null,"lastname":null,"brothers":[],"sisters":[],"hobbies":{"cycling":null,"listeningMusic":null,"running":null}}
let clearObject2 = clearAllValues(object);
console.log(clearObject2);
// output after changed by typeof: {"name":"","lastname":"","brothers":[],"sisters":[],"hobbies":{"cycling":false,"listeningMusic":false,"running":false}}

Categories