중첩 된 개체의 동적 설정 속성


84

레벨 수에 관계없이 기존 속성을 가질 수있는 객체가 있습니다. 예를 들면 :

var obj = {
    db: {
        mongodb: {
            host: 'localhost'
        }
    }
};

이에 대해 다음과 같이 속성을 설정 (또는 덮어 쓰기)하고 싶습니다.

set('db.mongodb.user', 'root');
// or:
set('foo.bar', 'baz');

속성 문자열은 깊이를 가질 수 있고 값은 모든 유형 / 사물 일 수 있습니다.
속성 키가 이미 존재하는 경우 값으로서의 객체 및 배열을 병합 할 필요가 없습니다.

이전 예제는 다음 객체를 생성합니다.

var obj = {
    db: {
        mongodb: {
            host: 'localhost',
            user: 'root'
        }
    },
    foo: {
        bar: baz
    }
};

그러한 기능을 어떻게 실현할 수 있습니까?


에 대한 결과 무엇을해야 set('foo', 'bar'); set('foo.baz', 'qux');, foo먼저 보유 String후가된다 Object? 어떻게 'bar'되나요?
Jonathan Lonowski


이 도움말을 추적 할 수없는 가망 수 : stackoverflow.com/questions/695050/...
르네 M.

1
set()방법 을 제거하고 obj.db.mongodb.user = 'root';원하는 것을 정확히 가지고 있습니까?
adeneo

@JonathanLonowski Lonowski barObject. @adeneo와 @rmertins Indeed :)하지만 불행히도 다른 로직을 감쌀 필요가 있습니다. @Robert 레비 나는 ... 하나를 발견하고 액세스하는 작업을 가지고 있지만 그것을 설정하는 것은 훨씬 더 복잡한 것 같다
존 B.에게

답변:


92

이 함수는 지정한 인수를 사용하여 obj컨테이너 의 데이터를 추가 / 업데이트해야합니다 . obj스키마 의 어떤 요소 가 컨테이너이고 값 (문자열, 정수 등)인지 추적해야합니다 . 그렇지 않으면 예외가 발생하기 시작합니다.

obj = {};  // global object

function set(path, value) {
    var schema = obj;  // a moving reference to internal objects within obj
    var pList = path.split('.');
    var len = pList.length;
    for(var i = 0; i < len-1; i++) {
        var elem = pList[i];
        if( !schema[elem] ) schema[elem] = {}
        schema = schema[elem];
    }

    schema[pList[len-1]] = value;
}

set('mongo.db.user', 'root');

2
@ bpmason1 왜 어디서나 var schema = obj대신 사용했는지 설명해 주 obj시겠습니까?
sman591

3
@ sman591 schemaschema = schema[elem]. 따라서 for 루프 후에 schema[pList[len - 1]].NET에서 mongo.db.user를 가리 킵니다 obj.
webjay

내 문제를 해결 한 덕분에 MDN 문서에서 이것을 찾을 수 없습니다. 그러나 할당 연산자가 내부 개체에 대한 참조를 제공하면 object2에서 수행 된 변경 사항이 object1에 반영되지 않도록 object1에서 별도의 object2를 만드는 방법에 대한 의문이 있습니다.
Onix

@Onix cloneDeep이것을 위해 lodash 기능을 사용할 수 있습니다 .
Aakash Thakur

@Onix const clone = JSON.parse (JSON.stringify (obj))
Mike Makuch

72

Lodash에는 _.set () 메서드가 있습니다.

_.set(obj, 'db.mongodb.user', 'root');
_.set(obj, 'foo.bar', 'baz');

1
키 값을 설정하는데도 사용할 수 있습니까? 그렇다면 예를 공유 할 수 있습니다. 주셔서 감사합니다
세이지 poudel

이것은 훌륭하지만 경로를 추적 / 결정하는 방법은 무엇입니까?

19

조금 늦었지만 여기에 도서관이 아닌 간단한 대답이 있습니다.

/**
 * Dynamically sets a deeply nested value in an object.
 * Optionally "bores" a path to it if its undefined.
 * @function
 * @param {!object} obj  - The object which contains the value you want to change/set.
 * @param {!array} path  - The array representation of path to the value you want to change/set.
 * @param {!mixed} value - The value you want to set it to.
 * @param {boolean} setrecursively - If true, will set value of non-existing path as well.
 */
function setDeep(obj, path, value, setrecursively = false) {
    path.reduce((a, b, level) => {
        if (setrecursively && typeof a[b] === "undefined" && level !== path.length){
            a[b] = {};
            return a[b];
        }

        if (level === path.length){
            a[b] = value;
            return value;
        } 
        return a[b];
    }, obj);
}

제가 만든이 기능은 당신이 필요로하는 것과 조금 더 정확하게 할 수 있습니다.

이 객체에 깊이 중첩 된 대상 값을 변경하고 싶다고 가정 해 보겠습니다.

let myObj = {
    level1: {
        level2: {
           target: 1
       }
    }
}

따라서 함수를 다음과 같이 호출합니다.

setDeep(myObj, ["level1", "level2", "target1"], 3);

결과 :

myObj = {level1 : {level2 : {target : 3}}}

재귀 적으로 설정 플래그를 true로 설정하면 객체가 존재하지 않는 경우 설정됩니다.

setDeep(myObj, ["new", "path", "target"], 3, true);

결과는 다음과 같습니다.

obj = myObj = {
    new: {
         path: {
             target: 3
         }
    },
    level1: {
        level2: {
           target: 3
       }
    }
}

1
이 코드는 깨끗하고 간단합니다. 대신 컴퓨팅의 level내가 사용하는 reduce'세 번째 인수이야.
Juan Lanus

level+1 또는 path.length-1 이어야 한다고 생각합니다
ThomasReggi

12

재귀 함수를 사용할 수 있습니다.

/**
 * Sets a value of nested key string descriptor inside a Object.
 * It changes the passed object.
 * Ex:
 *    let obj = {a: {b:{c:'initial'}}}
 *    setNestedKey(obj, ['a', 'b', 'c'], 'changed-value')
 *    assert(obj === {a: {b:{c:'changed-value'}}})
 *
 * @param {[Object]} obj   Object to set the nested key
 * @param {[Array]} path  An array to describe the path(Ex: ['a', 'b', 'c'])
 * @param {[Object]} value Any value
 */
export const setNestedKey = (obj, path, value) => {
  if (path.length === 1) {
    obj[path] = value
    return
  }
  return setNestedKey(obj[path[0]], path.slice(1), value)
}

더 간단합니다!


3
좋아 보인다! obj 매개 변수를 확인하여 거짓이 아닌지 확인하면 체인 아래의 소품이 존재하지 않으면 오류가 발생합니다.
C Smith

2
path.slice (1);
Marcos Pereira

1
훌륭한 대답, 훌륭하고 간결한 솔루션.
chim

if 문 은 문자열이 1 개 밖에 남지 않은 경우에도 항상 유형 obj[path[0]] = value;이기 때문에 그랬어 야한다고 생각 합니다. pathstring[]
Valorad

자바 스크립트 객체는 obj[['a']] = 'new value'. 코드 확인 : jsfiddle.net/upsdne03
Hemã Vidal

11

목표를 달성하기 위해 ES6 + 재귀를 사용하여 작은 함수를 작성했습니다.

updateObjProp = (obj, value, propPath) => {
    const [head, ...rest] = propPath.split('.');

    !rest.length
        ? obj[head] = value
        : this.updateObjProp(obj[head], value, rest.join('.'));
}

const user = {profile: {name: 'foo'}};
updateObjProp(user, 'fooChanged', 'profile.name');

나는 상태를 업데이트하기 위해 반응에 많이 사용했으며 나에게 꽤 잘 작동했습니다.


1
이것은 편리했고 중첩 된 속성으로 작동하도록 proPath에 toString ()을 넣어야했지만 그 후에는 훌륭하게 작동했습니다. const [head, ... rest] = propPath.toString (). split ( '.');
WizardsOfWor

1
@ user738048 @ 브루노 - 아킴 라인이 this.updateStateProp(obj[head], value, rest);있어야한다this.updateStateProp(obj[head], value, rest.join());
ma.mehralian

8

ES6에는 Computed Property NameRest Parameter를 사용하여이 작업을 수행하는 매우 멋진 방법이 있습니다 .

const obj = {
  levelOne: {
    levelTwo: {
      levelThree: "Set this one!"
    }
  }
}

const updatedObj = {
  ...obj,
  levelOne: {
    ...obj.levelOne,
    levelTwo: {
      ...obj.levelOne.levelTwo,
      levelThree: "I am now updated!"
    }
  }
}

에서 levelThree속성을 설정하는 동적 속성 인 경우 에서 속성 의 이름을 보유하는 위치 levelTwo를 사용할 수 있습니다 .[propertyName]: "I am now updated!"propertyNamelevelTwo


7

Lodash에는 필요한 작업을 정확히 수행하는 update 라는 메서드 가 있습니다.

이 메소드는 다음 매개 변수를받습니다.

  1. 업데이트 할 개체
  2. 업데이트 할 속성의 경로 (속성은 깊게 중첩 될 수 있음)
  3. 업데이트 할 값을 반환하는 함수 (원래 값을 매개 변수로 제공)

귀하의 예에서는 다음과 같습니다.

_.update(obj, 'db.mongodb.user', function(originalValue) {
  return 'root'
})

7

@ bpmason1의 답변에서 영감을 얻었습니다.

function leaf(obj, path, value) {
  const pList = path.split('.');
  const key = pList.pop();
  const pointer = pList.reduce((accumulator, currentValue) => {
    if (accumulator[currentValue] === undefined) accumulator[currentValue] = {};
    return accumulator[currentValue];
  }, obj);
  pointer[key] = value;
  return obj;
}

예:

const obj = {
  boats: {
    m1: 'lady blue'
  }
};
leaf(obj, 'boats.m1', 'lady blue II');
leaf(obj, 'boats.m2', 'lady bird');
console.log(obj); // { boats: { m1: 'lady blue II', m2: 'lady bird' } }

2

정답을 기반으로 문자열별로 obj 값을 설정하고 얻는 요점 을 만들었습니다 . 다운로드하거나 npm / yarn 패키지로 사용할 수 있습니다.

// yarn add gist:5ceba1081bbf0162b98860b34a511a92
// npm install gist:5ceba1081bbf0162b98860b34a511a92
export const DeepObject = {
  set: setDeep,
  get: getDeep
};

// https://stackoverflow.com/a/6491621
function getDeep(obj: Object, path: string) {
  path = path.replace(/\[(\w+)\]/g, '.$1'); // convert indexes to properties
  path = path.replace(/^\./, '');           // strip a leading dot
  const a = path.split('.');
  for (let i = 0, l = a.length; i < l; ++i) {
    const n = a[i];
    if (n in obj) {
      obj = obj[n];
    } else {
      return;
    }
  }

  return obj;
}

// https://stackoverflow.com/a/18937118
function setDeep(obj: Object, path: string, value: any) {
  let schema = obj;  // a moving reference to internal objects within obj
  const pList = path.split('.');
  const len = pList.length;
  for (let i = 0; i < len - 1; i++) {
    const elem = pList[i];
    if (!schema[elem]) {
      schema[elem] = {};
    }
    schema = schema[elem];
  }

  schema[pList[len - 1]] = value;
}

// Usage
// import {DeepObject} from 'somePath'
//
// const obj = {
//   a: 4,
//   b: {
//     c: {
//       d: 2
//     }
//   }
// };
//
// DeepObject.set(obj, 'b.c.d', 10); // sets obj.b.c.d to 10
// console.log(DeepObject.get(obj, 'b.c.d')); // returns 10

1

더 깊은 중첩 된 개체 만 변경해야하는 경우 다른 방법은 개체를 참조하는 것입니다. JS 객체는 해당 참조로 처리되므로 문자열 키 액세스 권한이있는 객체에 대한 참조를 만들 수 있습니다.

예:

// The object we want to modify:
var obj = {
    db: {
        mongodb: {
            host: 'localhost',
            user: 'root'
        }
    },
    foo: {
        bar: baz
    }
};

var key1 = 'mongodb';
var key2 = 'host';

var myRef = obj.db[key1]; //this creates a reference to obj.db['mongodb']

myRef[key2] = 'my new string';

// The object now looks like:
var obj = {
    db: {
        mongodb: {
            host: 'my new string',
            user: 'root'
        }
    },
    foo: {
        bar: baz
    }
};

1

또 다른 접근법은 재귀를 사용하여 객체를 파헤치는 것입니다.

(function(root){

  function NestedSetterAndGetter(){
    function setValueByArray(obj, parts, value){

      if(!parts){
        throw 'No parts array passed in';
      }

      if(parts.length === 0){
        throw 'parts should never have a length of 0';
      }

      if(parts.length === 1){
        obj[parts[0]] = value;
      } else {
        var next = parts.shift();

        if(!obj[next]){
          obj[next] = {};
        }
        setValueByArray(obj[next], parts, value);
      }
    }

    function getValueByArray(obj, parts, value){

      if(!parts) {
        return null;
      }

      if(parts.length === 1){
        return obj[parts[0]];
      } else {
        var next = parts.shift();

        if(!obj[next]){
          return null;
        }
        return getValueByArray(obj[next], parts, value);
      }
    }

    this.set = function(obj, path, value) {
      setValueByArray(obj, path.split('.'), value);
    };

    this.get = function(obj, path){
      return getValueByArray(obj, path.split('.'));
    };

  }
  root.NestedSetterAndGetter = NestedSetterAndGetter;

})(this);

var setter = new this.NestedSetterAndGetter();

var o = {};
setter.set(o, 'a.b.c', 'apple');
console.log(o); //=> { a: { b: { c: 'apple'}}}

var z = { a: { b: { c: { d: 'test' } } } };
setter.set(z, 'a.b.c', {dd: 'zzz'}); 

console.log(JSON.stringify(z)); //=> {"a":{"b":{"c":{"dd":"zzz"}}}}
console.log(JSON.stringify(setter.get(z, 'a.b.c'))); //=> {"dd":"zzz"}
console.log(JSON.stringify(setter.get(z, 'a.b'))); //=> {"c":{"dd":"zzz"}}

1

나는 똑같은 것을 달성해야했지만 Node.js에서 ... 그래서이 멋진 모듈을 찾았습니다 : https://www.npmjs.com/package/nested-property

예:

var mod = require("nested-property");
var obj = {
  a: {
    b: {
      c: {
        d: 5
      }
    }
  }
};
console.log(mod.get(obj, "a.b.c.d"));
mod.set(obj, "a.b.c.d", 6);
console.log(mod.get(obj, "a.b.c.d"));

복잡한 중첩 객체를 해결하는 방법. ```const x = { 'one': 1, 'two': 2, 'three': { 'one': 1, 'two': 2, 'three': [{ 'one': 1}, { 'one': 'ONE'}, { 'one': 'I'}]}, 'four': [0, 1, 2]}; console.log (np.get (x, 'three.three [0] .one')); ```
Sumukha HS

1

순수한 es6과 원래 객체를 변경하지 않는 재귀를 사용하여 내 자신의 솔루션을 생각해 냈습니다.

const setNestedProp = (obj = {}, [first, ...rest] , value) => ({
  ...obj,
  [first]: rest.length
    ? setNestedProp(obj[first], rest, value)
    : value
});

const result = setNestedProp({}, ["first", "second", "a"], 
"foo");
const result2 = setNestedProp(result, ["first", "second", "b"], "bar");

console.log(result);
console.log(result2);


기본값 setNestedProp = (obj = {}, keys, value) => {
blindChicken

1
네 좋아요. 또한 현장에서 키 인수를 destructure 코드의 또 다른 라인을 절약 할 수 찾고 다시
헨리 잉 - 시몬스을

기본적으로 지금은 하나의 라이너 👍
Henry Ing-Simmons

0

이전 속성이 필요한 함수가 존재하기를 원한다면 이와 같은 것을 사용할 수 있으며 중첩 속성을 찾고 설정할 수 있는지 여부를 나타내는 플래그도 반환합니다.

function set(obj, path, value) {
    var parts = (path || '').split('.');
    // using 'every' so we can return a flag stating whether we managed to set the value.
    return parts.every((p, i) => {
        if (!obj) return false; // cancel early as we havent found a nested prop.
        if (i === parts.length - 1){ // we're at the final part of the path.
            obj[parts[i]] = value;          
        }else{
            obj = obj[parts[i]]; // overwrite the functions reference of the object with the nested one.            
        }   
        return true;        
    });
}

0

ClojureScript assoc-in( https://github.com/clojure/clojurescript/blob/master/src/main/cljs/cljs/core.cljs#L5280 )에서 영감을 받아 재귀를 사용합니다.

/**
 * Associate value (v) in object/array (m) at key/index (k).
 * If m is falsy, use new object.
 * Returns the updated object/array.
 */
function assoc(m, k, v) {
    m = (m || {});
    m[k] = v;
    return m;
}

/**
 * Associate value (v) in nested object/array (m) using sequence of keys (ks)
 * to identify the path to the nested key/index.
 * If one of the values in the nested object/array doesn't exist, it adds
 * a new object.
 */
function assoc_in(m={}, [k, ...ks], v) {
    return ks.length ? assoc(m, k, assoc_in(m[k], ks, v)) : assoc(m, k, v);
}

/**
 * Associate value (v) in nested object/array (m) using key string notation (s)
 * (e.g. "k1.k2").
 */
function set(m, s, v) {
    ks = s.split(".");
    return assoc_in(m, ks, v);
}

노트 :

제공된 구현으로

assoc_in({"a": 1}, ["a", "b"], 2) 

보고

{"a": 1}

이 경우 오류가 발생하는 것을 선호합니다. 원하는 경우 체크인을 추가 하여 객체 또는 배열인지 assoc확인 m하고 그렇지 않으면 오류를 발생시킬 수 있습니다.


0

set 메서드를 짧게 작성하려고했는데 누군가에게 도움이 될 수 있습니다!

function set(obj, key, value) {
 let keys = key.split('.');
 if(keys.length<2){ obj[key] = value; return obj; }

 let lastKey = keys.pop();

 let fun = `obj.${keys.join('.')} = {${lastKey}: '${value}'};`;
 return new Function(fun)();
}

var obj = {
"hello": {
    "world": "test"
}
};

set(obj, "hello.world", 'test updated'); 
console.log(obj);

set(obj, "hello.world.again", 'hello again'); 
console.log(obj);

set(obj, "hello.world.again.onece_again", 'hello once again');
console.log(obj);


당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.