88

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; }'.
Lucas
  • 14,227
  • 9
  • 74
  • 124

6 Answers6

122

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 });
Hoang Hiep
  • 2,242
  • 5
  • 13
  • 17
  • 2
    I was initially thinking this, but wanted inline... (and for some reason i screwed up the [key: string] part using parens instead of square brackets)... – Lucas Mar 10 '17 at 17:20
  • 2
    You don't have to use an `any` you just have to make sure to include an `| number` (in this example) or you'll get a TS error on the `baz: number;`. Or you can use `unknown` – Jack Feb 02 '22 at 11:02
39

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});
Lucas
  • 14,227
  • 9
  • 74
  • 124
15

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
Tadhg McDonald-Jensen
  • 20,699
  • 5
  • 35
  • 59
9

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

MartinsOnuoha
  • 514
  • 5
  • 9
3

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);
Steven Barnett
  • 3,776
  • 2
  • 21
  • 11
  • 1
    this one seems to result in another msg that is like this: Type ... has no properties in common with type '...'p using typescript 2.3.4 – Jun Aug 08 '18 at 19:33
  • How does this work? If you put the object inline it doesn't work. – 1252748 Mar 08 '21 at 15:13
  • It's an inline type definition, not an inline object. Runs fine in [TS Playground](https://www.typescriptlang.org/play?#code/GYVwdgxgLglg9mABMOcAUAjAhgJwFyIDei2AXgWCALYYCmOiAvgJSIXV0OEBQifiOWlBA4k2HADoyAbm6Nu3CAgDOURHAwArRAF4iJLOUQBGADTqoAC3oEATE1mKVcADa0JLuAHM0KdBs1mZmkgA). – Steven Barnett Mar 08 '21 at 21:28
0

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)

Felix Orinda
  • 593
  • 4
  • 20