नेस्टेड ऑब्जेक्ट की गतिशील रूप से सेट की गई संपत्ति


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'?
जोनाथन लोनोस्की


इस मदद को पॉसिबल कर सकता है: stackoverflow.com/questions/695050/…
रेने एम।

1
यदि आप set()विधि को हटाते हैं और बस वही obj.db.mongodb.user = 'root';करते हैं, जो आप चाहते हैं कि आप वास्तव में क्या चाहते हैं?
एडेनो

@JonathanLonowski लोनोव्स्की barद्वारा अधिलेखित हो जाता है Object। @adeneo और @rmertins वास्तव में :) लेकिन मुझे दुर्भाग्य से चारों ओर कुछ अन्य तर्क लपेटने होंगे। @ रॉबर्ट लेवी मैंने पाया कि एक और काम कर रहा है, लेकिन इसे स्थापित करना बहुत अधिक जटिल लगता है ...
जॉन बी।

जवाबों:


95

यह फ़ंक्शन, आपके द्वारा निर्दिष्ट तर्कों का उपयोग करके, 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 schemaएक पॉइंटर है जो पथ के साथ नीचे जाता है schema = schema[elem]। इसलिए लूप के बाद, schema[pList[len - 1]]mongo.db.user को इंगित करता है obj
वेबजय

कि मेरी समस्या धन्यवाद हल, MDN डॉक्स में यह नहीं मिल सका। लेकिन मुझे एक और संदेह है, अगर असाइनमेंट ऑपरेटर आंतरिक वस्तुओं का संदर्भ देता है तो ऑब्जेक्ट 1 से अलग ऑब्जेक्ट 2 कैसे बनाया जाए ताकि ऑब्जेक्ट 2 पर किए गए परिवर्तन ऑब्जेक्ट 1 पर प्रतिबिंबित न हों।
ओनिक्स

@Onix आप इसके लिए लॉश cloneDeepफंक्शन का उपयोग कर सकते हैं ।
आकाश ठाकुर

@Onix const क्लोन = JSON.parse (JSON.stringify (obj))
माइक मच

79

लोदश में _सेट () विधि है।

_.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: {target: 3}}}

यदि वे मौजूद नहीं हैं, तो सेट को पुन: फ़हराए जाने वाले ध्वज को सही पर सेट करना ऑब्जेक्ट को सेट करेगा।

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

इस में परिणाम होगा:

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

1
इस कोड का उपयोग, स्वच्छ और सरल। कंप्यूटिंग के बजाय levelमैंने इस्तेमाल कियाreduce तीसरे तर्क का ।
जुआन लानुस

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 परम की जांच करने की जरूरत है कि यह गलत नहीं है, यदि श्रृंखला के नीचे प्रॉप्स में से कोई मौजूद नहीं है, तो एक त्रुटि होगी।
सी। स्मिथ

2
आप सिर्फ पाथ का उपयोग कर सकते हैं। स्लाइस (1);
मार्कोस परेरा

1
उत्कृष्ट उत्तर, एक अच्छा और संक्षिप्त समाधान।
चिम

मेरा मानना ​​है कि यदि कथन है, तो यह होना चाहिए था obj[path[0]] = value;क्योंकि pathहमेशा प्रकार का होता है string[], तब भी जब केवल एक स्ट्रिंग बची हो।
वलोरड

जावास्क्रिप्ट वस्तुओं का उपयोग करके काम करना चाहिए obj[['a']] = 'new value'। कोड की जाँच करें: jsfiddle.net/upsdne03
हेमला विडाल

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');

मैंने इसे अद्यतन स्थिति पर प्रतिक्रिया के लिए उपयोग किया, इसने मेरे लिए बहुत अच्छा काम किया।


1
यह आसान था, मुझे इसे नेस्टेड गुणों के साथ काम करने के लिए प्रोपैथ पर एक स्ट्रॉन्ग () डालना था, लेकिन इसके बाद मैंने बहुत अच्छा काम किया। const [सिर, ... बाकी] = propPath.toString ()। विभाजन ('।');
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!"जहां propertyNameमें संपत्ति के नाम पर रखती है levelTwo


7

लॉडश में अपडेट नाम की एक विधि होती है जो ठीक उसी तरह की होती है जैसी आपको चाहिए।

यह विधि निम्नलिखित मापदंडों को प्राप्त करती है:

  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

मैंने सही उत्तर के आधार पर स्ट्रिंग द्वारा ओब्ज मान प्राप्त करने और प्राप्त करने के लिए जिस्ट बनाया । आप इसे डाउनलोड कर सकते हैं या इसे npm / यार्न पैकेज के रूप में उपयोग कर सकते हैं।

// 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"));

कैसे जटिल नेस्टेड वस्तुओं के लिए हल करने के लिए। `` `कास्ट x = {'एक': 1, 'दो': 2, 'तीन': {'एक': 1, 'दो': 2, 'तीन': [{'एक': 1}, { 'एक': 'एक'}, {'एक': 'मैं'}]}, 'चार': [0, 1, 2,}}; कंसोल.लॉग (np.get (x, 'three.three [0] .one')); `` `
सुमुखा एचएस

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 = {}, कुंजियाँ, मान) => {
blindChicken

1
हाँ अच्छा है। पीछे मुड़कर सीटू में भी कुंजी तर्क को नष्ट कर सकता है और कोड की एक और लाइन को बचा सकता है
हेनरी इंग-सिमंस

मूल रूप से एक लाइनर अब 👍
हेनरी इंग-सिमंस

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/sain/clain/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

मैंने इस सेट विधि को संक्षेप में लिखने की कोशिश की , यह किसी की मदद कर सकता है!

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);


-1

JQuery की एक विस्तार विधि है:

https://api.jquery.com/jquery.extend/

बस एक वस्तु के रूप में ओवरराइट को पास करें और यह दोनों को मर्ज कर देगा।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.