javascript print error when calling missing object property - javascript

I would like to write a proxy object to automatically print errors when calling some property in original object which is not found.
const proxyObjectFn = () => {
const _obj = Object.assign({}, originalObject);
const get = (key) => {
const value = _obj[key];
if (value === undefined) {
console.error(`${key} not found`);
}
return value;
};
return {
get,
};
};
const proxyObject = proxyObjectFn();
export default proxyObject;
// caller
proxyObject.get('someProperty')
This works, but is there any elegant way so that I can call through proxyObject.someProperty instead of proxyObject.get('someProperty')?
Update
Let me make it more specific. Actually I am writing a translation object.
Original object may be from json, like { "HELLO_KEY": "Hello World" }. I am to call like { label: _t.SOME_I18N_KEY } in UI display code, assuming _t is the proxy object above. I can print the warning to tell me there is missing translation.

You can use the Proxy object:
const handler = {
get: (obj, prop) => {
if(!obj.hasOwnProperty(prop)) console.error(`${prop} not found`);
return obj[prop];
}
};
const _t = new Proxy({ "HELLO_KEY": "Hello World" }, handler);
console.log(_t.HELLO_KEY);
console.log(_t.SOME_NONEXISTENT_KEY);

Related

Javascript use decorator to change static class field value?

Is it possible for a JS field decorator to change its value?
A simplified use case would be something like this:
const addItem = (newValue) => {
return function (target) {
target.value.push(newValue);
};
};
class Test {
#addItem(4)
static values = [1,2,3];
}
const test = new Test();
console.log(test.constructor.values) // [1,2,3,4]
Using the following experimental decorators:
'#babel/plugin-proposal-decorators',
{
version: '2018-09',
decoratorsBeforeExport: true,
},
End goal is to make a decorator to inject tailwind style sheets into a lit elements static styles. Currently using a mixin for this but just doing this for fun and to learn whats possible with decorators.
Update to Barmars comments
When trying to return a value from the inner function, I end up getting an error:
export const addItem = (value) => {
return function (target) {
return [value];
};
};
Uncaught TypeError: An element descriptor's .kind property must be either "method" or "field", but a decorator created an element descriptor with .kind "undefined"
Looking at the documentation, the variables getting passed to each of these functions doesn't seem to match either.
function logged(value, { kind, name }) {
if (kind === "field") {
return function (initialValue) {
console.log(`initializing ${name} with value ${initialValue}`);
return initialValue;
};
}
}
When running that example, the 2nd parameter to logged() is undefined. "initialValue" also is an object, not the value:
Object { kind: "field", key: "styles", placement: "own", descriptor: {…}, initializer: value(), … }
Nicolo Ribaudo was able to help me over on Babel's discussions. The correct way to do this is to use the initializer function:
const addItem = (newValue) => {
return function (target) {
const { initializer } = target;
target.initializer = function () {
return [
...initializer.call(this),
newValue,
];
};
};
};
class Test {
#addItem(4)
static values = [1,2,3];
}
const test = new Test();
console.log(test.constructor.values) // [1,2,3,4]

How to check and access a function from another object using JavaScript Proxy?

I am practicing JavaScript Proxies and want to access a method from another object but with an empty Proxy object:
const data = {
hello: {
log() {
return 'hello log'
},
},
hi: {
log() {
return 'hi log'
},
},
}
const blankObject = {} // I can just pass the data object but I want an empty one
const proxy = new Proxy(blankObject, {
get(target, key) {
const v = target[key]
if (typeof v === 'function') {
// if data.hello.log() or data.hi.log()
return function() {
return data[key]['WHAT_TO_DO']// access data.hello.log or data.hi.log() here?
}
}
return v
}
})
proxy.hello.log() // hello log;
Basically I'm trying to check if that method property exist in another object. I just want to tell the proxy to get the value from another object without passing it into the constructor.
I don't understand why you want to use a different object than the object you are proxying. It makes little sense to proxy an empty object instead.
Secondly, if you are going to access the hello property of the proxy object, then realise that this hello value -- as found in the data object -- is not proxied, so it is not in this proxy that you should check whether the property is a function. At this point the log property is not accessed yet. It's only about the hello access that the proxy is aware.
But you can divert the path to the data object when hello is accessed, by verifying it is indeed a property of the data object. So:
const data = {
hello: {
log() {
return 'hello log';
},
},
hi: {
log() {
return 'hi log'
},
},
}
const blankObject = {};
const proxy = new Proxy(blankObject, {
get(target, key) {
if (key in data) return data[key]; // <---
return target[key]; // default
}
});
console.log(proxy.hello.log()); // hello log;
still trying to figure out what in the world you are trying to accomplish here. you're trying to add a method from one object, to another object?
const blankObject = { hello: data.hello };
though this feels a bit hacky. would probably be best to just pass the whole data object into the constructor, so that the blank object has a reference to it, and can use its methods whenever it needs to.
Doesn't matters, using a test, no needs to over-engineer!
let data = {
hello: {
log() {
return 'hello log'
},
},
hi: {
log() {
return 'hi log'
},
},
}
let blankObject = {} // I can just pass the data object but I want an empty one
// We test if the object has a given property and return accordingly
console.log(
(blankObject["hello"]) ? blankObject.hello.log() : data.hello.log()
)

How to dynamically ignore certain fields in a javascript object when converting it to json?

Currently, I am using the toJSON() object on a method to ignore any fields that are underscored e.g.
toJSON() {
const properties = Object.getOwnPropertyNames(this);
const publicProperties = properties.filter(property => {
return property.charAt(0) !== '_'
})
const json = publicProperties.reduce((obj, key) => {
obj[key] = this[key]
return obj
}, {})
return json
}
This was fine. But I have the concept of roles in my API and I would like to return private fields if the user is an admin.
This led me to the idea of doing:
toJSON(role='user') {
const properties = Object.getOwnPropertyNames(this);
const publicProperties = properties.filter(property => {
return property.charAt(0) !== '_' || role === 'admin'
})
const json = publicProperties.reduce((obj, key) => {
key = key.charAt(0) === '_' ? key.substring(1) : key
obj[key] = this[key]
return obj
}, {})
return json
}
But then the issue becomes how do I get the role argument passed to the toJSON() method, especially when JSON.stringify() is being called and the method calling JSON.stringify() I might not have access to.
I could set on my object a role property before returning a json response e.g.
const getCurrentProject = async (c) => {
const project = await projectService.getCurrentProject(c.get('projectId'));
project._role = c.get('payload').role
return c.json(project, httpStatus.OK);
};
But that doesn't seem ideal and then there are more issues when JSON.stringify() is called on an array of object as I would have to set that for each object.
My next idea was to use my own json response function that would have a replacer function for JSON.stringify()
const jsonResponse = (context, object, status) => {
const role = c.get('payload').role
const body = JSON.stringify(object, (key, value) => {
// function to set private vars to null based on role
})
headers = 'application/json; charset=UTF-8'
return c.body(body, status, headers)
}
The issue with this is that the replacer function will just set them to null and not hide them and I can't just blindly remove keys with null values as I might need them. I could set them to 'remove' or another placeholder and remove them after but again, it doesn't seem like the best way.
So currently I am confused on what I should do. Is there a way to globally override JSON.stringify() and add the role parameter as an argument, is there a better approach I am missing? Or should I just stick to the _role property and for lists of objects set it for each one.
Thanks!
You can use a replacer function. If you return a Function, Symbol, or undefined, the property is not included in the output. See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#the_replacer_parameter
I would use Object.entries, and then Array.filter the keys you want, finally Object.fromEntries to get back to an object.
example ->
const obj = {
_adminOnly: 'Lets have a party!',
name: 'bob',
age: 22,
_hideme: 'Hide unless admin',
toJSON: function (role='user') {
return Object.fromEntries(
Object.entries(this).
filter(([k]) => {
if (k === 'toJSON') return false;
return role === 'admin'
? true
: k.charAt(0) !== '_'
}
)
);
}
}
console.log(obj.toJSON());
console.log(obj.toJSON('admin'));

Is there a way to use an Accessor Function to define a property of an object in JS?

I have two inputs:
data = {"year": 2021}
valueAccessor = (d) => d["year"];
These two input can be dynamic, so I do not know what will be in the object or function.
I want create a new object, and set values to that object using the Accessor function. Is there a way to do this?
output = {}
output = definePropertyUsingFunction(output, valueAccessor, newValue)
Short answer is "no". But...
If you're in some sort of controlled learning environment, there are some things you could try.
If you know for a fact that:
Your getter function is pure (no side effects), and
Your getter function always accesses 1 property,
you could
Use a Proxy that tracks property access
Make a call to the getter to trigger it (again, if this is a pure function it should not have any side effects)
Write a value to the logged property
Here's a proof of concept:
const data = { year: 2000, month: 1, day: 28 };
const getYear = obj => obj.year;
const getMonth = obj => obj.month;
const toString = obj => `${obj.year}/${obj.month}/${obj.day}`;
const writeViaSimpleGetter = (obj, value, getter) => {
let propName = null;
const proxy = new Proxy(obj, {
get: (target, key) => {
if (propName) throw "Multiple properties accessed";
propName = key;
}
});
// Call the getter
getter(proxy);
if (!propName) throw "No property accessed";
return { ...obj, [propName]: value };
}
// Simple getters work:
console.log(
writeViaSimpleGetter(data, 2021, getYear)
)
console.log(
writeViaSimpleGetter(data, 12, getMonth)
)
// This throws:
try {
console.log(
writeViaSimpleGetter(data, "oops", toString)
)
} catch(e) { console.log("ERROR:", e) }
// This also throws:
try {
console.log(
writeViaSimpleGetter(data, "oops", () => {})
)
} catch(e) { console.log("ERROR:", e) }

Javascript: How to mock unknown object structure properties

I'm trying to create a dynamic mock for a javascript object.
I have a react component that receives an object with a dynamic structure (meaning i don't know which properties exist on it beforehand) and each property is either a json with any level of nesting or a function that returns a string.
example object:
const originalObj = {
prop1: {
innerProp2: {
moreProps: () => "some string",
differentProp: () => "some string 2",
anotherProp: () => "something else",
},
funcAtLevel2: () => "jlk"
},
funcAtThisLevel: () => "this returns string too"
}
The component renders the strings that it receives from this object and i'm testing other functionality of the component.
in order for the component to render i need the accessors to work, so if my component looks like this:
function MyComp(props: { content: any }) {
const { content } = props;
const moreProps = content.prop1.innerProp2.moreProps(); // moreProps = "some hard code string"
const differentProps = content.prop1.innerProp2.differentProps(); // differentProps = "some hard code string"
const anotherProp = content.prop1.innerProp2.anotherProp(); // anotherProp = "some hard code string"
const funcAtLevel2 = content.prop1.funcAtLevel2(); // funcAtLevel2 = "some hard code string"
const funcAtThisLevel = content.funcAtThisLevel(); // funcAtThisLevel = "some hard code string"
// other code...
return (
<div>
<span>{moreProps}</span>
<span>{differentProps}</span>
{/* more DOM structure here */}
</div>
);
}
I need the accessors to work dynamically.
I managed to get closer with this:
const handler = {
get(target, prop, receiver) {
return new Proxy({ [prop]: () => "some hard code string" }, handler);
},
};
const CompToRender = <MyComp content={new Proxy({}, handler)} />;
However I couldn't manage to return the last accessed property as method. How can I implement it?
EDIT: what i'm looking for is the magical object that i can pass to the rendered component instead of the one that is passed in production which will return some hard coded string so the component won't fail to render (the string value is of none importance).
const magicalObj = // how to define this without defining each property explicitely? the "content" object can be very large (over 1000 keys)
const CompToRender = <MyComp content={magicalObj} />;
function MyComp(props: { content: any }) {
const { content } = props;
// so moreProps and the variables below will receive some string just so the component will render
// without some accessor the properties will be undefined and it will throw an error
const moreProps = content.prop1.innerProp2.moreProps(); // moreProps = "some hard code string"
const differentProps = content.prop1.innerProp2.differentProps(); // differentProps = "some hard code string"
const anotherProp = content.prop1.innerProp2.anotherProp(); // anotherProp = "some hard code string"
const funcAtLevel2 = content.prop1.funcAtLevel2(); // funcAtLevel2 = "some hard code string"
const funcAtThisLevel = content.funcAtThisLevel(); // funcAtThisLevel = "some hard code string"
// other code...
return (
<div>
<span>{moreProps}</span>
<span>{differentProps}</span>
{/* more DOM structure here */}
</div>
);
}
Your code will work with this handler:
const handler = {
get() {
const f = () => 'value returned from function';
Object.setPrototypeOf(f, new Proxy({}, handler));
return f;
},
};
That is, if you define const content = new Proxy({}, handler);, then content.a.b.c.whatever() will return 'value returned from function'.
The key is returning a function that "extends" a Proxy using setPrototypeOf (I took the idea from invokable)

Categories