Reputation: 433
This is an extension of Typescript: interface that extends a JSON type
Given a JSON type like:
type JSONValue =
| string
| number
| boolean
| null
| JSONValue[]
| {[key: string]: JSONValue}
I'd like to find a way to tell typescript that interfaces that match the JSONType should be auto-casted when passed into functions that accept the JSONType. For example:
interface Foo {
name: 'FOO',
fooProp: string
}
const bar = (foo: Foo) => { return foo }
const wrap = (fn: (...args: JSONValue[]) => JSONValue, args: JSONValue[]) => {
return fn(...args);
}
wrap(bar, {name: 'FOO', fooProp: 'hello'});
This currently fails with:
Argument of type '(foo: Foo) => Foo' is not assignable to parameter of type '(...args: JSONValue[]) => JSONValue'.
Types of parameters 'foo' and 'args' are incompatible.
Type 'JSONValue' is not assignable to type 'Foo'.
Type 'null' is not assignable to type 'Foo'.
even though analytically we know that the passed in foo is a JSON compatible type.
Playground here
Is there a way to get typescript to recognize that this interface is in fact also JSON?
Upvotes: 1
Views: 1038
Reputation: 23885
There are multiple issues within your implementation currently. You are trying to type the function wrap
as if it accepts a function fn
which may have arbitrary JSONValue[]
data passed into it. But then you are passing in bar
, a function which only accepts Foo
. To fix this, we can make wrap
generic over T
where T
represents the parameters of fn
. We would then type args
to be of type T
too, so that we can pass args
to fn
safely.
Notice how I made args
a spread parameter which it was not before. We don't want args
to be a tuple type, as you don't pass a tuple to the function when calling it.
const wrap = <T extends JSONValue[]>(
fn: (...args: T) => JSONValue,
...args: T
) => {
return fn(...args);
}
But this will still not work. Foo
is currently an interface
which don't have implicit index signatures (see #15300). An interface
can not fulfill the {[key: string]: JSONValue}
constraint due to this missing implicit index signature. The only workaround we have without widening the JSONValue
type is to convert Foo
to be a type
.
type Foo = {
name: 'FOO',
fooProp: string
}
const bar = (foo: Foo) => { return foo }
const wrap = <T extends JSONValue[]>(
fn: (...args: T) => JSONValue,
...args: T
) => {
return fn(...args);
}
wrap(bar, { name: 'FOO', fooProp: 'hello'});
Upvotes: 3