-
-
Save jeneg/9767afdcca45601ea44930ea03e0febf to your computer and use it in GitHub Desktop.
function get(obj, path, def) { | |
var fullPath = path | |
.replace(/\[/g, '.') | |
.replace(/]/g, '') | |
.split('.') | |
.filter(Boolean); | |
return fullPath.every(everyFunc) ? obj : def; | |
function everyFunc(step) { | |
return !(step && (obj = obj[step]) === undefined); | |
} | |
} |
Not the fastest one but working for me:
export const get = (value: any, path: string, defaultValue?: any) => {
return String(path)
.split('.')
.reduce((acc, v) => {
if (v.startsWith('[')) {
const [, arrPart] = v.split('[');
v = arrPart.split(']')[0];
}
if (v.endsWith(']') && !v.startsWith('[')) {
const [objPart, arrPart, ...rest] = v.split('[');
const [firstIndex] = arrPart.split(']');
const otherParts = rest
.join('')
.replaceAll('[', '')
.replaceAll(']', '.')
.split('.')
.filter(str => str !== '');
return [...acc, objPart, firstIndex, ...otherParts];
}
return [...acc, v];
}, [] as string[])
.reduce((acc, v) => {
try {
acc = acc[v] !== undefined ? acc[v] : defaultValue;
} catch (e) {
return defaultValue;
}
return acc;
}, value);
};
Checked with the next tests:
const data = {
x: {y: {z: 1, xx: [['xxx', 1]]}},
a: [{b: {c: ['d', 'e', {f: 2}]}}, {b: {c: ['d', 'e', {f: 3}]}}],
q: [{w: {e: ['r', 't', {y: 4}]}}],
nullable: null,
undefinedDefined: undefined,
};
expect(get(data, 'x.z')).toEqual(undefined);
expect(get(data, 'x.y.z')).toEqual(1);
expect(get(data, 'a')).toEqual(data.a);
expect(get(data, 'a.b')).toEqual(data.a.b);
expect(get(data, 'a.[0].b.c')).toEqual(data.a[0].b.c);
expect(get(data, 'a[0].b.c')).toEqual(data.a[0].b.c);
expect(get(data, 'q[0].w.e[3]')).toEqual(undefined);
expect(get(data, 'q[0].w.e[2]')).toEqual(data.q[0].w.e[2]);
expect(get(data, 'x.y.xx[0]')).toEqual(data.x.y.xx[0]);
expect(get(data, 'x.y.xx[0][1]')).toEqual(data.x.y.xx[0][1]);
expect(get(data, 'x.z.a', 'hehe')).toEqual('hehe');
expect(get(data, 'nullable', 'hehe')).toEqual(null);
expect(get(data, 'undefinedDefined', 'hehe')).toEqual('hehe');
I would just use optional chaining and nullish coalescing:
https://caniuse.com/?search=optional%20chaining
https://caniuse.com/?search=nullish%20coalescing
Its pretty well supported now.
@SupremeTechnopriest, out of interest, how would you go about using that in situations where you don't know what the path is going to be? e.g. you have an object and a counterpart configuration that targets different portions of it.
If you knew the depth you could do something like:
const obj = {
a: {
b: {
c: 1
}
}
}
const path = 'a.b.c'
const fallback = 2
const parts = path.split('.')
const value = obj[parts[0]]?.[parts[1]]?.[parts[2]] ?? fallback
console.log(value) // 1
If you don't know the depth, I think the get
method is going to be the best route. You could check the length of parts
and do a switch and handle all possible depths, but Im not sure if thats going to out perform the get
methods above.
Maybe I will maintain a benchmark for this problem.
At the time this was being discussed optional chaining was not even a proposal yet. I would be curious to see this added to the benchmark if you want to take that on @haouarihk.
See my comment from May 29 2020