after having discovered that getters will allow me to solve a problem, I'd like to convert all my top level keys into getters but was wondering how I can do this. I'm leaning towards looping over Object.keys(obj) as a starting point but would appreciate any method:
const obj = {
parent: {
child: {
aunt: this.aunt // this will be undefined without getters
}
},
aunt: {
foo: {
bar: 1
}
},...
}
into:
const obj = {
get parent() {
return {
child: {
aunt: this.aunt
}
}
},
get aunt(){
return {
foo: {
bar: 1
}
}
},...
}
You could do it nicely with a map function over the object keys and using the defineProperty over a new object:
var obj_getters = {}
Object.keys(obj).map(function(key) {
Object.defineProperty(obj_getters, key, {
get: function() { return obj[key] }
});
})
Related
Below I have two sets of plain-objects, and within each there is a getter. I'd love to find a way to merge these two objects. However when I merge them I'd like them to still be getters. I do not want the values within side getters to be resolved.
const love = {
get cats() {
return 'meow';
},
};
const hate = {
get dogs() {
return 'woof';
},
};
console.log({...love, ...hate}); // { cats: 'meow', dogs: 'woof' }
console.log(Object.assign(love, hate)); // { cats: [Getter], dogs: 'woof' }
Use defineProperties to put the properties on the object, spreading into it values from Object.getOwnPropertyDescriptors:
const love = {
get cats() {
return 'meow';
},
};
const hate = {
get dogs() {
return 'woof';
},
};
const result = Object.defineProperties({}, {
...Object.getOwnPropertyDescriptors(love),
...Object.getOwnPropertyDescriptors(hate),
});
console.log(Object.getOwnPropertyDescriptor(result, 'dogs'));
Made this little nugget from #CertainPerformances answer:
function mergeGetters<A, B>(a: A, b: B): A & B {
const result = Object.defineProperties(
{},
{
...Object.getOwnPropertyDescriptors(a),
...Object.getOwnPropertyDescriptors(b),
}
);
return result;
}
I'm trying to reduce the amount of duplicate code i'm writing in JS objects. I have a methods that I want to use where almost nothing is changing except the target and I'd like to extract that out and somehow get the target through the objects property name. Hopefully the example I put together makes sense.
myObject = {
d: {
get: function(list, id) {
// do stuff
},
prop1: {
data: [],
list: myObject.config.lists.prop1.guid,
get: function(a,b) {
myObject.d.get(a,b)
}
},
// I want to write this once and use the object key ("prop2") as an argument
prop2: {
data: [],
list: myObject.config.lists.prop2.guid,
get: function(a,b) {
myObject.d.get(a,b)
}
}
}
};
Tried something like this but getting error "Cannot read prop 'spec' of undefined:"
myObject = {
d: {
get: function(list, id) {
// do stuff
}
},
// Use this to duplicate shared funtions for similar
spec: function(target) {
return {
data: [],
list: myObject.config.lists[target].guid,
get: function() {
myObject.d.get(a, b);
},
update: "",
delete: ""
};
},
// some how return `myObject.spec.get()`, allowing me to use myObject.d.prop1.get()
prop1: myObject.spec.apply(this, "prop1"),
prop2: myObject.spec.apply(this, "prop2")
};
So far the only way I was able to get it working was by setting prop1 and prop2 outside of the initial deceleration like this and explicitly declaring the target like #Bergi suggested:
var myObject = myObject || {};
myObject = {
d: {
get: function(list, id) {
// do stuff
}
},
// Use this to duplicate shared funtions for similar
spec: function(target) {
return {
data: [],
list: target,
get: function() {
myObject.d.get(a, b);
},
update: "",
delete: ""
};
}
};
// some how return `myObject.spec.get()`, allowing me to use myObject.d.prop1.get()
myObject.prop1 = myObject.spec("prop1");
myObject.prop2 = myObject.spec("prop2");
I have an objects
usersById: {
1: { name: 'John' },
2: { name: 'Michelle' },
...
}
I want to return the same object, but first populate the object at id=2 with a new property age, but sticking to immutability.
I would guess it would be something like
return {
...usersById,
...usersById[2].age = 40
}
but I receive an error In this environment the sources for assign MUST be an object. This error is a performance optimization and not spec compliant.
Alternatively, I would guess it should be something like
return Object.keys(usersById).map(userId => {
if (userId === 2) {
return {
...usersById[2],
...age = 40
}
}
return usersById[userId]
})
but it returns an array and not an object.
You've got the right idea but the wrong syntax. Try this instead:
return {
...usersById,
2: {
...usersById[2],
age: 40
}
}
Or if the key is dynamic, you can do this:
let key = 2;
return {
...usersById,
[key]: {
...usersById[key],
age: 40
}
}
You can make your own function to return same object with populated values
Simple example:
var usersById = {
1: { name: 'John' },
2: { name: 'Michelle' },
}
usersById = oneLevelDeepAssign(usersById,2,{age:21})
function oneLevelDeepAssign(object, key, objectToAssign){
return Object.assign({},object,{[key]:Object.assign({},object[key],objectToAssign)})
}
console.log(usersById);
I am currently reading about Symbols and Iterators (ES6 features) and after running into an example there, I am trying to make an object iterable so that I can use for...of feature. From few examples/answers here/articles I checked, it looks like this in plain case:
let obj = {
prop1: 5,
prop2: 'test',
prop3: new Date(),
[Symbol.iterator]: () => ({
items: obj.items,
next: function next() {
return {
done: this.items.length === 0,
value: this.items.shift()
}
}
})
};
Object.defineProperty(obj, "items", {
enumerable: false,
get: function() {
let props = [];
for(let prop in this) if(this.hasOwnProperty(prop)) props.push(this[prop]);
return props;
}
});
for(let prop of obj) console.log(prop);
But I find it annoying to list manually all the values of an object's properties in items array of iterator. Also it feels dirty and messy with Object.defineProperty. I am kind of trying tom expand the example from the link. Is there a smarter/simpler way to get all the object's properties inside iterator (instead of items: obj.items and related bloat or manually listing items like items: [5, 'test', new Date()])?
You could return a generator in the symbol iterator maybe:
[Symbol.iterator]:function*(){
for(value of Object.values(this)){
yield value;//may extend this to your needs
}
}
Or in your case:
[Symbol.iterator]:function*(){
var i=1;
while(this["prop"+i]){
yield this["prop"+i];//may extend this to your needs
i++;
}
}
http://jsbin.com/zekunalusi/edit?console
You should not use an items getter but instead just create the array inside the iterator method. Also you can use generator syntax to create the iterator, which is much easier.
But the simplest way to achieve what you want is
let obj = {
prop1: 5,
prop2: 'test',
prop3: new Date(),
[Symbol.iterator]() { return Object.keys(this).map(k => this[k])[Symbol.iterator](); }
};
function objectEntries(obj) {
let index = 0;
// In ES6, you can use strings or symbols as property keys,
// Reflect.ownKeys() retrieves both
let propKeys = Reflect.ownKeys(obj);
return {
[Symbol.iterator]() {
return this;
},
next() {
if (index < propKeys.length) {
let key = propKeys[index];
index++;
return { value: [key, obj[key]] };
} else {
return { done: true };
}
}
};
}
let obj = { first: 'Jane', last: 'Doe' };
for (let [key,value] of objectEntries(obj)) {
console.log(`${key}: ${value}`);
}
http://2ality.com/2015/02/es6-iteration.html
I'm looking for a way to walk an object based on an array and set the property for the last key on the object, for example:
var myArr = [ 'foo', 'bar', 'quz' ];
var myVal = 'somethingElse';
var myObj = {
foo: {
bar: {
quz: 'something'
}
}
};
I'd like to be able to change the value of the quz property to somethingElse. I've tried recursing but I feel like there's an easier way to do this.
I've been looking to lodash but can't find a method that seems to allow me to accomplish this.
You could walk the object like this:
var myArr = [ 'foo', 'bar', 'quz' ],
myVal = 'somethingElse',
myObj = {
foo: {
bar: {
quz: 'something'
}
}
};
var obj= myObj;
do {
obj= obj[myArr.shift()];
} while(myArr.length>1);
obj[myArr[0]]= 'somethingElse';
document.body.innerHTML= JSON.stringify(myObj);
Update
To address #Tomalak's concerns, and because you didn't specifically forbid a recursive solution, here's a reusable function with no side effects (other than changing the appropriate value of the object):
function setObj(obj, arr, val) {
!(arr.length-1) && (obj[arr[0]]=val) ||
setObj(obj[arr[0]], arr.slice(1), val);
}
Short-circuit evaluation prevents this from being an infinite loop.
Snippet:
var myArr = [ 'foo', 'bar', 'quz' ],
myVal = 'somethingElse',
myObj = {
foo: {
lorem: 'ignore me',
bar: {
quz: 'something'
},
other: {
quz: 'leave me be'
}
}
};
function setObj(obj, arr, val) {
!(arr.length-1) && (obj[arr[0]]=val) ||
setObj(obj[arr[0]], arr.slice(1), val);
}
setObj(myObj, myArr, 'somethingElse');
document.body.innerHTML= JSON.stringify(myObj);
var myArr = [ 'foo', 'bar', 'quz' ];
var myVal = 'somethingElse';
var myObj = {
foo: {
bar: {
quz: 'something'
}
}
};
function setHierarchcally(obj, keys, value) {
if ( !(obj && keys && keys.length) ) return;
if ( !obj.hasOwnProperty(keys[0]) ) return;
if (keys.length === 1) {
obj[keys[0]] = value;
} else {
setHierarchcally(obj[keys[0]], keys.slice(1, keys.length), value);
}
}
setHierarchcally(myObj, myArr, myVal);
getPath digs down into an object to get a property several levels deep based on an array of "paths", in your case MyArr. Use that to get the object containing the final property, and then just set it.
function getPath(obj, paths) {
return paths.reduce(function(obj, path) { return obj[path]; }, obj);
}
function setLastProperty(obj, paths, val) {
var final = paths.pop();
getPath(obj, paths) [ final ] = val;
}
setLastProperty(MyObj, MyArray, MyVal);
If you want more general object traverse, you can tweak js-travserse a little bit, as demoed in this jsfiddle I just created:
`https://jsfiddle.net/yxpx9wvL/10/
var leaves = new Traverse(myObj).reduce(function (acc, x) {
if (this.isLeaf) acc.push(x);
return acc;
}, []);
alert(leaves[0]);