Skip to content

Instantly share code, notes, and snippets.

@fawwaz
Last active January 26, 2024 14:53
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save fawwaz/b037a105e41fa8ed7292b324abb07f42 to your computer and use it in GitHub Desktop.
Save fawwaz/b037a105e41fa8ed7292b324abb07f42 to your computer and use it in GitHub Desktop.
How to create a dynamic nested object from array of properties

Background

Sometimes you want to describe how to access specific value in a nested object with an array of string consisiting a path to that specific object. So for example, if you have some js object like this :

const sampleObject = {
  a:{
    deep: {
      javascript: {
        object: 'I want this value'
      }
    }
  }
}

You may describe I want this value with an array of "json object path" like this :

const pathToTheValue = ["a", "deep", "javascript", "object"]

Challenge 1 : Reading the value

It is very clear, and you can write an read function which is used to retrieve the value in the path ( I want this value) like this :

const read = (obj, path) => {
  let o = obj;
  for(p of path) {
    o = o[p];
  }
  return o;
}
const pathToTheValue = ["a", "deep", "javascript", "object"]
read(sampleObject, pathToValue); 
// you will get 'I want this value'

Challenge 2 : Writing the value

Now, comes a much more harder part. How do we write the value of the path?
If the object we want to write is purely a nested object, you may be able to use several approaches in these stack overflow answers like this :

const write = (object, path, value) => {
  return path.reduceRight((obj, next, idx, fullPath) => {
    if(idx+1 === fullPath.length) {
      return {[next]: value}
    } else  {
      return {[next]: obj}
    }
  }, object);
}

const pathToTheValue = ["a", "deep", "javascript", "object"]
write({}, pathToValue, 'I want to write this value');

However Some library like soya-form keeps their internal data in non-pure nested object . It may have an array inside the nested object. This is an example on how soya-form keep their object :

const exampleNonPureNestedObject = {
  anObject:{
    whichHas:{
      someArray: [
        'singularValue1',
        'singularValue2',
      ]
    }
    andAlso:{
      someArrayOfObject: [
        {likeThis: 'value1 I want to Write'},
        {likeThis: 'value2'}
      ]
    }
  }
}

Now you can't use those stack overflow answers anymore.

Solution

I write a recursive function which is adapted from this stackoverflow answer such we can write a dynamic nonPure Nested Object :

const write = (obj, keys, v) => {
  if (keys.length === 0) {
    return v;
  }
  if (keys.length === 1) {
    obj[keys[0]] = v;
  } else {
    const [key, ...remainingKeys] = keys;
    const nextKey = remainingKeys[0];
    const nextRemainingKeys = remainingKeys.slice(1);

    if (typeof nextKey === "number") {
      // create array
      if (!obj[key]) {
        obj[key] = [];
      }

      // Fill empty index with empty object
      if (obj[key].length < nextKey + 1) {
        delta = nextKey + 1 - obj[key].length;
        for (let i = 0; i < delta; i++) {
          obj[key].push({});
        }
      }

      // recursively write the object
      obj[key][nextKey] = write(obj[key][nextKey], nextRemainingKeys, v);
    } else {
      // recursively write the object
      obj[key] = write(
        typeof obj[key] === "undefined" ? {} : obj[key],
        remainingKeys,
        v
      );
    }
  }

  return obj;
};

Now if you want to write value 1 I want to write into exampleNonPureNestedObject You can simply use it like this:

const pathToValue = ['anObject','andAlso','someArrayOfObject',0,'likeThis'] // note the integer index 
write({}, pathToValue, 'value 1 I want to write')

Fin

The write function can be extended into composableFunction.

const composableWrite = (path, value) => (object) => write(object, path, value);

Now you can chain the write function using compose function as seen in redux.

// before :
const objectToModify = {
  existingKey: 'value'
}

const output = compose(
  composableWrite(['anotherKey',0],'value 1'),
  composableWrite(['anotherKey',1],'value 2'),
  composableWrite(['arrayOfObject',0,'a'],'randomValue A'),
  composableWrite(['arrayOfObject',1,'b'],'another value')
)(objectToModify)

console.log(output)
/**
output : 
{
  existingKey: 'value',
  anotherKey:[
    'value 1',
    'value 2'
  ],
  arrayOfObject: [
    {a: 'randomValue A'},
    {b: 'another  value'}
  ]
}
**/
@fawwaz
Copy link
Author

fawwaz commented Aug 15, 2018

TODO : Translate object to path.

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