Throw error if config object doesn't contain all required properties - javascript

I have an ES6 class that require a config object.
If a property is missing, I'd like to throw an error.
The solution if found to keep the code short and not add a if(!object.propN)... for every property is to do :
class myClass {
constructor(config) {
if (!config) {
throw new Error("You must provide a config object");
}
this.prop1 = config.prop1;
this.prop2 = config.prop2;
this.prop3 = config.prop3;
this.prop4 = config.prop4;
for (const key in this) {
if (!this[key]) {
throw new Error(`Config object miss the property ${key}`);
}
}
}
}
Is it OK to do this in javascript ?

For configs, we usually use the feature Destructuring assignment to check whether the properties are in an object or not by using the sugar of ES6 (ES2015).
And furthermore, we can also set default values for the configs by this feature.
{prop1, prop2, prop3: path='', prop4='helloworld', ...others} = config
After destructuring assignment has been done, just need to do a check before what we are going to do with the specific config. i.e.
if (!prop1) {
throw new Error(`Config object miss the property ${prop1}`);
}
doSomething(prop1);
But if you still want to check all the configs at the beginning, you can do something like this,
class myClass {
constructor(config) {
if (!config) {
throw new Error("You must provide a config object");
}
// You can assign the values to a new object like "tmpConfig" or just assign them back to "config" for the case config.propX === undefined
({prop1: config.prop1=undefined, prop2: config.prop2=undefined, prop3: config.prop3=undefined, prop4: config.prop4=undefined} = config);
for (const key in config) {
if (!config[key]) {
throw new Error(`Config object miss the property ${key}`);
}
}
Object.assign(this, config);
}
}
Or only using Object.assign() by setting default values,
class myClass {
constructor(config) {
if (!config) {
throw new Error("You must provide a config object");
}
let requiredConfigs = {
prop1: undefined,
prop2: undefined,
prop3: undefined,
prop4: undefined,
}
let tmpConfig = Object.assign(requiredConfigs, config);
for (const key in tmpConfig) {
if (!tmpConfig[key]) {
throw new Error(`Config object miss the property ${key}`);
}
}
Object.assign(this, tmpConfig);
}
}
=> We use Destructuring assignment and Object.assign() a lot for doing setting configs things.

To avoid premature and excessive setting of all config properties into this object you should place the check for an empty property occurrence beforehand. The solution using Array.some function:
class myClass {
constructor(config) {
if (!config || typeof config !== "object") {
throw new Error("You must provide a config object!");
} else if (Object.keys(config).some((k) => !config[k])) { // returns 'true' if any empty property is found
throw new Error(`Config object has empty property(ies)!`);
}
this.prop1 = config.prop1;
this.prop2 = config.prop2;
this.prop3 = config.prop3;
this.prop4 = config.prop4;
}
}

Related

Jest: expect object not to have property

I want to write a test that asserts a given object does not have certain properties.
Say I have a function
function removeFooAndBar(input) {
delete input.foo;
delete input.bar;
return input;
}
Now I want to write a test:
describe('removeFooAndBar', () => {
it('removes properties `foo` and `bar`', () => {
const data = {
foo: 'Foo',
bar: 'Bar',
baz: 'Baz',
};
expect(removeFooAndBar(data))
.toEqual(expect.objectContaining({
baz: 'Baz', // what's left
foo: expect.not.exists() // pseudo
bar: undefined // this doesn't work, and not what I want
}));
});
});
What's the proper way to assert this?
Update after the discussion in the comments
You can use expect.not.objectContaining(). This approach works fine but has one unfortunate edge case: It matches when the property exists, but is undefined or null. To fix this you can explicitly add those values to be included in the check. You need the jest-extended package for the toBeOneOf() matcher.
expect({foo: undefined}).toEqual(expect.not.objectContaining(
{foo: expect.toBeOneOf([expect.anything(), undefined, null])}
));
An example with nested props that fails:
const reallyAnything = expect.toBeOneOf([expect.anything(), undefined, null]);
expect({foo: undefined, bar: {baz: undefined}}).toEqual(
expect.not.objectContaining(
{
foo: reallyAnything,
bar: {baz: reallyAnything},
}
)
);
Original answer
What I'd do is to explicitly check whether the object has a property named bar or foo.
delete data.foo;
delete data.bar;
delete data.nested.property;
expect(data).not.toHaveProperty('bar');
expect(data).not.toHaveProperty('foo');
expect(data.nested).not.toHaveProperty('property');
// or
expect(data).not.toHaveProperty('nested.property');
Or make this less repeating by looping over the properties that will be removed.
const toBeRemoved = ['foo', 'bar'];
toBeRemoved.forEach((prop) => {
delete data[prop];
expect(data).not.toHaveProperty(prop);
});
However, the loop approach isn't too great for possible nested objects.
I believe what you are looking for is expect.not.objectContaining()
expect(data).toEqual(expect.not.objectContaining({foo: 'Foo', bar: 'Bar'}));
expect.not.objectContaining(object) matches any received object that
does not recursively match the expected properties. That is, the
expected object is not a subset of the received object. Therefore, it
matches a received object which contains properties that are not in
the expected object. - Jest Documentation
This answer is a paraphrase of the accepted answer. It is added only because of this exact suggestion to the accepted answer was rejected.
You can explicitly check whether the object has a property named bar or foo.
delete data.foo;
delete data.bar;
expect(data).not.toHaveProperty('bar');
expect(data).not.toHaveProperty('foo');
For nested properties:
delete data.nested.property;
expect(data.nested).not.toHaveProperty('property');
// or
expect(data).not.toHaveProperty('nested.property');
Or make this less repeating by looping over the properties that will be removed.
const toBeRemoved = ['foo', 'bar', 'nested.property'];
toBeRemoved.forEach((prop) => {
expect(data).not.toHaveProperty(prop);
});
However, the loop approach isn't too great for possible nested objects. What you are looking for is expect.not.objectContaining().
expect({baz: 'some value'}).toEqual(expect.not.objectContaining(
{foo: expect.anything()}
));
This approach works fine but has one unfortunate edge case: It matches when the property exists, but is undefined or null:
expect({foo: undefined}).toEqual(expect.not.objectContaining(
{foo: expect.anything()}
));
would also match. To fix this you can explicitly add those values to be included in the check. You need the jest-extended package for the toBeOneOf() matcher.
expect({foo: undefined}).toEqual(expect.not.objectContaining(
{foo: expect.toBeOneOf([expect.anything(), undefined, null])}
));
An example with nested props that, expectedly, fails:
const reallyAnything = expect.toBeOneOf([expect.anything(), undefined, null]);
expect({foo: undefined, bar: {baz: undefined}}).toEqual(
expect.not.objectContaining(
{
foo: reallyAnything,
bar: {baz: reallyAnything},
}
)
);
can you check the result? example?
const result = removeFooAndBar(data)
expect(result.foo).toBeUndefined()
expect(result.bar).toBeUndefined()
you can check initially that the properties were there.
The other option is to extend the expect function: https://jestjs.io/docs/expect#expectextendmatchers
expect.extend({
withUndefinedKeys(received, keys) {
const pass = keys.every((k) => typeof received[k] === 'undefined')
if (pass) {
return {
pass: true,
}
}
return {
message: () => `expected all keys ${keys} to not be defined in ${received}`,
pass: false,
}
},
})
expect({ baz: 'Baz' }).withUndefinedKeys(['bar', 'foo'])
I'd just try because you know the data value to use it:
const data = {...};
const removed = {...data};
delete removed.foo;
delete removed.bar;
expect(removeFooAndBar(data)).toEqual(removed);
Edit 1: Because of Jest's expect.not, try something like:
const removed = removeFooAndBar(data);
expect(removed).not.toHaveProperty('foo');
expect(removed).not.toHaveProperty('bar');
expect(removed).toHaveProperty('baz');
Do not check object.foo === undefined as others suggest.
This will result to true if the object has the property foo set to undefined
eg.
const object = {
foo: undefined
}
Have you tried use the hasOwnProperty function?
this will give you the following results
const object = {foo: ''};
expect(Object.prototype.hasOwnProperty.call(object, 'foo')).toBe(true);
object.foo = undefined;
expect(Object.prototype.hasOwnProperty.call(object, 'foo')).toBe(true);
delete object.foo;
expect(Object.prototype.hasOwnProperty.call(object, 'foo')).toBe(false);
It is possible to check whether an object has selected fields (expect.objectContaining) and in a separate assertion whether it does not have selected fields (expect.not.objectContaining). However, it is not possible, by default, to check these two things in one assertion, at least I have not heard of it yet.
Goal: create a expect.missing matcher similar to standard expect.any or expect.anything which will check if the object does not have the selected field and can be used alongside matchers of existing fields.
My attempts to reach this goal are summarized below, maybe someone will find them useful or be able to improve upon them.
I point out that this is a proof of concept and it is possible that there are many errors and cases that I did not anticipate.
AsymmetricMatchers in their current form lack the ability to check their context, for example, when checking the expect.any condition for a in the object { a: expect.any(String), b: [] }, expect.any knows nothing about the existence of b, the object in which a is a field or even that the expected value is assigned to the key a. For this reason, it is not enough to create only expect.missing but also a custom version of expect.objectContaining, which will be able to provide the context for our expect.missing matcher.
expect.missing draft:
import { AsymmetricMatcher, expect } from 'expect'; // npm i expect
class Missing extends AsymmetricMatcher<void> {
asymmetricMatch(actual: unknown): boolean {
// By default, here we have access only to the actual value of the selected field
return !Object.hasOwn(/* TODO get parent object */, /* TODO get property name */);
}
toString(): string {
return 'Missing';
}
toAsymmetricMatcher(): string {
return this.toString(); // how the selected field will be marked in the diff view
}
}
Somehow the matcher above should be given context: object and property name. We will create a custom expect.objectContaining - let's call it expect.objectContainingOrNot:
class ObjectContainingOrNot extends AsymmetricMatcher<Record<string, unknown>> {
asymmetricMatch(actual: any): boolean {
const { equals } = this.getMatcherContext();
for (const [ property, expected ] of Object.entries(this.sample)) {
const received = actual[ property ];
if (expected instanceof Missing) {
Object.assign(expected, { property, propertyContext: actual });
} // TODO: this would be sufficient if we didn't care about nested values
if (!equals(received, expected)) {
return false;
}
}
return true;
}
toString(): string {
// borrowed from .objectContaining for sake of nice diff printing
return 'ObjectContaining';
}
override getExpectedType(): string {
return 'object';
}
}
Register new matchers to the expect:
expect.missing = () => new Missing();
expect.objectContainingOrNot = (sample: Record<string, unknown>) =>
new ObjectContainingOrNot(sample);
declare module 'expect' {
interface AsymmetricMatchers {
missing(): void;
objectContainingOrNot(expected: Record<string, unknown>): void;
}
}
Full complete code:
import { AsymmetricMatcher, expect } from 'expect'; // npm i expect
class Missing extends AsymmetricMatcher<void> {
property?: string;
propertyContext?: object;
asymmetricMatch(_actual: unknown): boolean {
if (!this.property || !this.propertyContext) {
throw new Error(
'.missing() expects to be used only' +
' inside .objectContainingOrNot(...)'
);
}
return !Object.hasOwn(this.propertyContext, this.property);
}
toString(): string {
return 'Missing';
}
toAsymmetricMatcher(): string {
return this.toString();
}
}
class ObjectContainingOrNot extends AsymmetricMatcher<Record<string, unknown>> {
asymmetricMatch(actual: any): boolean {
const { equals } = this.getMatcherContext();
for (const [ property, expected ] of Object.entries(this.sample)) {
const received = actual[ property ];
assignPropertyCtx(actual, property, expected);
if (!equals(received, expected)) {
return false;
}
}
return true;
}
toString(): string {
return 'ObjectContaining';
}
override getExpectedType(): string {
return 'object';
}
}
// Ugly but is able to assign context for nested `expect.missing`s
function assignPropertyCtx(ctx: any, key: PropertyKey, value: unknown): unknown {
if (value instanceof Missing) {
return Object.assign(value, { property: key, propertyContext: ctx });
}
const newCtx = ctx?.[ key ];
if (Array.isArray(value)) {
return value.forEach((e, i) => assignPropertyCtx(newCtx, i, e));
}
if (value && (typeof value === 'object')) {
return Object.entries(value)
.forEach(([ k, v ]) => assignPropertyCtx(newCtx, k, v));
}
}
expect.objectContainingOrNot = (sample: Record<string, unknown>) =>
new ObjectContainingOrNot(sample);
expect.missing = () => new Missing();
declare module 'expect' {
interface AsymmetricMatchers {
objectContainingOrNot(expected: Record<string, unknown>): void;
missing(): void;
}
}
Usage examples:
expect({ baz: 'Baz' }).toEqual(expect.objectContainingOrNot({
baz: expect.stringMatching(/^baz$/i),
foo: expect.missing(),
})); // pass
expect({ baz: 'Baz', foo: undefined }).toEqual(expect.objectContainingOrNot({
baz: 'Baz',
foo: expect.missing(),
})); // fail
// works with nested!
expect({ arr: [ { id: '1' }, { no: '2' } ] }).toEqual(expect.objectContainingOrNot({
arr: [ { id: '1' }, { no: expect.any(String), id: expect.missing() } ],
})); // pass
When we assume that the field is also missing when it equals undefined ({ a: undefined } => a is missing) then the need to pass the context to expect.missing disappears and the above code can be simplified to:
import { AsymmetricMatcher, expect } from 'expect';
class ObjectContainingOrNot extends AsymmetricMatcher<Record<string, unknown>> {
asymmetricMatch(actual: any): boolean {
const { equals } = this.getMatcherContext();
for (const [ property, expected ] of Object.entries(this.sample)) {
const received = actual[ property ];
if (!equals(received, expected)) {
return false;
}
}
return true;
}
toString(): string {
return `ObjectContaining`;
}
override getExpectedType(): string {
return 'object';
}
}
expect.extend({
missing(actual: unknown) {
// However, it still requires to be used only inside
// expect.objectContainingOrNot.
// expect.objectContaining checks if the objects being compared
// have matching property names which happens before the value
// of those properties reaches this matcher
return {
pass: actual === undefined,
message: () => 'It seems to me that in the' +
' case of this matcher this message is never used',
};
},
});
expect.objectContainingOrNot = (sample: Record<string, unknown>) =>
new ObjectContainingOrNot(sample);
declare module 'expect' {
interface AsymmetricMatchers {
missing(): void;
objectContainingOrNot(expected: Record<string, unknown>): void;
}
}
// With these assumptions, assertion below passes
expect({ baz: 'Baz', foo: undefined }).toEqual(expect.objectContainingOrNot({
baz: 'Baz',
foo: expect.missing(),
}));
It was fun, have a nice day!
I would just try:
expect(removeFooAndBar(data))
.toEqual({
baz: 'Baz'
})

Ensure my function returns mutated object as instanceof the same class typescript?

export const FilterUndefined = <T extends object>(obj: T): T => {
return Object.entries(obj).reduce((acc, [key, value]) => {
return value ? { ...acc, [key]: value } : acc;
}, {}) as T;
};
I'm migrating a database and part of cleaning up the old data structure, some of the values for some keys end up being literaly undefined. The key will still exist and will have the value undefined
I made this function but after modifying a class object with it, it will no longer be an instanceof the same class. How could I make this return an object that's instanceof the same class as the input parameter?
The as T makes TS compiler shut up but that's it.
I've also tried to get the prototype of that object and return new prototype(obj) or return new prototype.constructor(obj)
The console log of the prototype looks like this:
PROTOTYPE TestClass {}
I'm testing using this setup:
it('should return the same type that it receives', () => {
class TestClass {
name: string;
optionalProperty?: any;
}
let testObject = new TestClass();
testObject.name = 'My Name';
testObject.optionalProperty = undefined;
console.log(testObject instanceof TestClass);
testObject = FilterUndefined(testObject);
console.log(testObject instanceof TestClass);
console.log(testObject);
expect(testObject).instanceOf(TestClass);
});
EDIT: JSFiddle: https://jsfiddle.net/3sdg98xt/2/ but copy-pasted from vscode without any issues running it i'm getting an error 'execpted expression, got ';'
This solution will mutate the input object by removing the keys with undefined values.
function removeUndefined <T>(object: T): T {
for (const id in object) {
if (object[id] === undefined) {
delete object[id];
}
}
return object;
}
It seems it works for your test case: Test in typescript playground
Yes, with each iteration of reduce you are returning a new {} which is an instance Object.
So to make the return object of same instace as of argument you should make following changes.
export const FilterUndefined = (obj) => {
return Object.entries(obj).reduce((acc, [key, value]) => {
if (value) {acc[key] = value;}
else {delete acc[key]}
return acc;
}, new obj.constructor);
};
or you can use new obj.__proto__.constructor as per the target of typescript output you are using.
Reply in case you have typescript issues with this code snippet.

Is it possible to construct an object so that it throws an error when its keys are requested?

Imagine I have the following code:
const object = {};
// an error should be thrown
object.property.someMethod();
// an error should be thrown
object.foo;
Is it possible to throw an error when someMethod() is called or if any other non-existing property is called?
I guess that I need to do something with it's prototype, to throw an Error. However, I'm not sure what exactly I should do.
Any help would be appreciated.
Yes, using a Proxy with a handler.get() trap:
const object = new Proxy({}, {
get (target, key) {
throw new Error(`attempted access of nonexistent key \`${key}\``);
}
})
object.foo
If you want to modify an existing object with this behavior, you can use Reflect.has() to check for property existence and determine whether to forward the access using Reflect.get() or throw:
const object = new Proxy({
name: 'Fred',
age: 42,
get foo () { return this.bar }
}, {
get (target, key, receiver) {
if (Reflect.has(target, key)) {
return Reflect.get(target, key, receiver)
} else {
throw new Error(`attempted access of nonexistent key \`${key}\``)
}
}
})
console.log(object.name)
console.log(object.age)
console.log(object.foo)

Calling concat on proxied array throws error

I'm new to the ES6 Proxy object, and am encountering an error I don't understand when attempting to call concat on an array that has been proxied.
Background:
I thought the ES6 Proxy would work perfectly as a way to verify the "purity" of a reducer function in my React/Redux application. I can wrap my state object in a proxy that throws an error if I ever attempt to mutate that object. I'm using something based on the on-change library to do this:
const triggersOnChange = (object, onChange) => {
const handler = {
get (target, property, receiver) {
try {
return new Proxy(target[property], handler)
} catch (err) {
return Reflect.get(target, property, receiver);
}
}
defineProperty (target, property, descriptor) {
onChange()
return Reflect.defineProperty(target, property, descriptor)
}
deleteProperty (target, property) {
onChange()
return Reflect.deleteProperty(target, property)
}
}
return new Proxy(object, handler)
}
And here's an example test of how I intend to use the proxy wrapper:
describe('reducer', () => {
test('it returns an updated state object', () => {
const state = triggersOnChange({ items: [] }, () => {
throw new Error('Oops! You mutated the state object')
})
const action = {
payload: { item: 'foobar' }
}
expect(reducer(state, action)).toEqual({
items: [action.payload.item]
})
})
})
If I implement a "bad" reducer that mutates the state object, my test throws an error as intended:
const reducer = (state, action) => {
state.items.push(action.payload.item) // bad
return state
}
// test throws error "Oops! You mutated the state object"
But when I "purify" my reducer by returning a new state object, I get a different error that I don't quite understand:
const reducer = (state, action) => {
return Object.assign({}, state, {
items: state.items.concat(action.payload.item)
})
}
/*
TypeError: 'get' on proxy: property 'prototype' is a read-only and
non-configurable data property on the proxy target but the proxy did
not return its actual value (expected '[object Array]' but got
'[object Object]')
at Proxy.concat (<anonymous>)
*/
Am I missing something about proxy behavior here? Or is this perhaps an issue with the proxy-chaining behavior that results from my get trap? I initially thought this was a problem with using a proxy within Object.assign, but I encountered the same error when debugging before my reducer's return statement, where I actually use Object.assign. Help!
Edit: Happy to revise this question to make it a little more generic, but I’m not 100% what the issue is so I’ll wait and see if I can get any answers.
Your problem can be replicated with the following code:
var obj = {};
Object.defineProperty(obj, "prop", {
configurable: false,
value: {},
});
var p = new Proxy(obj, {
get(target, property, receiver) {
return new Proxy(Reflect.get(target, property, receiver), {});
},
});
var val = p.prop;
The core of the issue is that objects have invariants that they must stay consistent with, even when accessed via a Proxy object, and in this case you are breaking one of those invariants. If you look at the specification for Proxy's get, it states:
[[Get]] for proxy objects enforces the following invariants:
The value reported for a property must be the same as the value of the corresponding target object property if the target object property is a non-writable, non-configurable own data property.
The value reported for a property must be undefined if the corresponding target object property is a non-configurable own accessor property that has undefined as its [[Get]] attribute.
and in your case, you are not maintaining that first invariant, because even when a property is non-writable and non-configurable, you are returning a wrapping Proxy. The easiest approach would be to ensure that the proper value is returned in that case.
While we're at it, I'll also recommend using typeof explicitly instead of using try/catch so it is clearer.
get(target, property, receiver) {
const desc = Object.getOwnPropertyDescriptor(target, property);
const value = Reflect.get(target, property, receiver);
if (desc && !desc.writable && !desc.configurable) return value;
if (typeof value === "object" && value !== null) return new Proxy(value, handler);
else return value;
},

I don't understand what's going on this syntax javascript [duplicate]

This question already has answers here:
Javascript object bracket notation ({ Navigation } =) on left side of assign
(5 answers)
Closed 5 years ago.
How gonna assign in const { Types, Creators } in the below code I mean what Types gonna hold and what Creators gonna hold.
const { Types, Creators } = createActions({
userRequest: ['username'],
userSuccess: ['avatar'],
userFailure: null
})
var createActions = (function (config, options) {
if (R.isNil(config)) {
throw new Error('an object is required to setup types and creators');
}
if (R.isEmpty(config)) {
throw new Error('empty objects are not supported');
}
return {
Types: convertToTypes(config, options),
Creators: convertToCreators(config, options)
};
})
The syntax is object destructuring assignment. Types and Creators will be defined as the Types and Creators properties returned from the object returned at createActions() call. For example
const {Types, Creators} = (() => {
return {Types:0, Creators:1}
})();
console.log(Types, Creators)
This is called a destructuring assignment, It looks at the returned object and assigns the correct key to the variable. Think of it as shorthand for:
const createActions = (function (config, options) {
if (R.isNil(config)) {
throw new Error('an object is required to setup types and creators');
}
if (R.isEmpty(config)) {
throw new Error('empty objects are not supported');
}
return {
Types: convertToTypes(config, options),
Creators: convertToCreators(config, options)
};
})
let results = createActions({
userRequest: ['username'],
userSuccess: ['avatar'],
userFailure: null
}),
Types = results.Types,
Creators = results.Creators;

Categories