Skip to content

Instantly share code, notes, and snippets.

@harish2704
Last active June 3, 2023 23:41
Show Gist options
  • Star 13 You must be signed in to star a gist
  • Fork 5 You must be signed in to fork a gist
  • Save harish2704/d0ee530e6ee75bad6fd30c98e5ad9dab to your computer and use it in GitHub Desktop.
Save harish2704/d0ee530e6ee75bad6fd30c98e5ad9dab to your computer and use it in GitHub Desktop.
Simple lodash.get function in javascript
/* Implementation of lodash.get function */
function getProp( object, keys, defaultVal ){
keys = Array.isArray( keys )? keys : keys.split('.');
object = object[keys[0]];
if( object && keys.length>1 ){
return getProp( object, keys.slice(1) );
}
return object === undefined? defaultVal : object;
}
/* Implementation of lodash.set function */
function setProp( object, keys, val ){
keys = Array.isArray( keys )? keys : keys.split('.');
if( keys.length>1 ){
object[keys[0]] = object[keys[0]] || {};
return setProp( object[keys[0]], keys.slice(1), val );
}
object[keys[0]] = val;
}
@devmuffy
Copy link

Function getProp contains error. Look at the example below:

function getProp( object, keys, defaultVal ){
  keys = Array.isArray( keys )? keys : keys.split('.');
  object = object[keys[0]];
  if( object && keys.length>1 ){
    return getProp( object, keys.slice(1));
  }
  return object === undefined? defaultVal : object;
}

console.log(getProp({}, 'x', 'DEFAULT_VAL')); // => 'DEFAULT_VAL'
console.log(getProp({}, 'x.x', 'DEFAULT_VAL')); // => 'DEFAULT_VAL'
console.log(getProp({x: {}}, 'x.x', 'DEFAULT_VAL')); // => undefined

You don't pass down defaultVal to recursive call. Fixed version:

function getProp( object, keys, defaultVal ){
  keys = Array.isArray( keys )? keys : keys.split('.');
  object = object[keys[0]];
  if( object && keys.length>1 ){
    return getProp( object, keys.slice(1), defaultVal );
  }
  return object === undefined? defaultVal : object;
}

console.log(getProp({}, 'x', 'DEFAULT_VAL')); // => 'DEFAULT_VAL'
console.log(getProp({}, 'x.x', 'DEFAULT_VAL')); // => 'DEFAULT_VAL'
console.log(getProp({x: {}}, 'x.x', 'DEFAULT_VAL')); // => 'DEFAULT_VAL'

@manjunatha-d
Copy link

I guess return object ? object : defaultVal is more cleaner than return object === undefined? defaultVal : object;

@devmuffy
Copy link

It is clearer but those aren't the same statements.

// I'm also comparing _.get here
function getProp(object, keys, defaultVal) {
    keys = Array.isArray(keys) ? keys : keys.split('.');
    object = object[keys[0]];
    if (object && keys.length > 1) {
        return getProp(object, keys.slice(1), defaultVal);
    }
    return object === undefined ? defaultVal : object;
}

function getPropClearer(object, keys, defaultVal) {
    keys = Array.isArray(keys) ? keys : keys.split('.');
    object = object[keys[0]];
    if (object && keys.length > 1) {
        return getPropClearer(object, keys.slice(1), defaultVal);
    }
    return object ? object : defaultVal;
}

var testEmptyString = { x: '' }
console.log(
    getProp(testEmptyString, 'x', 'DEFAULT_VAL'), // => ''
    _.get(testEmptyString, 'x', 'DEFAULT_VAL'), // => ''
    getPropClearer(testEmptyString, 'x', 'DEFAULT_VAL') // => 'DEFAULT_VAL'
)

var testNull = { x: null }
console.log(
    getProp(testNull, 'x', 'DEFAULT_VAL'), // => null
    _.get(testNull, 'x', 'DEFAULT_VAL'), // => null
    getPropClearer(testNull, 'x', 'DEFAULT_VAL') // => 'DEFAULT_VAL'
)

var testNestedFalse = { x: { y: false } }
console.log(
    getProp(testNestedFalse, 'x.y', 'DEFAULT_VAL'), // => false
    _.get(testNestedFalse, 'x.y', 'DEFAULT_VAL'), // => false
    getPropClearer(testNestedFalse, 'x.y', 'DEFAULT_VAL') // => 'DEFAULT_VAL'
)

Here you can find pretty nice explanation what's going on.

@wingliu0
Copy link

This version can also support array syntax
Like a[0].b.c

__get(object, keys, defaultVal = null): any {
    keys = Array.isArray(keys) ? keys : keys.replace(/(\[(\d)\])/g, '.$2').split('.');
    object = object[keys[0]];
    if (object && keys.length > 1) {
      return this.__get(object, keys.slice(1), defaultVal);
    }
    return object === undefined ? defaultVal : object;
  }

@pi0
Copy link

pi0 commented Feb 1, 2018

This version is slightly optimized and also supports root selectors (getProp({ foo: 'bar'}, '' => { foo:bar } ):

/**
 * getProp utility - an alternative to lodash.get
 * @author @harish2704, @muffypl, @pi0
 * @param {Object} object
 * @param {String|Array} path
 * @param {*} defaultVal
 */
function getProp (object, path, defaultVal) {
  const _path = Array.isArray(path)
    ? path
    : path.split('.').filter(i => i.length)

  if (!_path.length) {
    return object === undefined ? defaultVal : object
  }

  return getProp(object[_path.shift()], _path, defaultVal)
}

@imnnquy
Copy link

imnnquy commented Jan 15, 2019

The above version has an issue when object is undefined and _path length > 0, an improvement here:

/**
 * getProp utility - an alternative to lodash.get
 * @author @harish2704, @muffypl, @pi0, @imnnquy
 * @param {Object} object
 * @param {String|Array} path
 * @param {*} defaultVal
 */
function getProp (object, path, defaultVal) {
  const _path = Array.isArray(path)
    ? path
    : path.split('.').filter(i => i.length)

  if (!_path.length) {
    return object === undefined ? defaultVal : object
  }

  return getProp(object[_path.shift()], _path, defaultVal)
}

@itneste
Copy link

itneste commented Jul 1, 2019

that's will be better.

 function getProp (object, path, defaultVal) {
    const PATH = Array.isArray(path)
      ? path
      : path.split('.').filter(i => i.length);
    if (!PATH.length) {
      return object === undefined ? defaultVal : object;
    }
    if (object === null || object === undefined || typeof (object[PATH[0]]) === 'undefined') {
      return defaultVal;
    }
    return getProp(object[PATH.shift()], PATH, defaultVal);
}

@wcdbmv
Copy link

wcdbmv commented Sep 3, 2019

that's will be better

/**
 * Gets the value at `path` of `object`.
 * @param {Object} object
 * @param {string|Array} path
 * @returns {*} value if exists else undefined
 */
const get = (object, path) => {
	if (typeof path === "string") path = path.split(".").filter(key => key.length);
	return path.reduce((dive, key) => dive && dive[key], object);
};

@alexbruno
Copy link

alexbruno commented Oct 8, 2019

The above version doesn't support some formats supported by lodash.get.

Like: get(obj, ['forecast', 0, 'main.temp'], null)

But this version do it:

function get (object, path, value) {
  const pathArray = Array.isArray(path) ? path : path.split('.').filter(key => key)
  const pathArrayFlat = pathArray.flatMap(part => typeof part === 'string' ? part.split('.') : part)

  return pathArrayFlat.reduce((obj, key) => obj && obj[key], object) || value
}

@Sooro1024
Copy link

Sooro1024 commented Jan 22, 2020

The above version don't support some formats supported by lodash.get.

Like: get(obj, ['forecast', 0, 'main.temp'], null)

But this version do it:

function get (object, path, value) {
  const pathArray = Array.isArray(path) ? path : path.split('.').filter(key => key)
  const pathArrayFlat = pathArray.flatMap(part => typeof part === 'string' ? part.split('.') : part)

  return pathArrayFlat.reduce((obj, key) => obj && obj[key], object) || value
}

If return value is falsy it will not play properly.

function get (object, path, value) {
  const pathArray = Array.isArray(path) ? path : path.split('.').filter(key => key);
  const pathArrayFlat = pathArray.flatMap(part => typeof part === 'string' ? part.split('.') : part);
  const checkValue = pathArrayFlat.reduce((obj, key) => obj && obj[key], object);
  return  checkValue === undefined ? value : checkValue
}

@alexbruno
Copy link

Sorry @Sooro1024, but I don't think it's necessary!
Did you notice the || in my return?

return pathArrayFlat.reduce((obj, key) => obj && obj[key], object) || value

If the reduce result is falsy (undefined inclusive), the value is returned.

So, if the expected return is a number, just pass a value = 0. And if a boolean is expected, just pass a value = false.

So, I think extra code is not necessary. Just take some Node or DevTools console test:

js

And here is a use test:

get

@Sooro1024
Copy link

Sooro1024 commented Jan 28, 2020 via email

@souljorje
Copy link

souljorje commented Nov 23, 2021

@pi0, you solution throws an error, but thanks for inspiration for the most performant solution from provided here.

const getPropByPath = (object, path, defaultValue) => {
    const _path = Array.isArray(path)
        ? path
        : path.split('.');
    if (object && _path.length) return getPropByPath(object[_path.shift()], _path, defaultValue);
    return object === undefined ? defaultValue : object;
};

// without default value
const getPropByPath = (object, path) => {
    const _path = Array.isArray(path)
        ? path
        : path.split('.');
    if (object && _path.length) return getPropByPath(object[_path.shift()], _path);
    return object;
};

Benchmarks: https://jsbench.me/ogkwc7fxlg/1. I didn't include implementations out of scope of original lodash.get and those which iterate over whole path without break - i.e. reduce-based - they're too slow.

@specimen151
Copy link

@pi0, you solution throws an error, but thanks for inspiration for the most performant solution from provided here.

const getPropByPath = (object, path, defaultValue) => {
    const _path = Array.isArray(path)
        ? path
        : path.split('.');
    if (object && _path.length) return getPropByPath(object[_path.shift()], _path, defaultValue);
    return object === undefined ? defaultValue : object;
};

// without default value
const getPropByPath = (object, path) => {
    const _path = Array.isArray(path)
        ? path
        : path.split('.');
    if (object && _path.length) return getPropByPath(object[_path.shift()], _path);
    return object;
};

Benchmarks: https://jsbench.me/ogkwc7fxlg/1. I didn't include implementations out of scope of original lodash.get and those which iterate over whole path without break - i.e. reduce-based - they're too slow.

This solution fixed for me a problem: Empty string counts as undefined.
I'd like the empty string back. It's not the same (for me).

I'm talking about the version without default value.

@romanown
Copy link

setProp is not work.

@andrewchilds
Copy link

I created a fork of this version that includes tests, handles falsey values (including undefined), and handles objects-inside-arrays (i.e. '[0].id') as well as arrays-inside-objects (i.e. 'a.b[0].c'):

https://gist.github.com/andrewchilds/30a7fb18981d413260c7a36428ed13da

@romanown
Copy link

many thanks

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