I think it;s dirty/unwanted question.
I have object name test, I just try to assign key and values(if value is valid).
In this below example x,y,z are variables, this variables are dynamic sometimes only we get value.
The below code is working fine, but I used everytime checked value is valid or not then i assign key and value into object.
Just I want to check some smart way to add key?
var test = {
a: "1",
b: "2"
}
var x = "3";
//here x value is dynamic, sometimes only we get value.
if (x) {
test.c = x;
}
var y = "4";
//here y value is dynamic, sometimes only we get value.
if (y) {
test.d = y;
}
var z = "5";
//here z value is dynamic, sometimes only we get value.
if (z) {
test.e = z;
}
console.log(JSON.stringify(test));
If, as in your code, the tests always check to see whether the value is truthy before adding to the object, you could use a Proxy:
const test = {
a: "1",
b: "2"
};
const testProx = new Proxy(test, {
set: (obj, prop, val) => {
if (val) obj[prop] = val;
}
});
testProx.c = 'foo';
testProx.d = null; // falsey, will fail the Proxy's test and will not be added to object
testProx.e = 'bar';
console.log(test);
If you need more complicated validating, such as different conditions for different keys, I'd suggest making an object indexed by key containing a function that returns whether the value for that key is valid:
const test = {
a: "1",
b: "2"
};
// just an example of having different conditions, this is not DRY code:
const testConditions = {
c: (v) => typeof v === 'string' && v[0] === 'c',
d: (v) => typeof v === 'string' && v[0] === 'd',
e: (v) => typeof v === 'string' && v[0] === 'e',
}
const testProx = new Proxy(test, {
set: (obj, prop, val) => {
if (testConditions[prop](val)) obj[prop] = val;
}
});
testProx.c = 'ccc';
// does not start with 'd', will fail the Proxy's test and will not be added to object:
testProx.d = 'fff';
testProx.e = 'eee';
console.log(test);
You can write it in a shorthand way like:
var x, y, z;
var test = {
a: "1",
b: "2",
c: x || null,
d: y || null,
e: z || null
}
console.log(JSON.stringify(test));
Bear in mind that x,y,z must be defined before your test variable or you'll get the error like Uncaught ReferenceError: x is not defined.
And also you can do more type checking with your x,y,z variables, with following syntax:
var x, y, z;
var test = {
a: "1",
b: "2",
c: (x == 3 ? x : null),
d: y ? y : null,
e: z ? z : null
}
console.log(JSON.stringify(test));
If it's coming from another variable then you can loop through that and check every key if the value is null and if not, add it to the test variable.
const main = {
x: 1,
y: 2,
z: 3
}
const test = {
a: 11,
b: 12,
c: 13
}
Object.keys(main).forEach((key) => {
if (main[key]) {
test[key] = main[key]
}
});
console.log(test);
Since the description of your question is not that much clear, I am answering assuming,
You need to validate every value you add into the object.
You are finally using the stringified object for further processing.
You need to automatically set the key for the values.
You may even use a proxy to do the same thing based on your requirement.
var test = {
currentKey:0,
add:function(val){
if(val){
this[String.fromCharCode('a'.charCodeAt()+(this.currentKey++))]=val;
}
},
toJSON:function(){
let obj={...this};
delete obj.currentKey;
delete obj.toJSON;
return obj;
}
}
let x = 90;
test.add(90);
let y = null;
test.add(y);
let z = 89;
test.add(z);
console.log(JSON.stringify(test));
Related
I want to iterate through nested Object and I want to store the all the child keys as separate array and return it as a Object(parent key as key and child keys as value).
let a = {
b: {
f: {
g: "hi",
i: "wow"
},
e: "hello"
},
c: {
j: "ola"
},
d: {
k: ["bonjour","salam"]
}
}
And I am expecting object like this.
{
a:[b,c,d],
b:[f,e],
f:[g,i],
c:[j],
d:[k]
}
I tried and got result to some extent.
let res = {};
let keyVal;
var isObject1: any = val => {
if (typeof val === 'object' && val)
res[keyVal] = Object.keys(val);
}
function retrieveObj(obj1) {
for (const key in obj1) {
const value1: any = obj1[key];
keyVal = key;
if (isObject1(value1))
retrieveObj(value1)
}
}
res['a'] = Object.keys(a);
retrieveObj(a);
Below is the output which I got.
{
a: [ 'b', 'c', 'd' ],
b: [ 'f', 'e' ],
c: [ 'j' ],
d: [ 'k' ]
}
can any one help me to get complete output.
Thanks in advance
You have a few problems with your code
your isObject1 check on whether something is an object or not, is not working correctly, because typeof [1,2,3] == 'object' will return true. You need an additional check for !Array.isArray(val)
You are handling only the first level. You need a recursive call for nested objects.
Not sure what compare is in your context
You should not define keyval as a global variable. If you need to pass values from one scope to another, use parameters, not global variables.
The following should work
let a = {
b: {
f: {
g: "hi",
i: "wow"
},
e: "hello"
},
c: {
j: "ola"
},
d: {
k: ["bonjour","salam"]
}
}
function getAllKeys(p, o, m) {
if (!o || typeof o !== "object" || Array.isArray(o))
return m;
m[p] = Object.keys(o);
for (let k of m[p])
getAllKeys(k, o[k], m)
return m;
}
let m = getAllKeys("a", a, {})
console.log(m)
How does it work
The function getAllKeys accepts as parameters
p the name of the property to look at
o the value of the property to look at
m the result object where all the arrays are merged into
First, if the o which is passed in, is not a "real" object we just return because we don't need to do anything on arrays or primitive types. As typeof [...] and typeof null also return 'object' we need the two additional checks ...
Next we add all keys of the current object o to the result object under a key of p (ie the name of the object we are currently looking at)
And then we check all keys of o recursively. Ie, we pass the key k and value o[k] togehter with the result object m recursively into getAllKeys.
The final return m is just for convinience, so that we don't need to define the resultobject prior to the first call of getAllKeys. Without this return m we would need to call this as follows
let m = {};
getAllKeys("a", a, m);
I don't know what you would expect from an object like the following
let a = {
b: {
c: {
d: 3
}
},
e: {
c: {
f: 4
}
}
}
The current approach will only return c: ['f']. If you want c: ['d', 'f'], you would need the following
m[p] = [ ...(m[p] || []), ...Object.keys(o)];
Consider the following:
var obj =
{
something: "blabla",
otherthing: "mehmeh"
}
let a = obj["something"];
// blabla
let a = obj["doesnotexist"];
// undefined
let a = obj["doesnotexist"] ?? "sorry";
// sorry
let a = obj["something"]["level2"];
let a = obj["something"]["level2"] ?? "sorry";
// Uncaught TypeError
I have to handle lots of cases where I can't know whether a certain object's structure is "complete", so to say, but I still want to access deeply nested properties and get a default return value in case the structure is broken at some level.
So I use this ugly function:
function Safe_Traverse(object, fields, saferesult)
{
if(typeof object != "array" && typeof object != "object") return saferesult;
var value = saferesult;
var check = object;
var i = 0;
var l = fields.length;
while(typeof check[fields[i]] != "undefined" && check[fields[i]]!=null)
{
check = check[fields[i]];
if(i == l-1) value = check; else i = i + 1;
}
return value;
}
let a = Safe_Traverse(obj, ["something", "level2"], "sorry");
// sorry
As I am lost in the many, continuous updates to ECMAScript, I am wondering if there's now a built-in way to achieve the above.
Thanks
Use ?. to return 'nullish' elements as undefined.
let a = obj?.["something"]?.["doesnotexist"];
// undefined
let a = obj?.["something"]?.["doesnotexist"] ?? "sorry";
// undefined
It depends on your use case. If it helps you have a complete object that contains default values if the original object doesn't have a property, then you can create an object with default values and combine it with the other object:
const defaultValues = {
a: 1,
b: {
c: 2
}
}
const obj = {
a: 2
}
const safeObj = {...defaultValues, ...obj}
console.log(safeObj)
//prints {a: 2 b: { c:2}}
Replacer in below code write on console current processed field name
let a = { a1: 1, a2:1 }
let b = { b1: 2, b2: [1,a] }
let c = { c1: 3, c2: b }
let s = JSON.stringify(c, function (field,value) {
console.log(field); // full path... ???
return value;
});
However I would like to get full "path" to field (not only its name) inside replacer function - something like this
c1
c2
c2.b1
c2.b2
c2.b2[0]
c2.b2[1]
c2.b2[1].a1
c2.b2[1].a2
How to do it?
Decorator
replacerWithPath in snippet determine path using this (thanks #Andreas for this tip ), field and value and some historical data stored during execution (and this solution support arrays)
JSON.stringify(c, replacerWithPath(function(field,value,path) {
console.log(path,'=',value);
return value;
}));
function replacerWithPath(replacer) {
let m = new Map();
return function(field, value) {
let path= m.get(this) + (Array.isArray(this) ? `[${field}]` : '.' + field);
if (value===Object(value)) m.set(value, path);
return replacer.call(this, field, value, path.replace(/undefined\.\.?/,''))
}
}
// Explanation fo replacerWithPath decorator:
// > 'this' inside 'return function' point to field parent object
// (JSON.stringify execute replacer like that)
// > 'path' contains path to current field based on parent ('this') path
// previously saved in Map
// > during path generation we check is parent ('this') array or object
// and chose: "[field]" or ".field"
// > in Map we store current 'path' for given 'field' only if it
// is obj or arr in this way path to each parent is stored in Map.
// We don't need to store path to simple types (number, bool, str,...)
// because they never will have children
// > value===Object(value) -> is true if value is object or array
// (more: https://stackoverflow.com/a/22482737/860099)
// > path for main object parent is set as 'undefined.' so we cut out that
// prefix at the end ad call replacer with that path
// ----------------
// TEST
// ----------------
let a = { a1: 1, a2: 1 };
let b = { b1: 2, b2: [1, a] };
let c = { c1: 3, c2: b };
let s = JSON.stringify(c, replacerWithPath(function(field, value, path) {
// "this" has same value as in replacer without decoration
console.log(path);
return value;
}));
BONUS: I use this approach to stringify objects with circular references here
There's just not enough information available in the replacer. These two objects have different shapes but produce the same sequence of calls:
let c1 = { c1: 3, c2: 2 };
let c2 = { c1: { c2: 3 } };
const replacer = function (field, value) {
console.log(field); // full path... ???
return value;
};
JSON.stringify(c1, replacer);
// logs c1, c2
JSON.stringify(c2, replacer);
// logs c1, c2
You'll have to write something yourself.
You can use custom walk function inside your replacer. Here's an example using a generator walk function:
const testObject = {a: 1, b: {a: 11, b: {a: 111, b: 222, c: 333}}, c: 3};
function* walk(object, path = []) {
for (const [key, value] of Object.entries(object)) {
yield path.concat(key);
if (typeof value === 'object') yield* walk(value, path.concat(key));
}
}
const keyGenerator = walk(testObject);
JSON.stringify(testObject, (key, value) => {
const fullKey = key === '' ? [] : keyGenerator.next().value;
// fullKey contains an array with entire key path
console.log(fullKey, value);
return value;
});
Console output:
fullKey | value
-------------------|------------------------------------------------------------
[] | {"a":1,"b":{"a":11,"b":{"a":111,"b":222,"c":333}},"c":3}
["a"] | 1
["b"] | {"a":11,"b":{"a":111,"b":222,"c":333}}
["b", "a"] | 11
["b", "b"] | {"a":111,"b":222,"c":333}
["b", "b", "a"] | 111
["b", "b", "b"] | 222
["b", "b", "c"] | 333
["c"] | 3
This works, assuming the algorithm in replacer is depth-first and consistent across all browsers.
Something like that. You need to adjust it for arrays. I think that you can do it yourself. The idea is clear.
let a = { a1: 1, a2:1 }
let b = { b1: 2, b2: [1,a] }
let c = { c1: 3, c2: b }
function iterate(obj, path = '') {
for (var property in obj) {
if (obj.hasOwnProperty(property)) {
if (typeof obj[property] == "object") {
iterate(obj[property], path + property + '.');
}
else {
console.log(path + property);
}
}
}
}
iterate(c)
Based on the other answers I have this function which adds a third path argument to the call of replacer:
function replacerWithPath(replacer) {
const m = new Map();
return function (field, value) {
const pathname = m.get(this);
let path;
if (pathname) {
const suffix = Array.isArray(this) ? `[${field}]` : `.${field}`;
path = pathname + suffix;
} else {
path = field;
}
if (value === Object(value)) {
m.set(value, path);
}
return replacer.call(this, field, value, path);
}
}
// Usage
function replacer(name, data, path) {
// ...
}
const dataStr = JSON.stringify(data, replacerWithPath(replacer));
BONUS:
I also created this function to iterate through an object in depth and to be able to use the replace function like with JSON.stringify.
The third argument to true will keep undefined values and empty objects.
It can be handy to modify and ignore values while iterating through an object, it returns the new object (without stringify).
function walkWith(obj, fn, preserveUndefined) {
const walk = objPart => {
if (objPart === undefined) {
return;
}
let result;
// TODO other types than object
for (const key in objPart) {
const val = objPart[key];
let modified;
if (val === Object(val)) {
modified = walk(fn.call(objPart, key, val));
} else {
modified = fn.call(objPart, key, val);
}
if (preserveUndefined || modified !== undefined) {
if (result === undefined) {
result = {};
}
result[key] = modified;
}
}
return result;
};
return walk(fn.call({ '': obj }, '', obj));
}
BONUS 2:
I use it to transform a data object coming from a form submission and containing files / arrays of files in mixed multipart, files + JSON.
function toMixedMultipart(data, bodyKey = 'data', form = new FormData()) {
const replacer = (name, value, path) => {
// Simple Blob
if (value instanceof Blob) {
form.append(path, value);
return undefined;
}
// Array of Blobs
if (Array.isArray(value) && value.every(v => (v instanceof Blob))) {
value.forEach((v, i) => {
form.append(`${path}[${i}]`, v);
});
return undefined;
}
return value;
};
const dataStr = JSON.stringify(data, replacerWithPath(replacer));
const dataBlob = new Blob([dataStr], { type: 'application/json' });
form.append(bodyKey, dataBlob);
return form;
}
I have an object. I would like to modify the object (not clone it) by removing all properties except for certain specific properties. For instance, if I started with this object:
var myObj={
p1:123,
p2:321,
p3:{p3_1:1231,p3_2:342},
p4:'23423',
//....
p99:{p99_1:'sadf',p99_2:234},
p100:3434
}
and only want properties p1, p2, and p100, how can I obtain this object:
var myObj={
p1:123,
p2:321,
p100:3434
}
I understand how I could do this with brute force, but would like a more elegant solution.
This was the first hit when googling 'js keep only certain keys' so might be worth an update.
The most 'elegant' solution might just be using underscore.js
_.pick(myObj, 'p1', 'p2', 'p100')
Just re-initialise the object:
myObj = {
p1: myObj.p1,
p2: myObj.p2,
p100: myObj.p100
};
Another way is to delete certain properties, which is less effective:
var prop = ['p1', 'p2', 'p100'];
for (var k in myObj) {
if (prop.indexOf(k) < 0) {
delete myObj[k];
}
}
You could use this approach:
let result = (({ p1, p2, p100 }) => ({ p1, p2, p100 }))(myObj);
which I learned at https://stackoverflow.com/a/25554551/470749.
You could use delete:
for (var k in myObj) {
if (k !== 'p1' && k !== 'p2' && k !== 'p100') {
delete myObj[k];
}
}
An alternative to indexOf:
var take = /^p(1|2|100)$/;
for (var k in myObj) {
if (!take.test(k)) {
delete myObj[k];
}
}
Shorter:
var take = /^p(1|2|100)$/;
for (var k in myObj) {
take.test(k) || delete myObj[k];
}
Array to RegExp:
var take = [1, 2, 100];
take = new RegExp('^p(' + take.join('|') + ')$'); // /^p(1|2|100)$/
take.test('p1'); // true
take.test('p3'); // false
Useful in a function:
function take(o, ids) {
var take = new RegExp('^p(' + ids.join('|') + ')$');
for (var k in o) take.test(k) || delete o[k];
return o;
}
Usage:
take(myObj, [1, 2, 100]); // { p1: 123, p2: 321, p100: 3434 }
If you don't like regular expressions:
function take(o, keys) {
for (var k in o) contains(keys, k) || delete o[k];
return o;
}
function contains(array, value) {
var i = -1, l = array.length;
while (++i < l) if (array[i] === value) return true;
return false;
}
function prefix(array, prefix) {
var i = -1, l = array.length, output = [];
while (++i < l) output.push(prefix + array[i]);
return output;
}
Usage:
take(myObj, ['p1', 'p2', 'p100']);
// with a middleman :
var idsToTake = [1, 2, 100];
take(myObj, prefix(idsToTake, 'p'));
Lodash has a function called pick which does what you're describing. I know that including a library isn't ideal, but you can also cherry-pick functions when using bundles, etc.
var myObj={
p1:123,
p2:321,
p3:{p3_1:1231,p3_2:342},
p4:'23423',
//....
p99:{p99_1:'sadf',p99_2:234},
p100:3434
}
var newObj = _.pick(myObj, 'p1', 'p2', 'p100')
var myObj = {a: 1, b: 2, c:3};
function keepProps(obj, keep) {
for (var prop in obj) {
if (keep.indexOf( prop ) == -1) {
delete obj[prop];
}
}
}
keepProps(myObj, ['a', 'b']);
console.log(myObj);
http://jsfiddle.net/mendesjuan/d8Sp3/2/
An object stored in a variable named o :
var o = { a: 1, b: 2 };
A new reference to this object :
var p = o;
o and p both refer to the same object :
o // { a: 1, b: 2 }
p // { a: 1, b: 2 }
o === p // true
Let's update the object through o :
delete o.b;
o // { a: 1 }
p // { a: 1 }
Let's update the object through p :
p.b = 2;
o // { a: 1, b: 2 }
p // { a: 1, b: 2 }
As you can see, o and p are in sync.
Let's "reinitialize" o :
o = { a: o.a };
o and p now refer to different objects :
o // { a: 1 }
p // { a: 1, b: 2 }
o === p // false
Let's update the object stored in o :
o.c = 3;
o // { a: 1, c: 3 }
p // { a: 1, b: 2 }
Let's update the object stored in p :
delete p.a;
o // { a: 1, c: 3 }
p // { b: 2 }
As you can see, o and p are not in sync anymore.
The question is : do you want to keep both variables (o and p) synchronized? If so, the second code block of VisioN's answer is the right one, otherwise, choose the first code block.
You can code your own implementation of _.pick, and use it according to your needs.
Having this snippet of code as the base for the following cases:
const myObj={
p1:123,
p2:321,
p3:{p3_1:1231,p3_2:342},
p4:'23423',
//....
p99:{p99_1:'sadf',p99_2:234},
p100:3434
}
let properties= ['p1','p2', 'p3', 'p100'];
case 1:
You want a shallow copy (with references to vector values)
const myNewObj = properties.reduce((newObj,property)=>{newObj[property] = myObj[property]; return newObj}, {})
// if we modify the original vector value of 'p3' in `myObj` we will modify the copy as well:
myObj.p3.p3_1 = 99999999999;
console.log(myNewObj); // { p1: 123, p2: 321, p3: { p3_1: 99999999999, p3_2: 42 }, p100: 3434 }
case 2:
You want a deep copy (losing references to vector values)
You can just use JSON utilities to that matter.
const myNewObj2 = properties.reduce((newObj,property)=>{newObj[property] = JSON.parse(JSON.stringify(myObj[property])); return newObj},{})
// no matter how hard you modify the original object, you will create a new independent object
myObj.p3.p3_1 = 99999999999;
console.log(myNewObj2) // { p1: 123, p2: 321, p3: { p3_1: 1231, p3_2: 342 }, p100: 3434 }
Reusing case 2 with a function
You could implement a reducer to use it in different scenarios, like this one:
function reduceSelectedProps(origin, properties){
return (newObj,property)=>{
newObj[property] = JSON.parse(JSON.stringify(origin[property]));
return newObj
}
}
So you could have a more elegant reuse of it:
const myNewObj3 = properties.reduce(reduceSelectedProps(myObj, properties),{});
// no matter how hard you modify the original object, you will create a new independent object
myObj.p3.p3_1 = 99999999999;
console.log(myNewObj3) // { p1: 123, p2: 321, p3: { p3_1: 1231, p3_2: 342 }, p100: 3434 }
disclaimers:
This is only an example that does not handle Date, Set, Map or function values inside the properties. To deal with all these cases (and many others), it needs a really complex function with checks on the prototypes and all that stuff. At this point, consider reusing the work of other developers using any library that could do it. Lodash?
I suppose you could add a new method to the prototype:
if (!('keepOnlyTheseProps' in Object.prototype)) {
Object.prototype.keepOnlyTheseProps = function (arr) {
var keys = Object.keys(this);
for (var i = 0, l = keys.length; i < l; i++) {
if (arr.indexOf(keys[i]) < 0) delete this[keys[i]];
}
}
}
myObj.keepOnlyTheseProps(['p1', 'p2', 'p100']);
Fiddle.
Pass a map of whitelisted keys into an IIFE (immediately invoked function expression); not just elegant but also flexible IMO (especially if moved off into a function not unlike in Juan Mendes' answer)
var myObj={
p1:123,
p2:321,
p3:{p3_1:1231,p3_2:342},
p4:'23423',
//....
p99:{p99_1:'sadf',p99_2:234},
p100:3434
}
var myObj = (function(origObj, whiteListMap) {
for (var prop in origObj) {
if (!whiteListMap[prop]) {
delete origObj[prop];
}
}
return myObj;
})(myObj, {'p1': 1, 'p2': 1, 'p100': 1});
console.log(JSON.stringify(myObj)); //{"p1":123,"p2":321,"p100":3434}
You could create a view on your first object, some kind of proxy that would only keep the desired properties on sight.
For instance the following function will create a view that allows to both read and write the underlying object, keeping only the choosen properties.
You can make it readonly very easily, by just removing the setter.
You might also want to seal the proxy object, so that no later modification can me made to it.
function createView(obj, propList) {
var proxy = {};
for (var propIndex in propList) {
var prop=propList[propIndex];
Object.defineProperty(proxy, prop,
{ enumerable : true ,
get : getter.bind(obj,prop),
set : setter.bind(obj,prop) } );
}
return proxy;
}
function getter(prop) { return this[prop] ; }
function setter(prop, value) { return this[prop] = value ; }
An example of use would be :
var myObj={
p1:123,
p2:321,
p3:{p3_1:1231,p3_2:342},
p4:'23423',
p99:{p99_1:'sadf',p99_2:234},
p100:3434
};
var objView = createView(myObj, ['p1', 'p2', 'p100']);
Here, objView 'reflects' the desired properties of myObj.
You can look at the small jsbin i made here :
http://jsbin.com/munudewa/1/edit?js,console
results :
"on objView, p1:123 p2:321 p100:3434 and p4 (not in view) is : undefined"
"modifiying, on the view, p1 to 1000 and p2 to hello "
"on objView, p1:1000 p2:hello p100:3434 and p4 (not in view) is : undefined"
"modifiying, on the viewed object, p1 to 200 and p2 to bye "
"on objView, p1:200 p2:bye p100:3434 and p4 (not in view) is : undefined"
notice that :
1) you can overwrite an object by its view, only keeping desired properties.
2) you can save in a hidden property / in a closure, the original object, so you can later change the properties you expose.
I Made this short solution for case where I have an array with objects.
so consider the array below?
arr=[{"a1":"A1","b1":"B1"},{"a1":"Z1","b1":"X1"}];
console.log(arr);
I want to keep only "b1" properties of all objects.
You can use map() and delete for that as follows:
arr=[{"a1":"A1","b1":"B1"},{"a1":"Z1","b1":"X1"}];
arr=arr.map(function(d){
delete d["a1"];
return d;
});
console.log(arr);
result is an array with objects but only "b1" properties.
just a single line of pure js code
var myObj={
p1:123,
p2:321,
p3:{p3_1:1231,p3_2:342},
p4:'23423',
//....
p99:{p99_1:'sadf',p99_2:234},
p100:3434
}
Object.keys(myObj).forEach(key => { if(!["p1","p2","p100"].includes(key)) delete myObj[key]; })
This question already has answers here:
Convert a JavaScript string in dot notation into an object reference
(34 answers)
Closed 7 years ago.
Consider this object in javascript,
var obj = { a : { b: 1, c: 2 } };
given the string "obj.a.b" how can I get the object this refers to, so that I may alter its value? i.e. I want to be able to do something like
obj.a.b = 5;
obj.a.c = 10;
where "obj.a.b" & "obj.a.c" are strings (not obj references).
I came across this post where I can get the value the dot notation string is referring to obj but what I need is a way I can get at the object itself?
The nesting of the object may be even deeper than this. i.e. maybe
var obj = { a: { b: 1, c : { d : 3, e : 4}, f: 5 } }
To obtain the value, consider:
function ref(obj, str) {
str = str.split(".");
for (var i = 0; i < str.length; i++)
obj = obj[str[i]];
return obj;
}
var obj = { a: { b: 1, c : { d : 3, e : 4}, f: 5 } }
str = 'a.c.d'
ref(obj, str) // 3
or in a more fancy way, using reduce:
function ref(obj, str) {
return str.split(".").reduce(function(o, x) { return o[x] }, obj);
}
Returning an assignable reference to an object member is not possible in javascript, you'll have to use a function like the following:
function set(obj, str, val) {
str = str.split(".");
while (str.length > 1)
obj = obj[str.shift()];
return obj[str.shift()] = val;
}
var obj = { a: { b: 1, c : { d : 3, e : 4}, f: 5 } }
str = 'a.c.d'
set(obj, str, 99)
console.log(obj.a.c.d) // 99
or use ref given above to obtain the reference to the containing object and then apply the [] operator to it:
parts = str.split(/\.(?=[^.]+$)/) // Split "foo.bar.baz" into ["foo.bar", "baz"]
ref(obj, parts[0])[parts[1]] = 99
Similar to thg435's answer, but with argument checks and supports nest levels where one of the ancestor levels isn't yet defined or isn't an object.
setObjByString = function(obj, str, val) {
var keys, key;
//make sure str is a string with length
if (!str || !str.length || Object.prototype.toString.call(str) !== "[object String]") {
return false;
}
if (obj !== Object(obj)) {
//if it's not an object, make it one
obj = {};
}
keys = str.split(".");
while (keys.length > 1) {
key = keys.shift();
if (obj !== Object(obj)) {
//if it's not an object, make it one
obj = {};
}
if (!(key in obj)) {
//if obj doesn't contain the key, add it and set it to an empty object
obj[key] = {};
}
obj = obj[key];
}
return obj[keys[0]] = val;
};
Usage:
var obj;
setObjByString(obj, "a.b.c.d.e.f", "hello");
If this javascript runs in a browser then you can access the object like this:
window['obj']['a']['b'] = 5
So given the string "obj.a.b" you have to split the it by .:
var s = "obj.a.b"
var e = s.split(".")
window[e[0]][e[1]][e[2]] = 5
Returning an assignable reference to an object member is not possible in javascript. You can assign value to a deep object member by dot notation with a single line of code like this.
new Function('_', 'val', '_.' + path + ' = val')(obj, value);
In you case:
var obj = { a : { b: 1, c: 2 } };
new Function('_', 'val', '_.a.b' + ' = val')(obj, 5); // Now obj.a.b will be equal to 5
var obj = { a : { b: 1, c: 2 } };
walkObject(obj,"a.b"); // 1
function walkObject( obj, path ){
var parts = path.split("."), i=0, part;
while (obj && (part=parts[i++])) obj=obj[part];
return obj;
}
Or if you like your code terse:
function walkObject( o, path ){
for (var a,p=path.split('.'),i=0; o&&(a=p[i++]); o=o[a]);
return o;
}
Below is a simple class wrapper around dict:
class Dots(dict):
def __init__(self, *args, **kargs):
super(Dots, self).__init__(*args, **kargs)
def __getitem__(self, key):
try:
item = super(Dots, self).__getitem__(key)
except KeyError:
item = Dots()
self.__setitem__(key, item)
return Dots(item) if type(item) == dict else item
def __setitem__(self, key, value):
if type(value) == dict: value = Dots(value)
super(Dots, self).__setitem__(key, value)
__getattr__ = __getitem__
__setattr__ = __setitem__
Example:
>>> a = Dots()
>>> a.b.c = 123
>>> a.b.c
123
>>> a.b
{'c': 123}
>>> a
{'b': {'c': 123}}
Missing key are created on the fly as empty Dots():
>>> if a.Missing: print "Exists"
...
>>> a
{'Missing': {}, 'b': {'c': 123}}