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

GenericsTypescripttypescript2.0Object Literal

Generics 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)

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionLucasView Question on Stackoverflow
Solution 1 - GenericsHoang HiepView Answer on Stackoverflow
Solution 2 - GenericsLucasView Answer on Stackoverflow
Solution 3 - GenericsTadhg McDonald-JensenView Answer on Stackoverflow
Solution 4 - GenericsMartinsOnuohaView Answer on Stackoverflow
Solution 5 - GenericsSteven BarnettView Answer on Stackoverflow
Solution 6 - GenericsFelix OrindaView Answer on Stackoverflow