问题
I have
data =
{
'first': {
'number': 1,
'text': 'Ya.'
},
'second': {
'number': 10,
'text': 'Da.'
}
};
And I really want to access it like:
number = data['first.number'];
Actually in a more flexible way, like:
numberOrText = data[memberName+'.'+propertyName];
Is there any lightweight library, or snippet you can suggest? This is - https://github.com/martinvl/KVCObject - so cool, but a bit overhead for this.
回答1:
You can easily resolve keypath with reduce function, without using any library.
First, we are creating an example object, called target, with some nested objects inside :
const target = {
foo: {
bar: {
example: 65
}
}
};
Then, we define a variable keypath containing keypath string : we want to access example property inside our target object.
const keypath = 'foo.bar.example';
The hard work begins today ! Keypath is splitted by dot separator and we obtain a keys array. We iterate over this array (with reduce function) and for each iteration, we return a new object.
const result = keypath.split('.').reduce((previous, current) => {
return previous[current];
}, target);
Finally, result variable value is 65. It works !
回答2:
if you have all dot-based paths (no array syntax), you can use eval or a simple sliding recursive function:
var data = {
'first': {
'number': 1,
'text': 'Ya.'
},
'second': {
'number': 10,
'text': 'Da.'
}
};
// the simple but discouraged way using eval:
alert(
eval(
"data.second.text"
)
); //shows "Da."
// a non-eval looping solution take s bit more code, but can be faster to execute:
function resolve(obj, path){
var r=path.split(".");
if(path){return resolve(obj[r.shift()], r.join("."));}
return obj
}
alert(
resolve(data, "first.text")
); //shows: "Ya."
回答3:
I think You may like underscore-keypath.
var foo = {
bar : {
name : "Cool!"
},
scores : [55, 27, 100, 33]
};
_(foo).valueForKeyPath("bar.name"); // --> "Cool!"
_(foo).setValueForKeyPath("bar.name", "BAR"); // --> sets foo.bar.name as "BAR"
_(foo).valueForKeyPath("scores.@max"); // --> 100
回答4:
Based on @dandavis pretty simple suggestions, I can set up accessors as prototype properties.
No eval
, also leave Object.prototype
untouched in terms of enumerating using Object.defineProperty
.
The solution actually goes like this:
function stringContains(string, value)
{ return string.indexOf(value) != -1; }
Object.defineProperty(Object.prototype, "setValueForKey", { value: function(value, key)
{ this[key] = value; }});
Object.defineProperty(Object.prototype, "setValueForKeyPath", { value: function(value, keyPath)
{
if (keyPath == null) return;
if (stringContains(keyPath, '.') == false) { this.setValueForKey(value, keyPath); return; }
var chain = keyPath.split('.');
var firstKey = chain.shift();
var shiftedKeyPath = chain.join('.');
this[firstKey].setValueForKeyPath(value, shiftedKeyPath);
}});
Object.defineProperty(Object.prototype, "getValueForKey", { value: function(key)
{ return this[key]; }});
Object.defineProperty(Object.prototype, "getValueForKeyPath", { value: function(keyPath)
{
if (keyPath == null) return;
if (stringContains(keyPath, '.') == false) { return this.getValueForKey(keyPath); }
var chain = keyPath.split('.');
var firstKey = chain.shift();
var shiftedKeyPath = chain.join('.');
return this[firstKey].getValueForKeyPath(shiftedKeyPath);
}});
Test are fine:
data = {
'name' : 'data',
'first': {
'number': 1,
'text': 'Ya.',
'meta' : {
'lang' : 'en'
}
},
'second': {
'number': 10,
'text': 'Ba.',
'meta' : {
'lang' : 'en'
}
},
'third': {
'number': 100,
'text': 'Da.',
'meta' : {
'lang' : 'hu'
}
}
};
data.setValueForKey('chunk', 'name');
data.setValueForKeyPath('blob', 'name');
var thirdLanguage = data.getValueForKeyPath('third.meta.lang');
data.setValueForKeyPath(thirdLanguage, 'first.meta.lang');
data.setValueForKeyPath(thirdLanguage, 'second.meta.lang');
log(data);
Output is the same with hu
as language in every data member.
回答5:
Make a helper function that reads a variable number of arguments or an array of parameters.
Object.prototype.$ = function() {
var result = this;
var list;
/*
Array .$(["first", "text"])
String .$("second.number")
String Parameters .$("first", "text")
*/
if(arguments.length == 1 && Object.prototype.toString.call(arguments[0]) === "[object Array]")
list = arguments[0];
else if(arguments.length == 1 && typeof(arguments[0]) == 'string' && arguments[0].indexOf(".") >= 0)
list = arguments[0].split(".");
else
list = arguments;
for(var i=0; i<list.length; i++)
result = result[list[i]];
return result;
}
// test it
data =
{
'first': {
'number': 1,
'text': 'Ya.'
},
'second': {
'number': 10,
'text': 'Da.'
}
};
var s = "second";
var s2 = "first.number";
console.log(data.$("first", "text"));
console.log(data.$(s, "number"));
console.log(data.$(["first", "number"]));
console.log(data.$(s2));
edit You could also make a helper function to DEnormalize your object, but only read values after you denormalize it because editing values will cause conflicts since your object will have copies of inner object values.
Example:
data["first"]["number"] == data["first.number"];
data["first.number"] = -1;
data["first"]["number"] != data["first.number"];
De-normalize code
function denormalize(obj, lv) {
var more = false;
for(var k in obj) {
if(k.split(".").length == lv) {
var node = obj[k]
if(node && typeof(node) == 'object') {
more = true;
for(var k2 in node) {
obj[k + "." + k2] = node[k2];
}
}
}
}
if(more)
denormalize(obj, lv + 1);
return obj;
}
// test it
data =
{
'first': {
'number': 1,
'text': 'Ya.'
},
'second': {
'number': 10,
'text': 'Da.'
},
"third": [{"number": 5, "text": "meh"},{"number": 6, "text": "beh"}]
};
denormalize(data, 1);
for(var k in data)
console.log(k + " : " + data[k]);
回答6:
ES2015 can use the destructuring:
data =
{
'first': {
'number': 1,
'text': 'Ya.'
},
'second': {
'number': 10,
'text': 'Da.'
}
};
const {first:{number: yourValue}} = data;
console.log(yourValue); // 1
More examples
回答7:
I'm a little late to this, but I needed the same thing, and figured this was small and functional. (It expects you split('.')
your.key.path
to become ['your', 'key', 'path']
data =
{
'first': {
'number': 1,
'text': 'Ya.'
},
'second': {
'number': 10,
'text': 'Da.',
'array': ['a', {'b':'bar'}, 'c']
}
};
function valueAtPath(object, path) {
if (!object || path.length === 0) return object
return valueAtPath(object[path.shift()], path)
}
function setValueAtPath(object, path, value) {
if (!object || path.length === 0) return null
if (path.length === 1) object[path[0]] = value
else return setValueAtPath(object[path.shift()], path, value)
}
console.log(valueAtPath(data, ['second', 'array', 1, 'b']))
setValueAtPath(data, ['second', 'array', 1, 'b'], 'foo')
console.log(data)
来源:https://stackoverflow.com/questions/17683616/access-property-via-its-keypath-in-javascript