Skip to content

Instantly share code, notes, and snippets.

@alvaropinot
Last active June 21, 2016 09:59
Show Gist options
  • Save alvaropinot/39fdc70739646114f5708f5aafb96222 to your computer and use it in GitHub Desktop.
Save alvaropinot/39fdc70739646114f5708f5aafb96222 to your computer and use it in GitHub Desktop.
get.js
function _get(property, defaultValue) {
var path = property.split(/[\.\["'\]]+/);
return function(obj) {
return typeof obj === 'object' ? path.reduce(function (acc, key) {
var value = acc && acc[key];
// should allow null and falsy values.
return (value || (value !== undefined)) ?
value :
defaultValue;
}, obj) :
obj;
}
}
test('should return a level one property', function () {
var obj = {
foo: 'foo'
};
var actual = _get('foo')(obj),
expected = 'foo';
equal(actual, expected);
});
test('should return a level two property', function () {
var obj = {
foo: {
bar: 'bar'
}
};
var actual = _get('foo.bar')(obj),
expected = 'bar';
equal(actual, expected);
});
test('called with no value', function () {
var actual = _get('foo.bar')(),
expected = undefined;
equal(actual, expected, 'should return undefined');
});
test('called with a primitive value instead of an object', function () {
var actual = _get('foo.bar')(2),
expected = 2;
equal(actual, expected, 'should return the value');
});
test('should allow a default value when a property its not defined', function () {
var obj = {
foo: {
bar: 'bar'
}
};
var actual = _get('foo.biz', 5)(obj),
expected = 5;
equal(actual, expected, 'should return the default value');
actual = _get('bar', 5)(obj);
expected = 5;
equal(actual, expected, 'should return the default value');
});
test('should work for keys with `null` values', function () {
var obj = {
foo: {
bar: null
}
};
var actual = _get('foo.bar', 5)(obj),
expected = null;
equal(actual, expected, 'should return null');
});
test('should work for keys with `false` values', function () {
var obj = {
foo: {
bar: false
}
};
var actual = _get('foo.bar', 5)(obj),
expected = false;
equal(actual, expected, 'should return false');
});
test('should work for keys with `0` values', function () {
var obj = {
foo: {
bar: 0
}
};
var actual = _get('foo.bar', 5)(obj),
expected = 0;
equal(actual, expected, 'should return 0');
});
test('should return default value for undefined keys', function () {
var obj = {
foo: {
bar: undefined
}
};
var actual = _get('foo.bar', 5)(obj),
expected = 5;
equal(actual, expected, 'should return the default value');
});
test('should return default value for undefined keys', function () {
var obj = {
foo: {
bar: {
biz: 'biz'
}
}
};
var actual = _get('foo.bar.biz', 5)(obj),
expected = 'biz';
equal(actual, expected);
});
// TODO: move to array tests module and clean those objs.
test('should work with deep arrays', function () {
var obj = {
foo: {
arr: [
{},
{
subarr: [
{},
{
name:'rita'
}
]
}
]
}
};
var actual = _get('foo.arr[1].subarr[1].name')(obj),
expected = 'rita';
equal(actual, expected);
});
test('should work with deep arrays and undenined properties', function () {
var obj = {
foo: {
arr: [
{},
{
subarr: [
{},
{
name:'rita'
}
]
}
]
}
};
var actual = _get('foo.arr[2].subarr[5].name')(obj),
expected = undefined;
equal(actual, expected);
});
test('should work with deep arrays and return defaultValue', function () {
var obj = {
foo: {
arr: [
{},
{
subarr: [
{},
{
name:'rita'
}
]
}
]
}
};
var actual = _get('foo.arr[2].subarr[5].name', 2)(obj),
expected = 2;
equal(actual, expected);
});
@sergueyarellano
Copy link

sergueyarellano commented Jun 16, 2016

function _get(parent, nested) {
    return typeof nested === 'string' ?
        (typeof parent === 'object' &&
        nested
            .split(/[\.\["'\]!]/)
            .filter(function(el) {
                return /(exists)/.test(el) ? false : el;
            })
            .reduce(function(acc, current, index, arr) {
                return index === arr.length - 1 && nested.indexOf('exists!') === 0 ?
                    acc && typeof acc[current] !== 'undefined' :
                    (acc || false) && acc[current];
            }, parent)) :
        parent;
}


var ns = {a:{b:{c:0}}};

console.log('get(ns, "")',_get(ns, '')) // Object {a: Object}
console.log("get(ns, 'a.b.')", _get(ns, 'a.b.')) // Object {c: 0}
console.log("get(ns, 'a.b.d')", _get(ns, 'a.b.d')) // undefined
console.log("get(ns, 'a.b.c')", _get(ns, 'a.b.c')) // 0
console.log("get(ns, 'u.b.c')", _get(ns, 'u.b.c')) // undefined

console.log("get(ns, 'a['b'].c')", _get(ns, 'a["b"].c')) // 0
console.log("get(ns, 'a.b['c']')", _get(ns, 'a.b["c"]')) // 0
console.log("get(ns, 'a['b']['c']')", _get(ns, 'a["b"]["c"]')) // 0
console.log("get(ns, 'a.b['forgery']')", _get(ns, 'a.b["forgery"]')) // undefined


console.log("get(ns, 'exists!a.b.')", _get(ns, 'exists!a.b.')) // true
console.log("get(ns, 'exists!a.b.d')", _get(ns, 'exists!a.b.d')) // false
console.log("get(ns, 'exists!a.b.c')", _get(ns, 'exists!a.b.c')) // true
console.log("get(ns, 'exists!u.b.c')", _get(ns, 'exists!u.b.c')) // false
console.log("get(ns, 'exists!u.b.c.d.e')", _get(ns, 'exists!u.b.c.d.e')) // false


console.log("get(ns, 'exists!a['b'].c')", _get(ns, 'exists!a["b"].c')) // true
console.log("get(ns, 'exists!a.b['c']')", _get(ns, 'exists!a.b["c"]')) // true
console.log("get(ns, 'exists!a['b']['c']')", _get(ns, 'exists!a["b"]["c"]')) // true
console.log("get(ns, 'exists!a.b['forgery']')", _get(ns, 'exists!a.b["forgery"]')) // false
console.log("get(ns, 'exists!u.b['forgery'].c')", _get(ns, 'exists!u.b["forgery"].c')) // false


console.log("get(ns, 2)",_get(ns, 2)) // Object {a: Object}
console.log("get(ns, function(){})", _get(ns, function(){})) // Object {a: Object}

console.log("get(ns)", _get(ns)) // Object {a: Object}
console.log("get('')", _get('')) // ""
console.log("get(false)", _get(false)) // false

@alvaropinot
Copy link
Author

how about something like 'a[2]["a"]'.split(/[\.\["'\]]+/).slice(0, -1) instead of that filter?

@alvaropinot
Copy link
Author

alvaropinot commented Jun 17, 2016

I was thinking about a npm package named get-puntillo, get-el-puntillo, get-puntito or something funny :D

By the way, there are like 100 getters like this one, not just the lodash one hahaha

@sergueyarellano
Copy link

hahaha, nice job merging those, I think get-puntillo sounds perfect!

There are lots of them...yeah...but we got ours!

get-puntillo in da house! make some noooise

@sergueyarellano
Copy link

sergueyarellano commented Jun 20, 2016

hey man, I'm still getting "undefined" when using [""] syntax

and "split" method still returning "empty string" as last element of the array when using [""]. It sounds logic, because the expression always matches /\]/.

Still thinking, this kind of approach fits better:

function _get(parent, nested, defaultValue) {
    return typeof nested === 'string' ?
        (typeof parent === 'object' &&
        nested
            .split(/[\.\["'\]]+/)
            .reduce(function(acc, current) {
                return /^$/.test(current) ? acc : acc && acc[current]; // checking for empty strings
            }, parent)) || defaultValue: // return default if value is falsy
        parent;
}

var ns = {a:{b:{c:0}}};

console.log(_get(ns, '')) // Object {a: Object}
console.log( _get(ns, 'a.b.')) // Object {c: 0}
console.log( _get(ns, 'a.b.d')) // undefined
console.log(_get(ns, 'a.b.c', 2)) // 2
console.log( _get(ns, 'u.b.c')) // undefined
console.log( _get(ns, 'u.b.c', 3)) // 3

Although we do not have memoization yet, we could implement it if needed with your "closure" style.

;)

PS: Cannot use "slice" because not all the cases for "split" return empty string as last element

@sergueyarellano
Copy link

another variant if you want a default value only if you pass it, and the current value is falsy:

function _get(parent, nested, defaultValue) {
    var argsLength = [].slice.call(arguments).length; // get arguments length
    var value = typeof nested === 'string' ?
        (typeof parent === 'object' &&
        nested
            .split(/[\.\["'\]]+/)
            .reduce(function(acc, current) {
                return /^$/.test(current) ? acc : acc && acc[current]; // checking for empty strings
            }, parent)) :
        parent;
    return argsLength === 3 ? value || defaultValue : value; // if defaultValue is passed, then return default only if value is falsy
}

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