-
-
Save olayemii/948e5f611ee3f83e811d0040a2f0c4d4 to your computer and use it in GitHub Desktop.
const getObjectProperty = (obj, path, defaultValue="", returnUndefined=true) => { | |
const checkForDefaultValue = value => | |
value !== undefined ? value : undefined; | |
if (path === undefined) { | |
return obj; | |
} | |
try { | |
const value = path.split('.').reduce((o, i) => o[i], obj); | |
if (value === undefined && returnUndefined) return value; | |
return value !== undefined ? value : checkForDefaultValue(defaultValue); | |
} catch (e) { | |
if (e instanceof TypeError) return checkForDefaultValue(defaultValue); | |
throw e; | |
} | |
}; | |
/* | |
const a = { | |
b: { | |
c: [ | |
{ | |
d: { | |
e: 14 | |
} | |
}, | |
[16, 11] | |
], | |
d: 12 | |
} | |
}; | |
getObjectProperty(a, 'b.d', "Default!"); | |
// 12 | |
getObjectProperty(a, 'b.e', "Not a property"); | |
// undefined | |
getObjectProperty(a, 'b.e', "Not a property", false); | |
// Not a property | |
getObjectProperty(a, 'b.c.0.d.e', "Not a property!"); | |
// 14 | |
getObjectProperty(a, 'b.c.1.0', "Not a property!"); | |
//16 | |
*/ |
What if the path we need is inside an array of one of the fields in the object?
So, I'll suggest, let's refactor to call itself based on the type of field that has children. 😄
So, if the type of the field is an object, it calls itself,
if the type of field is an array, it traverses the tree or plucks from the tree and then return that found field.
if the type of field is an array that holds objects, it calls itself to operate at this level.
Thus a recursive function call to only breaks out when it found the required, else the default value or undefined is thrown.
This helps people in .ts to stop doing a?.b?.c?.d? === something
😄
Well done brother 👍
Let's clarify usage:
const a = {b: c: [{d: {e: 14}}], [16, 11]]], d: 12};
getObjectProperty(a, 'b.d', "Not a property!");
// 12
getObjectProperty(a, 'b.e', "Not a property!");
// Not a property!
getObjectProperty(a, 'b.c.0.d.e', "Not a property!");
// 14
getObjectProperty(a, 'b.c.1.0', "Not a property!");
// 16
Thanks @Just4Ease , @ahkohd :-)
@olayemii
I wrote some tests for the method, but the last test did not pass until I have to modify your code to check if it's trying to return undefined
.
UPDATE SNIPPET:
const getObjectProperty = (obj, path, defaultValue) => {
const checkForDefaultValue = value =>
value !== undefined ? value : undefined;
if (path === undefined) {
return obj;
}
try {
const value = path.split('.').reduce((o, i) => o[i], obj);
return value !== undefined ? value : checkForDefaultValue(defaultValue);
} catch (e) {
if (e instanceof TypeError) return checkForDefaultValue(defaultValue);
throw e;
}
};
export default getObjectProperty;
Oh yes, the tests I wrote:
describe('getObjectProperty', () => {
const testObject = { a: 'A', b: 'B', c: [1, 2] };
it('should check if property `a` exists', () => {
const test = getObjectProperty(testObject, 'a');
expect(test).toBe('A');
});
it('should check if property `d` exists', () => {
const test = getObjectProperty(testObject, 'd');
expect(test).toBeUndefined();
});
it('should check if property `c.0` exists', () => {
const test = getObjectProperty(testObject, 'c.0');
expect(typeof test).toBe('number');
expect(test).toBe(1);
});
it('should check if property `c` does not exist.', () => {
const test = getObjectProperty(testObject, 'c', 'Not Found');
expect(Array.isArray(test)).toBe(true);
expect(test).not.toBe('Not Found');
});
it(`should check if property 'd' exists, returns default value if it doesn't`, () => {
const test = getObjectProperty(testObject, 'd', 'Not Found');
expect(typeof test).toBe('string');
expect(test).toBe('Not Found');
});
});
Oh yes, the tests I wrote:
describe('getObjectProperty', () => { const testObject = { a: 'A', b: 'B', c: [1, 2] }; it('should check if property `a` exists', () => { const test = getObjectProperty(testObject, 'a'); expect(test).toBe('A'); }); it('should check if property `d` exists', () => { const test = getObjectProperty(testObject, 'd'); expect(test).toBeUndefined(); }); it('should check if property `c.0` exists', () => { const test = getObjectProperty(testObject, 'c.0'); expect(typeof test).toBe('number'); expect(test).toBe(1); }); it('should check if property `c` does not exist.', () => { const test = getObjectProperty(testObject, 'c', 'Not Found'); expect(Array.isArray(test)).toBe(true); expect(test).not.toBe('Not Found'); }); it(`should check if property 'd' exists, returns default value if it doesn't`, () => { const test = getObjectProperty(testObject, 'd', 'Not Found'); expect(typeof test).toBe('string'); expect(test).toBe('Not Found'); }); });
Hmm, @ahkohd I see, but in this case the value of testObject.d
is actually undefined 🤔 Should the default value really suffice when it's not trying to read a property from undefined
?
@olayemii
The issue is that this line of code can result to undefined and since you used return
, undefined
will be returned
.
return path.split(".").reduce((o, i) => o[i], obj);
Try this in your browser's console with your original function:
const testObject = { a: 'A', b: 'B', c: [1, 2] };
getObjectProperty(testObject, 'd', 'Not Found');
// results in undefined instead of 'No Found'
getObjectProperty(testObject, 'd', 'Not Found');
I feel this should actually return undefined, because testObject.d
is actually having a value of undefined
My initial idea was that this
return path.split(".").reduce((o, i) => o[i], obj);
returns an undefined
except when we are trying to read from undefined like undefined.name
that is when the catch
block gets invoked and a defaultValue can be used.
Very well, for my use case the later function works. Maybe you should provide a flag.
Very well, for my use case the later function works. Maybe you should provide a flag.
@ahkohd okay, I added a flag to allow undefined returns or fall back to a set default value, I also included your example. 👌🏾
Great!
Nice one!