Recursive Object.defineProperty() getters - javascript

I'm trying to recursively assign the object values from class constructor argument as a properties of the class. Can't figure out how to do a recursion - getting 'Maximum call stack size exceeded' and infinity loops most of the time.
Here is the demo:
const Locale = function(rules) {
for (let prop in rules) {
Object.defineProperty(this, prop, {
get: function () {
console.log('getter for "%s" called', prop)
return rules[prop];
}
});
}
}
const rules = {
a: {
b: {
c: 'value'
}
}
}
const locale = new Locale(rules);
console.log(locale.a.b.c);
Now I'm getting the following console output:
getter for "a" called
value
How to assign a getter for each level of the rules object? Expected console output:
getter for "a" called
getter for "b" called
getter for "c" called
value

You need to create a Locale object for each nested level of the rules object:
const Locale = function(rules) {
for (let prop in rules) {
Object.defineProperty(this, prop, {
get: function () {
console.log('getter for "%s" called', prop);
// create new Locale if object, return value if not an object
if( rules[prop] !== null && typeof rules[prop] === 'object' )
return new Locale( rules[prop] );
else
return rules[prop];
}
});
}
}
const rules = {
a: {
b: {
c: 'value'
}
}
}
const locale = new Locale(rules);
console.log(locale.a.b.c);

If you just want to use a function instead of a class you can do:
var locale = (rules) => {
if (rules !== null && typeof rules !== 'object') return rules
return Object.defineProperties({}, Object.keys(rules).reduce((acc, key) => ({
...acc,
[key]: {
get: () => {
console.log('getter for "%s" called', key)
return locale(rules[key])
}
}
}), {}))
}
var l = locale(rules)
console.log('locale', l)
console.log(l.a.b.c)

Related

Underscore "defaults" from scratch

I've been trying to rewrite the _.defaults method from underscore.js and I keep getting this error:
should copy source properties to undefined properties in the destination object‣
AssertionError: expected { a: 'existing' } to deeply equal { a: 'existing', b: 2, c: 3, d: 4 }
Here's the missing conditions:
-should copy source properties to undefined properties in the destination object and should return the destination object
and my code:
_.defaults = function (destination, source) {
Object.keys(destination).forEach(key => {
if (destination[key] === undefined || destination[key] === null) {
destination[key] = source[key];
}
})
return destination;
};
The idea in _.defaults is that keys from the source object might be missing entirely in the destination object. So you need to iterate the keys of the source object instead of those of the destination object:
_.defaults = function (destination, source) {
Object.keys(source).forEach(key => {
if (destination[key] === undefined || destination[key] === null) {
destination[key] = source[key];
}
})
return destination;
};
Side note: you can write x == null instead of x === null || x === undefined. Saves some precious keystrokes.
_.defaults = function (destination, source) {
Object.keys(source).forEach(key => {
if (destination[key] == null) {
destination[key] = source[key];
}
})
return destination;
};

How to avoid 'undefined' errors in nested objects [duplicate]

This question already has answers here:
Access Javascript nested objects safely
(14 answers)
Closed 4 years ago.
I'm looking for some good strategies for avoiding errors in JavaScript when using dot notation to call the children of children in objects that may or may not exist.
At the bottom of the code snippet below is an example of a solution that works, but is inelegant (at best).
It would be great to see some native JavaScript solutions or even external libraries that can help avoid this kind of error.
const object1 = {
foo: {
bar: {
baz: 'payload'
}
}
};
const object2 = {};
const array = [object1, object2];
// this will fail on object2 because obj.foo is undefined
array.forEach(obj => {
if (obj.foo.bar.baz) {
console.log(obj.foo.bar.baz);
} else {
console.log('undefined');
}
}
);
// this will work, but it's horrible to write all those nested if statements.
array.forEach(obj => {
if (obj) {
if (obj.foo) {
if (obj.foo.bar) {
if (obj.foo.bar.baz) {
console.log(obj.foo.bar.baz);
}
}
}
} else {
console.log('undefinded');
}
}
);
Lodash already did it for us: https://lodash.com/docs#get
const object = { 'a': [{ 'b': { 'c': 3 } }] };
_.get(object, 'a[0].b.c');
// => 3
_.get(object, ['a', '0', 'b', 'c']);
// => 3
_.get(object, 'a.b.c', 'default');
// => 'default'
No sure if that's enough of an improvement but you can use a single if statement with the following condition:
(obj && obj.foo && obj.foo.bar && obj.foo.bar.baz)
This will check if obj.foo.bar.baz exists.
const array=[{foo:{bar:{baz:'payload'}}},{}]
array.forEach(obj => {
if (obj && obj.foo && obj.foo.bar && obj.foo.bar.baz) {
console.log(obj.foo.bar.baz);
} else {
console.log('undefined');
}
});
You could chain all checks with logical AND &&.
const
object1 = { foo: { bar: { baz: 'payload' } } },
object2 = {},
array = [object1, object2];
array.forEach(obj => {
if (obj && obj.foo && obj.foo.bar && obj.foo.bar.baz) {
console.log(obj.foo.bar.baz);
} else {
console.log('undefined');
}
});
For an automatic check, you could take an array of keys and return either the value or undefined.
const
getValue = (object, keys) => keys.reduce((o, k) => (o || {})[k], object),
object1 = { foo: { bar: { baz: 'payload' } } },
object2 = {},
array = [object1, object2];
array.forEach(obj => console.log(getValue(obj, ['foo', 'bar', 'baz'])));
Just to share my two cents:
Some time ago I made a function that allowed to safely access deep properties in javascript using proxies:
// Here is where the magic happens
function optional(obj, evalFunc, def) {
// Our proxy handler
const handler = {
// Intercept all property access
get: function(target, prop, receiver) {
const res = Reflect.get(...arguments);
// If our response is an object then wrap it in a proxy else just return
return typeof res === "object" ? proxify(res) : res != null ? res : def;
}
};
const proxify = target => {
return new Proxy(target, handler);
};
// Call function with our proxified object
return evalFunc(proxify(obj, handler));
}
const obj = {
items: [{
hello: "Hello"
}]
};
console.log(optional(obj, target => target.items[0].hello, "def")); // => Hello
console.log(optional(obj, target => target.items[0].hell, {
a: 1
})); // => { a: 1 }
Also, I wrote an article on this for further reference.

Access to object property with dynamic name and dynamic nesting level

I read the property of an object I want to access from a string: level1.level2.property OR level1.property OR ... The names and the nesting may vary. I store the objects in a separate module (workerFunctions).
I know that I can access the objects dynamically with the []notation, e.g.:
var level1="level1";
var property="property";
console.log(workerFunctions[level1][property])
However, I don't know how to construct this "workerFunctions[level1][property]" dynamically from varying input strings, so to produce e.g.:
console.log(workerFunctions[level1][level2][property])
in consequence of the string: level1.level2.property.
Thank you in advance.
You could split the path and use the parts as properties for the given object.
function getValue(o, path) {
return path.split('.').reduce(function (o, k) {
return (o || {})[k];
}, o);
}
var o = { A : { B: { C: { value: 'Brenda' } } } };
console.log(getValue(o, 'A.B.C').value); // Brenda
console.log(getValue(o, 'Z.Y.X')); // undefined
For better use with dots in properties, you could use an array directly to avoid wrong splitting.
function getValue(o, path) {
return path.reduce(function (o, k) {
return (o || {})[k];
}, o);
}
var o = { A : { 'B.C': { value: 'Brenda' } } };
console.log(getValue(o, ['A', 'B.C', 'value'])); // Brenda
console.log(getValue(o, ['Z.Y.X'])); // undefined
This should do it :
const str = 'level1.level2.property';
let value = { //workerFunctions;
level1: {
level2: {
property: 'this is the value'
}
}
};
str.split(/\./).forEach((prop) => {
value = value[prop];
});
console.log(value);

How to use javascript proxy for nested objects

I have this code in js bin:
var validator = {
set (target, key, value) {
console.log(target);
console.log(key);
console.log(value);
if(isObject(target[key])){
}
return true
}
}
var person = {
firstName: "alfred",
lastName: "john",
inner: {
salary: 8250,
Proffesion: ".NET Developer"
}
}
var proxy = new Proxy(person, validator)
proxy.inner.salary = 'foo'
if i do proxy.inner.salary = 555; it does not work.
However if i do proxy.firstName = "Anne", then it works great.
I do not understand why it does not work Recursively.
http://jsbin.com/dinerotiwe/edit?html,js,console
You can add a get trap and return a new proxy with validator as a handler:
var validator = {
get(target, key) {
if (typeof target[key] === 'object' && target[key] !== null) {
return new Proxy(target[key], validator)
} else {
return target[key];
}
},
set (target, key, value) {
console.log(target);
console.log(key);
console.log(value);
return true
}
}
var person = {
firstName: "alfred",
lastName: "john",
inner: {
salary: 8250,
Proffesion: ".NET Developer"
}
}
var proxy = new Proxy(person, validator)
proxy.inner.salary = 'foo'
A slight modification on the example by Michał Perłakowski with the benefit of this approach being that the nested proxy is only created once rather than every time a value is accessed.
If the property of the proxy being accessed is an object or array, the value of the property is replaced with another proxy. The isProxy property in the getter is used to detect whether the currently accessed object is a proxy or not. You may want to change the name of isProxy to avoid naming collisions with properties of stored objects.
Note: the nested proxy is defined in the getter rather than the setter so it is only created if the data is actually used somewhere. This may or may not suit your use-case.
const handler = {
get(target, key) {
if (key == 'isProxy')
return true;
const prop = target[key];
// return if property not found
if (typeof prop == 'undefined')
return;
// set value as proxy if object
if (!prop.isProxy && typeof prop === 'object')
target[key] = new Proxy(prop, handler);
return target[key];
},
set(target, key, value) {
console.log('Setting', target, `.${key} to equal`, value);
// todo : call callback
target[key] = value;
return true;
}
};
const test = {
string: "data",
number: 231321,
object: {
string: "data",
number: 32434
},
array: [
1, 2, 3, 4, 5
],
};
const proxy = new Proxy(test, handler);
console.log(proxy);
console.log(proxy.string); // "data"
proxy.string = "Hello";
console.log(proxy.string); // "Hello"
console.log(proxy.object); // { "string": "data", "number": 32434 }
proxy.object.string = "World";
console.log(proxy.object.string); // "World"
I published a library on GitHub that does this as well. It will also report to a callback function what modifications have taken place along with their full path.
Michal's answer is good, but it creates a new Proxy every time a nested object is accessed. Depending on your usage, that could lead to a very large memory overhead.
I have also created a library type function for observing updates on deeply nested proxy objects (I created it for use as a one-way bound data model). Compared to Elliot's library it's slightly easier to understand at < 100 lines. Moreover, I think Elliot's worry about new Proxy objects being made is a premature optimisation, so I kept that feature to make it simpler to reason about the function of the code.
observable-model.js
let ObservableModel = (function () {
/*
* observableValidation: This is a validation handler for the observable model construct.
* It allows objects to be created with deeply nested object hierarchies, each of which
* is a proxy implementing the observable validator. It uses markers to track the path an update to the object takes
* <path> is an array of values representing the breadcrumb trail of object properties up until the final get/set action
* <rootTarget> the earliest property in this <path> which contained an observers array *
*/
let observableValidation = {
get(target, prop) {
this.updateMarkers(target, prop);
if (target[prop] && typeof target[prop] === 'object') {
target[prop] = new Proxy(target[prop], observableValidation);
return new Proxy(target[prop], observableValidation);
} else {
return target[prop];
}
},
set(target, prop, value) {
this.updateMarkers(target, prop);
// user is attempting to update an entire observable field
// so maintain the observers array
target[prop] = this.path.length === 1 && prop !== 'length'
? Object.assign(value, { observers: target[prop].observers })
: value;
// don't send events on observer changes / magic length changes
if(!this.path.includes('observers') && prop !== 'length') {
this.rootTarget.observers.forEach(o => o.onEvent(this.path, value));
}
// reset the markers
this.rootTarget = undefined;
this.path.length = 0;
return true;
},
updateMarkers(target, prop) {
this.path.push(prop);
this.rootTarget = this.path.length === 1 && prop !== 'length'
? target[prop]
: target;
},
path: [],
set rootTarget(target) {
if(typeof target === 'undefined') {
this._rootTarget = undefined;
}
else if(!this._rootTarget && target.hasOwnProperty('observers')) {
this._rootTarget = Object.assign({}, target);
}
},
get rootTarget() {
return this._rootTarget;
}
};
/*
* create: Creates an object with keys governed by the fields array
* The value at each key is an object with an observers array
*/
function create(fields) {
let observableModel = {};
fields.forEach(f => observableModel[f] = { observers: [] });
return new Proxy(observableModel, observableValidation);
}
return {create: create};
})();
It's then trivial to create an observable model and register observers:
app.js
// give the create function a list of fields to convert into observables
let model = ObservableModel.create([
'profile',
'availableGames'
]);
// define the observer handler. it must have an onEvent function
// to handle events sent by the model
let profileObserver = {
onEvent(field, newValue) {
console.log(
'handling profile event: \n\tfield: %s\n\tnewValue: %s',
JSON.stringify(field),
JSON.stringify(newValue));
}
};
// register the observer on the profile field of the model
model.profile.observers.push(profileObserver);
// make a change to profile - the observer prints:
// handling profile event:
// field: ["profile"]
// newValue: {"name":{"first":"foo","last":"bar"},"observers":[{}
// ]}
model.profile = {name: {first: 'foo', last: 'bar'}};
// make a change to available games - no listeners are registered, so all
// it does is change the model, nothing else
model.availableGames['1234'] = {players: []};
Hope this is useful!
I wrote a function based on Michał Perłakowski code. I added access to the path of property in the set/get functions. Also, I added types.
const createHander = <T>(path: string[] = []) => ({
get: (target: T, key: keyof T): any => {
if (key == 'isProxy') return true;
if (typeof target[key] === 'object' && target[key] != null)
return new Proxy(
target[key],
createHander<any>([...path, key as string])
);
return target[key];
},
set: (target: T, key: keyof T, value: any) => {
console.log(`Setting ${[...path, key]} to: `, value);
target[key] = value;
return true;
}
});
const proxy = new Proxy(obj ,createHander<ObjectType>());

Convert javascript object camelCase keys to underscore_case

I want to be able to pass any javascript object containing camelCase keys through a method and return an object with underscore_case keys, mapped to the same values.
So, I have this:
var camelCased = {firstName: 'Jon', lastName: 'Smith'}
And I want a method to output this:
{first_name: 'Jon', last_name: 'Jon'}
What's the fastest way to write a method that takes any object with any number of key/value pairs and outputs the underscore_cased version of that object?
Here's your function to convert camelCase to underscored text (see the jsfiddle):
function camelToUnderscore(key) {
return key.replace( /([A-Z])/g, "_$1").toLowerCase();
}
console.log(camelToUnderscore('helloWorldWhatsUp'));
Then you can just loop (see the other jsfiddle):
var original = {
whatsUp: 'you',
myName: 'is Bob'
},
newObject = {};
function camelToUnderscore(key) {
return key.replace( /([A-Z])/g, "_$1" ).toLowerCase();
}
for(var camel in original) {
newObject[camelToUnderscore(camel)] = original[camel];
}
console.log(newObject);
If you have an object with children objects, you can use recursion and change all properties:
function camelCaseKeysToUnderscore(obj){
if (typeof(obj) != "object") return obj;
for(var oldName in obj){
// Camel to underscore
newName = oldName.replace(/([A-Z])/g, function($1){return "_"+$1.toLowerCase();});
// Only process if names are different
if (newName != oldName) {
// Check for the old property name to avoid a ReferenceError in strict mode.
if (obj.hasOwnProperty(oldName)) {
obj[newName] = obj[oldName];
delete obj[oldName];
}
}
// Recursion
if (typeof(obj[newName]) == "object") {
obj[newName] = camelCaseKeysToUnderscore(obj[newName]);
}
}
return obj;
}
So, with an object like this:
var obj = {
userId: 20,
userName: "John",
subItem: {
paramOne: "test",
paramTwo: false
}
}
newobj = camelCaseKeysToUnderscore(obj);
You'll get:
{
user_id: 20,
user_name: "John",
sub_item: {
param_one: "test",
param_two: false
}
}
es6 node solution below. to use, require this file, then pass object you want converted into the function and it will return the camelcased / snakecased copy of the object.
const snakecase = require('lodash.snakecase');
const traverseObj = (obj) => {
const traverseArr = (arr) => {
arr.forEach((v) => {
if (v) {
if (v.constructor === Object) {
traverseObj(v);
} else if (v.constructor === Array) {
traverseArr(v);
}
}
});
};
Object.keys(obj).forEach((k) => {
if (obj[k]) {
if (obj[k].constructor === Object) {
traverseObj(obj[k]);
} else if (obj[k].constructor === Array) {
traverseArr(obj[k]);
}
}
const sck = snakecase(k);
if (sck !== k) {
obj[sck] = obj[k];
delete obj[k];
}
});
};
module.exports = (o) => {
if (!o || o.constructor !== Object) return o;
const obj = Object.assign({}, o);
traverseObj(obj);
return obj;
};
Came across this exact problem when working between JS & python/ruby objects. I noticed the accepted solution is using for in which will throw eslint error messages at you ref: https://github.com/airbnb/javascript/issues/851 which alludes to rule 11.1 re: use of pure functions rather than side effects ref:https://github.com/airbnb/javascript#iterators--nope
To that end, figured i'd share the below which passed the said rules.
import { snakeCase } from 'lodash'; // or use the regex in the accepted answer
camelCase = obj => {
const camelCaseObj = {};
for (const key of Object.keys(obj)){
if (Object.prototype.hasOwnProperty.call(obj, key)) {
camelCaseObj[snakeCase(key)] = obj[key];
}
}
return camelCaseObj;
};
Marcos Dimitrio posted above with his conversion function, which works but is not a pure function as it changes the original object passed in, which may be an undesireable side effect. Below returns a new object that doesn't modify the original.
export function camelCaseKeysToSnake(obj){
if (typeof(obj) != "object") return obj;
let newObj = {...obj}
for(var oldName in newObj){
// Camel to underscore
let newName = oldName.replace(/([A-Z])/g, function($1){return "_"+$1.toLowerCase();});
// Only process if names are different
if (newName != oldName) {
// Check for the old property name to avoid a ReferenceError in strict mode.
if (newObj.hasOwnProperty(oldName)) {
newObj[newName] = newObj[oldName];
delete newObj[oldName];
}
}
// Recursion
if (typeof(newObj[newName]) == "object") {
newObj[newName] = camelCaseKeysToSnake(newObj[newName]);
}
}
return newObj;
}
this library does exactly that: case-converter
It converts snake_case to camelCase and vice versa
const caseConverter = require('case-converter')
const snakeCase = {
an_object: {
nested_string: 'nested content',
nested_array: [{ an_object: 'something' }]
},
an_array: [
{ zero_index: 0 },
{ one_index: 1 }
]
}
const camelCase = caseConverter.toCamelCase(snakeCase);
console.log(camelCase)
/*
{
anObject: {
nestedString: 'nested content',
nestedArray: [{ anObject: 'something' }]
},
anArray: [
{ zeroIndex: 0 },
{ oneIndex: 1 }
]
}
*/
following what's suggested above, case-converter library is deprectaed, use snakecase-keys instead -
https://github.com/bendrucker/snakecase-keys
supports also nested objects & exclusions.
Any of the above snakeCase functions can be used in a reduce function as well:
const snakeCase = [lodash / case-converter / homebrew]
const snakeCasedObject = Object.keys(obj).reduce((result, key) => ({
...result,
[snakeCase(key)]: obj[key],
}), {})
jsfiddle
//This function will rename one property to another in place
Object.prototype.renameProperty = function (oldName, newName) {
// Do nothing if the names are the same
if (oldName == newName) {
return this;
}
// Check for the old property name to avoid a ReferenceError in strict mode.
if (this.hasOwnProperty(oldName)) {
this[newName] = this[oldName];
delete this[oldName];
}
return this;
};
//rename this to something like camelCase to snakeCase
function doStuff(object) {
for (var property in object) {
if (object.hasOwnProperty(property)) {
var r = property.replace(/([A-Z])/, function(v) { return '_' + v.toLowerCase(); });
console.log(object);
object.renameProperty(property, r);
console.log(object);
}
}
}
//example object
var camelCased = {firstName: 'Jon', lastName: 'Smith'};
doStuff(camelCased);
Note: remember to remove any and all console.logs as they aren't needed for production code

Categories