2

I have a bunch of JSON with multiple objects, arrays, strings, booleans, numbers etc. which are stored in one object at the root level and component.

Here is a sample:

{
  "theme": {
    "auto": {
      "sensor": "sensor.sn1_ldr",
      "below": 600
    },
    "ui": {
      "cards": {
        "round": false,
        "elevation": 1
      }
    },
    ...
  },
  ...
}

I have managed to pass back the path and new value of the item in an array like so:

["theme", "auto", "sensor"]

How do I from there set the new value of that path? ie. the equivalent of:

config.theme.auto.sensor = newValue;

but using the path array passed back?

Method I have so far:

handleConfigChange = (path, value) => {
  console.log(path, value);
  let config = this.state.config;
  // Set the new value

  this.setState({ config });
};
Timmo
  • 2,266
  • 4
  • 34
  • 54
  • 1
    At least very very related: https://stackoverflow.com/questions/6491463/accessing-nested-javascript-objects-with-string-key The function in the accepted answer starts out by converting a given string into an array just like yours. – T.J. Crowder Sep 22 '18 at 15:42
  • 1
    you should defiantly read [this](https://medium.com/javascript-inside/safely-accessing-deeply-nested-values-in-javascript-99bf72a0855a) – Sagiv b.g Sep 22 '18 at 15:45
  • is the number of elements in array is always same ? – aravind_reddy Sep 22 '18 at 15:49

4 Answers4

3

You could store the last key and reduce the object by taking the keys from the path.

function setValue(object, path, value) {
    var last = path.pop();
    path.reduce((o, k) => o[k] = o[k] || {}, object)[last] = value;
}

var config = { theme: { auto: { sensor: "sensor.sn1_ldr", below: 600 }, ui: { cards: { round: false, elevation: 1 } } } },
    path = ["theme", "auto", "sensor"];

setValue(config, path, 'foo');

console.log(config);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Nina Scholz
  • 376,160
  • 25
  • 347
  • 392
  • Thanks for that! I'll have to do a bit of research into how on earth it works but it solves my problem – Timmo Sep 22 '18 at 15:55
2

Below is a simple version of Nina's answer (the one using reduce).

Hope this helps you get what's going on behind that.

const setValueByPathArray = (object, path, value) => {
  let i = 0
  let reference = object
  while (i < path.length - 1) {
    const currentPath = path[i]
    reference = reference[currentPath]
    i += 1
  }
  const lastPath = path[path.length - 1]
  reference[lastPath] = value
  return object
}

const config = { theme: { auto: { sensor: "sensor.sn1_ldr", below: 600 }, ui: { cards: { round: false, elevation: 1 } } } }
const path = ["theme", "auto", "sensor"];

setValueByPathArray(config, path, 'foo')
Muhammet Enginar
  • 508
  • 6
  • 14
0

You can iterate over the path, keying into each level of the object until you're at the location you'd like to modify. I'm assuming valid path here, but you can raise an error if root becomes falsey (or a non-object) in the loop:

const tree = {
  "theme": {
    "auto": {
      "sensor": "sensor.sn1_ldr",
      "below": 600
    },
    "ui": {
      "cards": {
        "round": false,
        "elevation": 1
      }
    },
  },
};

const path = ["theme", "auto", "sensor"];
const newValue = "Hello World!";
let root = tree;

for (let i = 0; i < path.length - 1; i++) {
  root = root[path[i]];
}

root[path.at(-1)] = newValue;

console.log(tree);

path.slice(0, -1).forEach also works if the traditional for loop makes you uneasy.

ggorlen
  • 44,755
  • 7
  • 76
  • 106
0
  1. Use Object.key to get the key Array:

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys

  1. Use recursion to loop through.

    const object1 = {
      "theme": {
        "auto": {
          "sensor": "sensor.sn1_ldr",
          "below": 600
        },
        "ui": {
          "cards": {
            "round": false,
            "elevation": 1
          }
        },
    
      },  
    }
    
    var result = [];
    see(object1);
    console.log(result);
    
    function see (obj){
        var k = Object.keys(obj)
        for (var i = 0; i < k.length; i++){
            result.push(k[i]);
            if (obj[k[i]] instanceof Object){
                see(obj[k[i]]);
            }
        }
    }
    
S.S. Anne
  • 15,171
  • 8
  • 38
  • 76
Terry D
  • 349
  • 3
  • 15