Skip to content

Instantly share code, notes, and snippets.

@andrewchilds
Forked from harish2704/lodash.get.js
Last active July 8, 2024 14:15
Show Gist options
  • Save andrewchilds/30a7fb18981d413260c7a36428ed13da to your computer and use it in GitHub Desktop.
Save andrewchilds/30a7fb18981d413260c7a36428ed13da to your computer and use it in GitHub Desktop.
Simple, standalone, vanilla implementation of lodash.get
// Simple implementation of lodash.get
// Handles arrays, objects, and any nested combination of the two.
// Also handles undefined as a valid value - see test case for details.
// Based on: https://gist.github.com/harish2704/d0ee530e6ee75bad6fd30c98e5ad9dab
export function deepGet(obj, query, defaultVal) {
query = Array.isArray(query) ? query : query.replace(/(\[(\d)\])/g, '.$2').replace(/^\./, '').split('.');
if (!(query[0] in obj)) {
return defaultVal;
}
obj = obj[query[0]];
if (obj && query.length > 1) {
return deepGet(obj, query.slice(1), defaultVal);
}
return obj;
}
import deepGet from './deepGet.js';
describe('deepGet', () => {
const testObj = {
a: {
b: {
c: {
d: 123
}
},
e: [
{ f: 9 },
{ g: 10 }
]
}
};
const testArr = [
{ id: 1, comments: [{ text: 'hello' }, { text: 'goodbye' }] },
{ id: 2, comments: [] }
];
const falseyObj = {
isUndefined: undefined,
isNull: null,
isZero: 0,
isEmptyString: ''
};
it('handles nested objects', () => {
expect(deepGet(testObj, 'a.b.c.d')).toBe(123);
});
it('handles arrays inside an object', () => {
expect(deepGet(testObj, 'a.e[0].f')).toBe(9);
});
it('handles objects inside an array', () => {
expect(deepGet(testArr, '[0].comments[1].text')).toBe('goodbye');
});
it('returns the default value if query was not found', () => {
const defaultVal = 'oh no';
expect(deepGet(testObj, 'invalid.not[0].found', defaultVal)).toBe(defaultVal);
});
it('returns falsey values, including undefined', () => {
const defaultVal = 'my default';
expect(deepGet(falseyObj, 'isUndefined', defaultVal)).toBe(undefined);
expect(deepGet(falseyObj, 'isNull', defaultVal)).toBe(null);
expect(deepGet(falseyObj, 'isZero', defaultVal)).toBe(0);
expect(deepGet(falseyObj, 'isEmptyString', defaultVal)).toBe('');
});
});
@andrewchilds
Copy link
Author

if obj is undefined, what would you return?

Thanks for writing and sharing this, Andrew. It's very helpful 🙇🏻‍♂️

My pleasure! Glad it's helpful. If obj is pretty much anything other than an Object or Array, it will throw an exception like this:

deepGet(undefined, 'hello', 'default')
VM76:3 Uncaught TypeError: Cannot use 'in' operator to search for 'hello' in undefined
    at deepGet (<anonymous>:3:18)

I suppose this could be updated to guard against that, but what should it return instead? null, undefined, false?

@simkimsia
Copy link

simkimsia commented Jul 4, 2022

but what should it return instead? null, undefined, false?
return default value?

update ignore my previous message about some error. i was wrong

@Shailesh200
Copy link

This is throwing error:

const obj = {
  "one": {
    "a": {
      "c": [
        "a",
        {
          "b": 123
        }
      ]
    }
  },
  "two": 50,
  "three": 75,
  "four": 12
}
deepGet(obj, 'one.a.c[1].b[0]', 'hi')

Error: Uncaught TypeError: Cannot use 'in' operator to search for '0' in 123
A falsy case not handled.

@alexzobi
Copy link

alexzobi commented Jan 11, 2023

Thanks for this. If you want to mimic the actual functionality of lodash's get, then your logic and your test on line 49 is incorrect. lodash.get will return the default value even if the key in the object exists but the value is undefined. therefore the test on line 49 should return "my default". I've updated to match lodash, fix @Shailesh200 's error, added typescript support and removed param mutation.

type GetReturnType<T> = T | undefined
type ValueType = Record<string | number, unknown>

function deepGet<T>(
  value: unknown,
  query: string | Array<string | number>,
  defaultVal: GetReturnType<T> = undefined
): GetReturnType<T> {
  const splitQuery = Array.isArray(query) ? query : query.replace(/(\[(\d)\])/g, '.$2').replace(/^\./, '').split('.');

  if (!splitQuery.length || splitQuery[0] === undefined) return value as T

  const key = splitQuery[0]

  if (
    typeof value !== 'object'
    || value === null
    || !(key in value)
    || (value as ValueType)[key] === undefined
  ) {
    return defaultVal
  }

  return deepGet((value as ValueType)[key], splitQuery.slice(1), defaultVal)
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment