proxy handler with deep nested objects - javascript

I want to make a proxy handler for any composite object. For example, an object like this (I want to handle function call):
let = obj {
a: {
b: {
c: callback => setTimeout(() => callback(null, 'hello'), 2000)
}
},
aa: {
bb: (callback, x, y) => setTimeout(() => callback(null, x + y), 40000)
}
};
I made a simple handler, but it doesn't work with nested properties:
var handler = {
get: function(target, name) {
return typeof target[name] == 'function' ? 'function call' : target[name];
}
}
I can a make handler to wrap each property in proxy. It works, but I think it is not the best way:
var handler = {
get: function(target, name) {
if (typeof target[name] == 'function') {
return 'function call';
}
return typeof target[name] == 'object' ? new Proxy(target[name], handler) : target[name];
}
}
Is there more efficient way to solve this problem?
var obj = {
a: {
b: {
c: callback => setTimeout(() => callback(null, 'hello'), 2000)
}
},
aa: {
bb: (callback, x, y) => setTimeout(() => callback(null, x + y), 40000)
}
};
var handler = {
get: function(target, name) {
if (typeof target[name] == 'function') {
return 'function call';
}
return typeof target[name] == 'object' ? new Proxy(target[name], handler) : target[name];
}
}
var p = new Proxy(obj, handler);
p.a.b.c

Related

Why object is not proxied like an argument in function

i have an interesting example of code that's not working like i'm expected.
I really dont understand why my obj wouldnt proxy.
I'm expect that obj ill proxy via link, but it's not. Can anyone explain how it's works and what I don't understand? Thank you!
let obj = {
foo: "123"
};
function test(fn, object) {
object = new Proxy(object, {
get(target, key) {
console.log('get');
return target[key];
},
set(target, key, value) {
console.log('set');
target[key] = value;
return true;
}
});
fn();
}
test(() => {
obj.foo = "helloworld";
console.log(obj.foo);
}, obj);
UPD: i want to assign object to proxy variable ONLY through arguments can i?
Either assign the proxy to obj
let obj = {
foo: "123"
};
function test(fn, object) {
obj = new Proxy(object, {
get(target, key) {
console.log('get');
return target[key];
},
set(target, key, value) {
console.log('set');
target[key] = value;
return true;
}
});
fn();
}
test(() => {
obj.foo = "helloworld";
console.log(obj.foo);
}, obj);
Or pass the proxy as an argument
let obj = {
foo: "123"
};
function test(fn, object) {
const o = new Proxy(object, {
get(target, key) {
console.log('get');
return target[key];
},
set(target, key, value) {
console.log('set');
target[key] = value;
return true;
}
});
fn(o);
}
test((o) => {
o.foo = "helloworld";
console.log(o.foo);
}, obj);

search array within a object in javascript / typescript

I want to find the first array in an object in Javascript. However my functions cause an error:
el is undefined
el contains the object that looks like this:
data = {
foo: {
bar: [{object a},
{object b},
{object c}
]
}
}
let el = data;
el = this.searchArray(el);
el.forEach(element => {
console.log(element);
let siblingData = this.injectFilterParameters(element);
});
//here is unimportant code
searchArray(obj) {
if (Array.isArray(obj)) {
return obj;
} else {
Object.keys(obj).forEach(key => {
if (Array.isArray(obj[key])) {
return obj[key];
} else {
return this.searchArray(obj[key]);
}
})
}
}
const data = {
foo: {
bar: [
{ object: 'a' },
{ object: 'b' },
{ object: 'c' }
]
}
}
console.log(findArray(data))
function findArray(data: unknown): unknown[] | undefined {
if (Array.isArray(data)) {
return data
}
if (typeof data !== 'object') {
return undefined
}
const object = data as Record<string, unknown>
for (const key of Object.keys(object)) {
const value = object[key]
const valueSearchResult = findArray(value)
if (valueSearchResult != undefined) {
return valueSearchResult
}
}
return undefined
}

How to check if an object property is undefined without a for in loop?

Say I have a function,
const doSomething = (a : String, b : Object) => {
const something = 'something';
if (!a) {
throw new Error(
'a is missing'
);
}
for (let prop in b) {
if (b[prop] === undefined) { // erroneously previously if (!b[prop])
throw new Error(
`prop ${prop} in b is missing`
);
}
}
return something;
}
Is there any way to avoid the for..in loop, and somehow check for an undefined property in b without it?
Something like,
const doSomething = (a : String, b : Object) => {
const something = 'something';
if (!a || Object.values(b.includes(undefined))) {
throw new Error(
`${!a ? 'a is missing' : 'b contains undefined'}`
);
}
return something;
}
But ideally I'd like something a little cleaner and to know the actual undefined prop.
You can use Object.entries and Array.some to determine if any property is undefined
function hasUndefinedValue(obj){
return Object.entries(obj).some(e => typeof e[1] === 'undefined');
}
Example below has a few test cases.
function hasUndefinedValue(obj){
return Object.entries(obj).some(e => typeof e[1] === 'undefined');
}
var tests = [
{a:"foo"},
{a:"foo", b: undefined},
{a:"foo",b:[1,2,3]}
];
for(var i = 0;i<tests.length;i++){
console.log(tests[i],hasUndefinedValue(tests[i]));
}
You can use Object.keys().
const obj = {
a: 'val',
b: undefined
};
Object.keys(obj)
.forEach(key => {
if(!obj[key]) {
throw new Error(`prop ${key} has undefined value`)
}
});
--Edit--
const obj = {
a: 'val',
b: false
};
Object.keys(obj)
.forEach(key => {
if(!obj[key]) {
throw new Error(`prop ${key} has undefined value`)
}
});
If only undefined case needs to be handled, then you achieve it by using obj[key] == undefined.

Make the function immutable

I build following method that change the structure of the input objects. It works but I would like to make it immutable.
function formatObj(obj: Object) {
const keys = Object.keys(obj)
keys.forEach((key) => {
const value = obj[key]
if (value === null || value === undefined) {
return
}
else if (key === 'size') {
obj[key] = { value, min: 0, max: 0 }
}
else if (typeof value === 'object') {
formatObj(obj[key])
}
})
}
I tried this but it returns a list of undefined...
function formatObj(obj: Object) {
const keys = Object.keys(obj)
const r = keys.map((key) => {
const value = obj[key]
if (value === null || value === undefined) {
return { key: value }
}
else if (key === 'size') {
return { key: { value, min: 0, max: 0 } }
}
else if (typeof value === 'object') {
return formatObj(obj[key])
}
})
return r
}
Why? How can I solve?
The idiom you're looking for is Object.fromEntries(Object.entries(...).map...):
let mapObject = (obj, fn) => Object.fromEntries(
Object.entries(obj).map(([k, v]) => fn(k, v))
);
//
let test = {
a: 0,
size: 123,
sub: {
b: null,
size: 456
}
}
let transformer = (key, value) => {
if(key === 'size')
return [key, { value, min: 0, max: 0 }];
if(!value || typeof value !== 'object')
return [key, value];
return [key, mapObject(value, transformer)]
}
let test2 = mapObject(test, transformer)
console.log(test2)
One drawback of this is that you have to have an external transformer variable to be able to reapply it. A nicer approach would be to define the transformer inside the mapper and pass it around as an argument. Also, it won't hurt to make it a bit more generic:
function transform(obj, fn) {
function transformer(x) {
if (!x || typeof x !== 'object')
return x;
if (Array.isArray(x))
return x
.map((v, k) => fn(k, v, transformer))
.filter(x => x !== void 0)
.map(x => x[1]);
return Object.fromEntries(
Object.entries(x)
.map(([k, v]) => fn(k, v, transformer))
.filter(x => x !== void 0));
}
return transformer(obj);
}
//
let test = {
a: 0,
size: 123,
removeMe: 123,
renameMe: 456,
sub: {
b: null,
size: 456,
removeMe: 1230,
renameMe: 4560,
sub2: {
foo: ['bar', 'baz', {size: 798}]
}
}
}
let test2 = transform(test, (key, value, transformer) => {
// return undefined to remove a key
if (key === 'removeMe')
return;
// return a new key
if (key === 'renameMe')
return ['renamed!', value];
// return a new value
if (key === 'size')
return [key, {value, min: 0, max: 0}];
// keep transforming
return [key, transformer(value)]
});
document.write('<pre>' + JSON.stringify(test2,0,4))
Object.fromEntries is relatively new, but can be trivially polyfilled.
result of a Array map function is a list. In this case you probably want to use reduce function to make result object again. It would help better to understand if you provided some sample input and output data.
function formatObj(obj: Object) {
const keys = Object.keys(obj)
const r = keys.reduce((resultObj, key) => {
const value = obj[key]
if (value === null || value === undefined) {
return { ...resultObj, [key]: value }
}
else if (key === 'size') {
return { ...resultObj, [key]: { value, min: 0, max: 0 } }
}
else if (typeof value === 'object') {
return { ...resultObj, [key]: formatObj(obj[key]) }
}
}, {})
return r
}

Save an object containing functions to a .js file

Say I have an object like below:
let obj = {
mounted: (value) => {
return "Yeesss" + value;
},
a: "123123",
c: {
d: (key) => {
return key + 1;
}
}
}
I can stringify the object and its functions like so:
let objString = JSON.stringify(obj, (key, value) => {
if(typeof value === "function") return "[Function]" + value;
return value;
});
which will then be like:
{
"mounted":"[Function](value) => {\n return \"Yeesss\" + value;\n }",
"a":"123123",
"c":{
"d":"[Function](key) => {\n return key + 1;\n }"
}
}
I can save this string to a .json file and read it and parse it like below which will give me back the same object:
let obj = JSON.parse(objString, (key, value) => {
if(typeof value === "string" && value.startsWith("[Function]")){
value = value.substring(10);
value = eval(value);
return value;
}
return value;
});
What I want to know is how can I save that object in the beginning with fs in a .js file in the following format:
module.exports = {
mounted: (value) => {
return "Yeesss" + value;
},
a: "123123",
c: {
d: (key) => {
return key + 1;
}
}
}
So that I can require it and use it some other time in the future.
You could do something like this:
Use JSON.stringify() with the function-handling to produce a string.
Search and replace the function-values with an dequoted and unescaped value.
Add module prefix.
let obj = {
mounted: (value) => {
return "Yeesss" + value;
},
a: "123123",
c: {
d: (key) => {
return key + 1;
}
}
};
let json = JSON.stringify(obj, (key, value) => {
if (typeof value === 'function') {
return '[FUNCTION]' + value;
}
return value;
}, ' ');
let moduleJavascript = json.replace(/"(\[FUNCTION])?((?:\\.|[^\\"])*)"(:)?/g, (match, group1, group2, group3) => {
if (group1) return JSON.parse('"' + group2 + '"');
if (group3 && /^\w+$/.test(group2)) return group2 + ':';
return match;
});
moduleJavascript = 'module.exports = ' + moduleJavascript + ';';
console.log(moduleJavascript);
If this gets too complicated, you might consider using a code generation tool. EJS might fit your needs.
Template:
module.exports = {
mounted: (value) => {
return "Yeesss" + value;
},
a: <%= locals.avalue %>,
c: {
d: (key) => {
return key + 1;
}
}
};
And call with:
let ejs = require('ejs'),
fs = require('fs');
fs.readFile('template.ejs', (err, templateText) => {
if (err) throw err;
let moduleJavascript = ejs.render(templateText, {
// Arguments into the template
avalue: "123123"
}, {
// To make <%= %> escape JavaScript instead of HTML
escape: JSON.stringify
});
});

Categories