Zoe Edwards
Zoe Edwards

Reputation: 13697

Resolve Object namepath with JavaScript

I have an object:

const list = {
    library: {
        books: {
            count: 100
        }
    }
}

And I have an array:

const key = [ 'library', 'books', 'count' ]

I’d like to get the value 100 from the object using that array.

I’ve tried just using the array:

const list = {
    library: {
        books: {
            count: 100
        }
    }
}

const key = [ 'library', 'books', 'count' ]

console.log(list[key])

No surprises here, returns undefined.

And I’ve tried converting it to a string:

const list = {
    library: {
        books: {
            count: 100
        }
    }
}

const key = [ 'library', 'books', 'count' ]

console.log(list[key.toString()])

Again obviously returns undefined.

I can of course just loop through the object using the array – but I was hoping that somebody knew of a lovely one-liner that would work like magic.

Upvotes: 1

Views: 250

Answers (3)

colxi
colxi

Reputation: 8730

What you are trying to implement is known as a namepath resolution. You can achieve it, using reduce, or a regular for iteration.

I've prepared some benchmarks here : Performance test : reduce vs for loop

The for loop, is aparently 10% faster than reduce

( tested in Chrome 65.0.3325 / Windows 10 0.0.0 )


For loop

Faster, and maybe easier to understand.

// namepath resolver 
function resolvePath(target, namepath){
    for( let i=0 ; i< namepath.length;i++ ) target = target[namepath[i] ];
    return target;
};

// your target
const list = {
    library: {
        books: {
            count: 100
        }
    }
};
// your namepath
const key = [ 'library', 'books', 'count' ];

alert( resolvePath(list, key) ) // outputs 100


Array.prototype.reduce() (since ES5)

Shorter and more elegant, but less intuitive at the beggining. And according to my performance tests, slower.

Usage: Mozilla Developer Documentation of Reduce

// your target
const list = {
    library: {
        books: {
            count: 100
        }
    }
};
// your namepath
const key = [ 'library', 'books', 'count' ];

alert ( key.reduce((target,key) => target[key], list) )


Suicidal mode

If you don't care about security, and you are in the mood of entering the real danger zone...

eval( 'list.' + key.join('.') )

Definitely the shortest :D

(don't even think about using this)

Upvotes: 1

Muhammad Usman
Muhammad Usman

Reputation: 10148

Try this. A shorter version. Just for fun :)

const list = {
    library: {
        books: {
            count: 100
        }
    }
}
const key = [ 'library', 'books', 'count' ]
console.log(list[key[0]][key[1]][key[2]])

Upvotes: 0

Mark
Mark

Reputation: 92461

The most succinct way I know is with reduce:

const list = {
    library: {
        books: {
            count: 100
        }
    }
}

const key = [ 'library', 'books', 'count' ]

// simple one-liner
let val = key.reduce((obj,key) => obj[key], list)
console.log(val)

Of course, you'll probably want to do some checks if you're dealing with inconsistent data.

You can also use a recursive function but not quite as short:

const list = {
    library: {
        books: {
            count: 100
        }
    }
}

const key = [ 'library', 'books', 'count' ]

function getKey(obj, list) {
    let key = list.shift()
    if (key === undefined) return
    let val = obj[key]
    if (typeof val !== 'object') return val
    return getKey(val, list)

}

let val = getKey(list, key)
console.log(val)

Upvotes: 3

Related Questions