Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can you declare a object literal type that allows unknown properties in typescript?

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; }'.
like image 836
Lucas Avatar asked Mar 10 '17 17:03

Lucas


People also ask

What is unknown type in TypeScript?

unknown is the type-safe counterpart of any . Anything is assignable to unknown , but unknown isn't assignable to anything but itself and any without a type assertion or a control flow based narrowing. Likewise, no operations are permitted on an unknown without first asserting or narrowing to a more specific type.

Is there an object type in TypeScript?

In TypeScript, object is the type of all non-primitive values (primitive values are undefined , null , booleans, numbers, bigints, strings). With this type, we can't access any properties of a value.

What is any type and unknown type in TypeScript and when to use it?

In Typescript, any value can be assigned to unknown, but without a type assertion, unknown can't be assigned to anything but itself and any. Similarly, no operations on an unknown are allowed without first asserting or restricting it down to a more precise type.


4 Answers

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 }); 
like image 129
Hoang Hiep Avatar answered Nov 08 '22 22:11

Hoang Hiep


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}); 
like image 34
Lucas Avatar answered Nov 08 '22 22:11

Lucas


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
like image 28
Tadhg McDonald-Jensen Avatar answered Nov 08 '22 22:11

Tadhg McDonald-Jensen


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' }
}

like image 26
MartinsOnuoha Avatar answered Nov 08 '22 23:11

MartinsOnuoha