Can you declare a object literal type that allows unknown properties in typescript?
GenericsTypescripttypescript2.0Object LiteralGenerics Problem Overview
Essentially I want to ensure that an object argument contains all of the required properties, but can contain any other properties it wants. For example:
function foo(bar: { baz: number }) : number {
return bar.baz;
}
foo({ baz: 1, other: 2 });
But this results in:
Object literal may only specify known properties, and 'other' does not exist in type '{ baz: number; }'.
Generics Solutions
Solution 1 - Generics
Yes, you can. Try this:
interface IBaz {
baz: number;
[key: string]: any;
}
function foo(bar: IBaz) : number {
return bar.baz;
}
foo({ baz: 1, other: 2 });
Solution 2 - Generics
Well, i hate answering my own questions, but the other answers inspired a little thought... This works:
function foo<T extends { baz: number }>(bar: T): void {
console.log(bar.baz);
}
foo({baz: 1, other: 2});
Solution 3 - Generics
If the known fields are coming from a generic type the way to allow wildcards is with T & {[key: string]: unknown}
, any fields that are known must fit with the type's constraints and other fields are allowed (and considered type unknown
)
Here is a sample:
type WithWildcards<T> = T & { [key: string]: unknown };
function test(foo: WithWildcards<{baz: number}>) {}
test({ baz: 1 }); // works
test({ baz: 1, other: 4 }); // works
test({ baz: '', other: 4 }); // fails since baz isn't a number
Then if you have a generic type T
you can allow wildcard fields with WithWildCards<T>
Note that extra properties are not marked as errors if the object is coming from anything other than an object literal, TS is just telling you that with the typing putting that property in the literal is extraneous.
Here are some other cases where extra properties are and aren't allowed
interface Foos{
a?: string
b?: string
}
type WithWildcards<T> = T & { [key: string]: unknown };
declare function acceptFoos(foo: Foos): void;
declare function acceptWild(foo: WithWildcards<Foos>):void
acceptFoos( {a:'', other: ''}) // ERROR since the property is extraneous
const data = {a:'', other: ''}
acceptFoos( data) // allowed since it is compatible, typescript doesn't force you to remove the properties
const notCompat = {other: ''}
acceptFoos(notCompat) //ERROR: Type '{ other: string; }' has no properties in common with type 'Foos'
acceptFoos({a:'', ...notCompat}) // this is also allowed
acceptWild(notCompat) // allowed
Solution 4 - Generics
If you want to allow unknown properties for the entire object, you can use Record
function doStuff(payload: Record<string|number, unknown>): Record<string|number, unknown> {
return { anyProp: 'anyValue' }
}
Solution 5 - Generics
This can be most easily accomplished by defining a type definition for the the function parameter. Here's an example with inline type definition:
function foo(bar: { baz: number }) : number {
return bar.baz;
}
const obj = { baz: 1, other: 2 };
foo(obj);
Solution 6 - Generics
When working with unknown
type you can use the concept of narrowing
to check out for the type you expect to get from the value you are going through and manipulate the values as per your need
E.g
const messages: string []=Object.values(obj).map(val: unknown=>typeof obj==='object'?obj!['message']:obj)