Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Alternative to lodash get method _.get()
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);
}
}
@jeneg
Copy link
Author

jeneg commented Apr 1, 2016

Usage example

var prop = get(data, '[0].items[0].property', 'backupValue');

Loading

@bigchirv
Copy link

bigchirv commented Dec 1, 2016

Hey, thanks for this. I wrote this other one based on yours:

const get = (obj, path, def) => (() => typeof path === 'string' ? path.replace(/\[(\d+)]/g,'.$1') : path.join('.'))()
  .split('.')
  .filter(Boolean)
  .every(step => ((obj = obj[step]) !== undefined)) ? obj : def

Loading

@wxs77577
Copy link

wxs77577 commented Oct 16, 2018

Thanks for share, I use reduce, it supports dot notation . only.

const get = (value, path, defaultValue) => {
  return String(path).split('.').reduce((acc, v) => {
    try {
      acc = acc[v]
    } catch (e) {
      return defaultValue
    }
    return acc
  }, value)
}
console.log(get([[[[120]]]], '0.0.0.0', 0))

Loading

@diegolealco
Copy link

diegolealco commented Apr 18, 2019

NOTE disregard this, it has very poor performance.

I made a different function for the same purpose.

function attempt(getter, fallback) {
  try {
    return getter();
  } catch (error) {
    return fallback;
  }
}

and here is how I use it.

const prop = attempt(() => some.deeply['nested'].value, 'fallback value')

Loading

@KrickRay
Copy link

KrickRay commented Apr 28, 2019

@NoPause I would not advise to do so. It's very slow with errors

Loading

@alex-malyita
Copy link

alex-malyita commented Jul 25, 2019

import {expect} from "chai";
import { get } from './helpers';

describe("Helpers - get", function () {
    it("should return fallback value", function () {
        const input = {
            test: 'test'
        };
        expect(get(input, 'noexist', null)).to.deep.equal(null);
    });
    it("should return fallback value for inner level", function () {
        const input = {
            test: 'test'
        };
        expect(get(input, 'noexist.nested.noexist', null)).to.deep.equal(null);
    });
    it("should return value first level", function () {
        const input = {
            test: 'test'
        };
        expect(get(input, "test", null)).to.deep.equal("test");
    });
    it("should return value nested level", function () {
        const input = {
            test: {
                nested: {
                    test: "test"
                }
            }
        };
        expect(get(input, 'test.nested.test', null)).to.deep.equal("test");
    });
});

Loading

@SupremeTechnopriest
Copy link

SupremeTechnopriest commented Aug 29, 2019

Hey all! I did a little benchmarking on the methods provided here. Here are the results:

const { Suite } = require('benchmark')

const suite = new Suite()

const get1 = function get (obj, path, def) {
  const fullPath = path
    .replace(/\[/g, '.')
    .replace(/]/g, '')
    .split('.')
    .filter(Boolean)

  return fullPath.every(everyFunc) ? obj : def

  function everyFunc (step) {
    return !(step && (obj = obj[step]) === undefined)
  }
}

const get2 = (obj, path, def) => (() => typeof path === 'string' ? path.replace(/\[(\d+)]/g, '.$1') : path.join('.'))()
  .split('.')
  .filter(Boolean)
  .every(step => ((obj = obj[step]) !== undefined)) ? obj : def

const get3 = (value, path, defaultValue) => {
  return String(path).split('.').reduce((acc, v) => {
    try {
      acc = acc[v]
    } catch (e) {
      return defaultValue
    }
    return acc
  }, value)
}

const obj = { a: { b: { c: 'd' } } }

// add tests
suite
  .add('get1', function () {
    get1(obj, 'a.b.c', undefined)
  })
  .add('get2', function () {
    get2(obj, 'a.b.c', undefined)
  })
  .add('get3', function () {
    get3(obj, 'a.b.c', undefined)
  })
  .on('cycle', function (event) {
    console.log(String(event.target))
  })
  .on('complete', function () {
    console.log('Fastest is ' + this.filter('fastest').map('name'))
  })
  .run({ 'async': true })
get1 x 2,971,925 ops/sec ±0.60% (90 runs sampled)
get2 x 1,743,194 ops/sec ±2.77% (87 runs sampled)
get3 x 5,272,977 ops/sec ±2.20% (84 runs sampled)
Fastest is get3

The best implementation is @wxs77577's. I omitted @diegolealco submission because it didn't pass my requirement test.

Loading

@diegolealco
Copy link

diegolealco commented Sep 2, 2019

I had forgotten about this.
Thanks @KrickRay you are right, it isn't such a good idea.

Loading

@mcissel
Copy link

mcissel commented Sep 13, 2019

It doesn't always return the default value though

get({r:5}, 't', 34)

returns undefined

Loading

@SupremeTechnopriest
Copy link

SupremeTechnopriest commented Sep 16, 2019

@mcissel I noticed that too and was able to fix it with the following:

const get3 = (value, path, defaultValue) => {
  return String(path).split('.').reduce((acc, v) => {
    try {
      acc = acc[v] || defaultValue
    } catch (e) {
      return defaultValue
    }
    return acc
  }, value)

Performance didn't change.

Loading

@DominicTobias
Copy link

DominicTobias commented Jan 25, 2020

@SupremeTechnopriest thanks for the benchmark, would be interesting to see lodash's one too to compare. Lodash has quite a lot of methods it uses behind the scenes which are needed to run on older browsers and overall will be the most robust, but would be interesting to see perf difference

Loading

@KrickRay
Copy link

KrickRay commented Jan 27, 2020

@DominicTobias you can already forget about the get, browsers will add https://github.com/tc39/proposal-optional-chaining

Loading

@roddds
Copy link

roddds commented Jan 29, 2020

@SupremeTechnopriest the || on line 4 needs to be a bit more specific:

const get = (value, path, defaultValue) =>
  String(path)
    .split('.')
    .reduce((acc, v) => {
      try {
        acc = acc[v] === undefined ? defaultValue : acc[v];
      } catch (e) {
        return defaultValue;
      }
      return acc;
    }, value);

And if you don't care about multiple returns:

const get = (value, path, defaultValue) =>
  String(path)
    .split('.')
    .reduce((acc, v) => {
      try {
        return acc[v] === undefined ? defaultValue : acc[v];
      } catch (e) {
        return defaultValue;
      }
    }, value);

Loading

@darrylsepeda
Copy link

darrylsepeda commented May 29, 2020

@SupremeTechnopriest It's nice to see your benchmark, but third solution doesn't cater array index in object.

const get3 = (value, path, defaultValue) => {
  return String(path).split('.').reduce((acc, v) => {
    try {
      acc = acc[v] || defaultValue
    } catch (e) {
      return defaultValue
    }
    return acc
  }, value)

For example: get(data, "[0].name"); (it shows nothing in my case)

I ended up using 2nd solution for my case.

Loading

@SupremeTechnopriest
Copy link

SupremeTechnopriest commented May 29, 2020

@darrylsepeda Soon we will be able to replace get with nullish coalescing and optional chaining. https://arpitbatra.netlify.app/posts/lodash-new-js/

Loading

@MalikBagwala
Copy link

MalikBagwala commented Oct 15, 2020

@da

@SupremeTechnopriest It's nice to see your benchmark, but third solution doesn't cater array index in object.

const get3 = (value, path, defaultValue) => {
  return String(path).split('.').reduce((acc, v) => {
    try {
      acc = acc[v] || defaultValue
    } catch (e) {
      return defaultValue
    }
    return acc
  }, value)

For example: get(data, "[0].name"); (it shows nothing in my case)

I ended up using 2nd solution for my case.

You should access it using '0.name' (remove the square braces)

Loading

@nischithbm
Copy link

nischithbm commented Nov 4, 2020

const get3 = (value, path, defaultValue) => {
  return String(path).split('.').reduce((acc, v) => {
    try {
      acc = acc[v] || defaultValue
    } catch (e) {
      return defaultValue
    }
    return acc
  }, value);
}

get3({ a: { b: { c: 0 } } }, 'a.b.c', 'defaultVal') // => this will return defaultVal whereas it should actually be returning 0
get3({ a: { b: { c: "" } } }, 'a.b.c', 'defaultVal') // => same here

this is happening due to this statement
acc[v] || defaultValue

it would end up evaluating for this truthfulness, instead of just for undefined or null cases!

So, the better implementation (functionality-wise) would be as follows

get4 = (value, path, defaultValue) => {
  return String(path).split('.').reduce((acc, v) => {
    try {
      acc = (acc[v] !== undefined && acc[v] !== null) ? acc[v] : defaultValue
    } catch (e) {
      return defaultValue
    }
    return acc
  }, value);
}

Another enhacement we could do within the implementation would be

'a[0].b.c'.replace(/[/g, '.').replace(/]/g, '') // => results into "a.0.b.c"
['a', '0', 'b', 'c'].join('.'); // => results into "a.0.b.c"

Once we have all different formats converted to a simple dot notation, we could use the above implementation.

Hence covering all use cases supported by lodash.get as shown below
image

Loading

@DominicTobias
Copy link

DominicTobias commented Nov 4, 2020

Loading

@gabrieljmj
Copy link

gabrieljmj commented Aug 11, 2021

The main reason of developing it was that the get lodash method was the only method I was using from it and no support no nullish operator on my working node version.

const get = (obj, path, defaultValue) => {
    const result = path.split('.').reduce((r, p) => {
        if (typeof r === 'object') {
            p = p.startsWith("[") ? p.replace(/\D/g, "") : p;

            return r[p];
        }

        return undefined;
    }, obj);

    return result !== undefined ? defaultValue : result;
};

Running this same benchmark test with the function named as get4 got this results:

get1 x 2,192,434 ops/sec ±1.10% (92 runs sampled)
get2 x 1,097,014 ops/sec ±0.84% (92 runs sampled)
get3 x 4,552,938 ops/sec ±0.56% (93 runs sampled)
get4 x 4,604,406 ops/sec ±0.33% (89 runs sampled)
Fastest is get4

Loading

@haouarihk
Copy link

haouarihk commented Oct 21, 2021

just use obj?.param1?.param2 ?? defaultvalue

Loading

@SupremeTechnopriest
Copy link

SupremeTechnopriest commented Oct 21, 2021

just use obj?.param1?.param2 ?? defaultvalue

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

@darrylsepeda Soon we will be able to replace get with nullish coalescing and optional chaining.

Loading

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