Search code examples
javascriptdata-structuresnestedjavascript-objects

Nesting dot notation within bracket notation to create nested objects


"Write a function arrayToList that builds up a list structure like"

let LL = { data: 1, next: { data: 2, next: { data: 3, next: null }}};

I understand the typical solution to this problem, where the list must be built from the inside out:

function arrToLList(arr) {
  let LList = null;
  for (let i = arr.length - 1; i >= 0; i--) {
    LList = { data: arr[i], next: LList };
  }
  return LList;
}

But my initial solution was to brute force it with a typical for loop.

function arrayToLList(arr) {
  let d = "data";
  let n = "next";

  let LList = nextNode();

  for (let i = 0; i < arr.length; i++) {
    LList[d] = arr[i];
    d = "next." + d;
    LList[n] = nextNode();
    n = "next." + n;
  }

  function nextNode() {
    return {
      data: null,
      next: null
    };
  }

  return LList;
}

Solution

  • What you want to achieve is possible, but you need to customize the functionality of how getting a property works when you use bracket notation. As you mentioned, using dot notation with bracket notation won't work, you need a way to define this logic yourself. ES6 introduced Proxies which allows you to specify a set method trap for your object. Whenever you set a value on the object, the set method will be called. Using this idea, you can split the dot-notation string by . and traverse the path it returns to get your nested object. Once you have retrieved the nested object, you can set its value.

    See example below:

    function arrayToLList(arr) {
      let d = "data";
      let n = "next";
    
      let LList = nextNode();
    
      for (let i = 0; i < arr.length; i++) {
        LList[d] = arr[i];
        d = "next." + d;
        
        if(i < arr.length-1) // don't add null object to last node
          LList[n] = nextNode();
        n = "next." + n;
      }
    
      function nextNode() {
        const obj = {
          data: null,
          next: null
        };
        
        return new Proxy(obj, {
          set: function(obj, key, val) {
            const path = key.split('.');
            const last = path.pop();
            for(const prop of path) 
              obj = obj[prop];
           
            obj[last] = val;
            return true;
          }
        });
      }
    
      return LList;
    }
    
    console.log(arrayToLList([1, 2, 3]));

    However, you don't need to use a proxy. A more straightforward way of doing this would be by creating a method such as setValueByPath(val, obj, strPath) which performs the logic in the proxy for you. Then, instead of setting your object using bracket notation, you simply call the setValueByPath(obj, strPath):

    function setValudByPath(val, obj, strPath) { // pefroms same logic from proxy, just using reduce instead
      const path = strPath.split('.');
      const last = path.pop();
      path.reduce((nested, p) => nested[p], obj)[last] = val;
    }
    
    function arrayToLList(arr) {
      let d = "data";
      let n = "next";
    
      let LList = {data: null, next: null};
    
      for (let i = 0; i < arr.length; i++) {
        setValudByPath(arr[i], LList, d); // same as LList[d] = arr[i];
        d = "next." + d;
        
        if(i < arr.length-1) // don't add null object to last node
          setValudByPath({data: null, next: null}, LList, n); // same as: LList[n] = nextNode();
        n = "next." + n;
      }
    
      return LList;
    }
    
    console.log(arrayToLList([1, 2, 3]));