ネストされたオブジェクトのプロパティを動的に設定します


88

私は、任意の数のレベルの深さであり、既存のプロパティを持つことができるオブジェクトを持っています。例えば:

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 2013


このヘルプが可能である可能性があります:stackoverflow.com/questions/695050/…–
Rene M.

1
set()メソッドを削除して、obj.db.mongodb.user = 'root';あなたが望んでいるように見えるものを正確に持っているとしたら?
adeneo 2013

@JonathanLonowskiLonowskibarはによって上書きされますObject。@adeneoと@rmertins確かに:)しかし、残念ながら、他のロジックをラップする必要があります。@Robert Levy私はそれを見つけてアクセスを機能させましたが、設定は非常に複雑に思えます...
John B.

回答:


96

この関数は、指定した引数を使用して、objコンテナー内のデータを追加/更新する必要があります。objスキーマ内のどの要素がコンテナで、どの要素が値(文字列、intなど)であるかを追跡する必要があることに注意してください。そうしないと、例外がスローされ始めます。

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 2015年

3
@ sman591schemaは、。を使用してパスを下に移動するポインタschema = schema[elem]です。したがって、forループの後、のschema[pList[len - 1]]mongo.db.userを指しobjます。
webjay 2017年

それは私の問題のおかげで解決しました、MDNドキュメントでこれを見つけることができませんでした。しかし、別の疑問があります。代入演算子が内部オブジェクトへの参照を与える場合、object2で行われた変更がobject1に反映されないように、object1から別のobject2を作成する方法。
オニキス

@OnixcloneDeepこれにはlodash関数を利用できます。
AakashThakur20年

@Onix CONSTクローン= JSON.parse(JSON.stringify(OBJ))
マイクMakuch

79

Lodashには_.set()メソッドがあります。

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

1
キーの値を設定するためにも使用できますか?はいの場合、例を共有できますか。ありがとう
セージプーデル

これは素晴らしいことですが、パスをどのように追跡/決定しますか?
トム

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:{ターゲット:3}}}

set recursivelyフラグをtrueに設定すると、オブジェクトが存在しない場合はオブジェクトが設定されます。

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

結果は次のようになります。

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

1
このコードを使用して、クリーンでシンプルに。計算する代わりに、の3番目の引数levelを使用しましたreduce
JuanLanus19年

1
私はと考えてい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 paramをチェックして、falseでないことを確認する必要があります。チェーンの下の小道具のいずれかが存在しない場合、エラーがスローされます。
C・スミス

2
path.slice(1);を使用できます。
マルコスペレイラ

1
優れた答え、素晴らしく簡潔な解決策。
チム

ifステートメントは、文字列が1つしか残っていない場合でも、が常に型であるobj[path[0]] = value;ためであるはずだと思います。pathstring[]
Valorad

Javascriptオブジェクトはを使用して動作するはずobj[['a']] = 'new value'です。コードを確認:jsfiddle.net/upsdne03
HEMAヴィダル

12

目標を達成するために、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');

状態を更新するためのreactでよく使用しましたが、かなりうまく機能しました。


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

9

ES6はあまりにも使用してこれを行うにはかなりクールな方法があるコンピュープロパティ名残りのパラメータを

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!"whereを使用できます。propertyNamelevelTwo


8

@ 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' } }

7

Lodashには、必要なことを正確に実行するupdateというメソッドがあります。

このメソッドは、次のパラメーターを受け取ります。

  1. 更新するオブジェクト
  2. 更新するプロパティのパス(プロパティは深くネストできます)
  3. 更新する値を返す関数(元の値をパラメーターとして指定)

あなたの例では、次のようになります。

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

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')); `` `
SumukhaHS20年

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
いいね。振り返ってみると、キーの引数をその場
HenryIng-Simmons20年

基本的には1つのライナー今👍
ヘンリーING-シモンズ

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-inhttps://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.