Search code examples
javascriptjsonobjectpropertiespropertypath

How to get object property dynamically using a custom function


I have a funciton that accept a property name

func(propertyName) {
  return object[propertyName];
}

so by calling func('value') will return the object.value

However the object is complex, so it has inner props

What I want to do is to be able to do that func('property1.property2')

What is the best way to do that?


Solution

  • A combination of reduce and the Optional chaining operator ensures both a fail safe implementation and a fail safe access of any passed property key path at any passed type/object ...

    function getValueByKeyPath(obj, path) {
      return String(path)
        .split('.')
        .reduce((value, key) => value?.[key], Object(obj))
    }
    
    const sampleData = {
      foo: {
        value: 'foo',
        bar: {
          value: 'bar',
          baz: {
            value: 'baz',
          },
        },
      },
    };
    
    console.log(
      "getValueByKeyPath(sampleData, 'foo.bar.baz') ...",
      getValueByKeyPath(sampleData, 'foo.bar.baz')
    );
    console.log(
      "getValueByKeyPath(sampleData, 'foo.bar.baz.value') ...",
      getValueByKeyPath(sampleData, 'foo.bar.baz.value')
    );
    
    console.log(
      "\nfail safe ... getValueByKeyPath(sampleData, 'foo.biz.baz.value') ...",
      getValueByKeyPath(sampleData, 'foo.biz.baz.value')
    );
    
    console.log(
      "\nfail safe ... getValueByKeyPath('', 'toString') ...",
      getValueByKeyPath('', 'toString')
    );
    console.log(
      "fail safe ... getValueByKeyPath(null, '') ...",
      getValueByKeyPath(null, '')
    );
    console.log(
      "fail safe ... getValueByKeyPath() ...",
      getValueByKeyPath()
    );
    .as-console-wrapper { min-height: 100%!important; top: 0; }

    The above approach can be extended to bracket notation with both quoted and unquoted keys for e.g. nested/mixed object and array based data-structures. Then one does not split the key-path at simply any dot but also at any opening and closing bracket while capturing the value inside the brackets. The result of this split operation needs to be sanitized from some falsy artifacts, the reducing part stays the same ...

    function getValueByKeyPath(obj, path) {
      return String(path)
        .split(/\.|\[['"]?([^'"\]]*)['"]?\]/)
        .filter(elm => !!elm)
        .reduce((value, key) => value?.[key], Object(obj))
    }
    
    const sampleDataA = {
      foo: {
        value: 'foo',
        bar: {
          value: 'bar',
          baz: {
            value: 'baz',
          },
        },
      },
    };
    const sampleDataB = {
      foo: {
        bar: [{
          baz: {
            value: 'baz',
            biz: {
              value: 'biz',
            },
          }
        }, {
          buzz: {
            value: 'buzz',
            booz: {
              value: 'booz',
            },
          }
        }],
      },
    };
    
    console.log(
      "getValueByKeyPath(sampleDataA, 'foo.bar.baz.value') ...",
      getValueByKeyPath(sampleDataA, 'foo.bar.baz.value')
    );
    console.log(
      "getValueByKeyPath(sampleDataA, 'foo.bar[\"baz\"].value') ...",
      getValueByKeyPath(sampleDataA, 'foo.bar["baz"].value')
    );
    console.log(
      "getValueByKeyPath(sampleDataB, 'foo.bar[1][\"buzz\"].booz') ...",
      getValueByKeyPath(sampleDataB, 'foo.bar[1]["buzz"].booz')
    );
    console.log(
      "fail safe ... getValueByKeyPath(sampleDataB, 'foo.bar[2].buzz.booz') ...",
      getValueByKeyPath(sampleDataB, 'foo.bar[2].buzz.booz')
    );
    .as-console-wrapper { min-height: 100%!important; top: 0; }