3

I have a question about walk down an object dynamically by an given array object.

Tried with some static code but this is not flexible in a situation where there are more or less levels

// value = 10
// field = ["data", "input", "level", "0"]
item[field[0]][field[1]][field[2]][field[3]] = value

I have no clue where to start with a function doing this with a for loop. Can anybody give me some advice to get started.

0

6 Answers 6

2

You could reduce the fields and take an object and it's properties. At the end assign the value with the last key.

const
    setValue = (object, [...path], value) => {
        var last = path.pop();
        path.reduce((o, k) => o[k] = o[k] || {}, object)[last] = value;
    },
    object = {},
    value = 10,
    fields = ["data", "input", "level", "0"];
    
setValue(object, fields, value);

console.log(object);

Sign up to request clarification or add additional context in comments.

Comments

1

There's a built-in method on Lodash that does just this - _.set.

_.set(item, field, value)

let item = {
  data: {
    input: {
      level: [5]
    }
  }
};

const field = ["data", "input", "level", "0"];
const value = 10;

_.set(item, field, value);

console.log(item);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.js"></script>

1 Comment

No problem @Almer, always glad to help.
0

You can use a recursive function to navigate through your object :

var value = 10;
var field = ["data", "input", "level", "0"];

var obj =
{
  data: 
  {
      input:
      {
        level: [42]
      }
  }
};

function SetValue(field, obj, value, index = 0)
{
  var memberName = field[index];
  // not at last item ?
  if (index < field.length - 1)
  {
    
    if (obj.hasOwnProperty(memberName))
    {
      SetValue(field, obj[memberName], value, index + 1);
    }
  }
  else
  {
    obj[memberName] = value;
  }
}
console.log("Before");
console.log(obj);
console.log("---------------------");
SetValue(field, obj, value);
console.log("After");
console.log(obj);
console.log("---------------------");

Comments

0

I already use a function in production that literally does what you want:

/**
 * Replace an item in datasource with specified path with new value.  
 * Will _create_ an item if the path does not currently exist.
 * @param {object} o Datasource
 * @param {array} k Array of keys used to access item (usually getPath())
 * @param {*} v New value of specified item
 */
const replaceItem = (o, k, v) => k.reduce((r, e, i, a) => {
  if (!a[i + 1]) r[e] = v;
  else return r[e]
}, o)

const dataSource = {
  data: {
    input: {
      level: [1]
    }
  }
}

const path = ["data", "input", "level", "0"]
replaceItem(dataSource, path, 5)
console.log(dataSource)

Comments

0

another version, with a twist. It differentiates wether the object it creates should be an Object or an Array

const setValue = (object, path, value) => {
  if (!object || !path || !path.length) return;

  var key = path[0],
    i = 1,
    prev = key;

  while (i < path.length) {
    key = path[i++];
    object = object[prev] || (object[prev] = +key === (key >>> 0) ? [] : {});
    prev = key;
  }

  object[key] = value;
};

const object = {};
setValue(object, ["data", "input", "level", "0"], 10);
console.log(object);

Comments

0

You could keep a current variable which represents the current object you're on in your object and then set the value at the end once your loop is complete:

const item = {
  "data": {
    "input": {
      "level": {
        0: -1
      }
    }
  }
}

let value = 10;
let field = ["data", "input", "level", "0"];
let current = item[field[0]];
for (let i = 1; i < field.length - 1; i++) {
  current = current[field[i]];
}
current[field.pop()] = value;
console.log(item);

The above can also be achieved recursively (use nf === undefined instead of !nf if falsy field values exist):

const item = {
  "data": {
    "input": {
      "level": {
        0: -1
      }
    }
  }
}


let value = 10;
let field = ["data", "input", "level", "0"];

const set_val = (val, obj, [f, nf, ...rest]) =>
	!nf ? obj[f] = val : set_val(val, obj[f], [nf, ...rest]);

set_val(value, item, field);
console.log(item);

If you wish to build the item object from scratch, you could also do something like so:

const value = 10;
const field = ["data", "input", "level", "0"];
let item = {};
let curr = item;
for (let i = 0; i < field.length - 1; i++) {
  curr[field[i]] = {}; 
  curr = curr[field[i]];
}

curr[field.pop()] = value;
console.log(item);

This build can also be done recursively like so:

const value = 10;
const field = ["data", "input", "level", "0"];
let item = {};
let curr = item;
const set_val = (val, [f, nf, ...rest], item = {}) => {
  if (!nf) {
    item[f] = val;
    return item;
  } else {
    item[f] = set_val(val, [nf, ...rest], item[f]);
    return item;
  }
}

console.log(set_val(value, field));

2 Comments

why converting a string to a number? even with zero, this does not work ...
@NinaScholz good point, a previous comment on my post made me think I had to convert it to a number, but now I realize that it isn't needed

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.