mpen
mpen

Reputation: 283313

How to define a JSON-compatible parameter to avoid error "Index signature is missing"?

I've got a function defined as:

export function useSubmitHandler(url: string, data: Json): [FormEventHandler<HTMLFormElement>, boolean] {}

Where Json is:

type JsonPrimitive = string | number | boolean | null | undefined
interface JsonMap extends Record<string, JsonPrimitive | JsonArray | JsonMap> {}
interface JsonArray extends Array<JsonPrimitive | JsonArray | JsonMap> {}
export type Json = JsonPrimitive | JsonMap | JsonArray

If I try to call it with an arbitrary interface though, I get an error:

TS2345: Argument of type 'Fee' is not assignable to parameter of type 'Json'.   
Type 'Fee' is not assignable to type 'JsonMap'.     
Index signature is missing in type 'Fee'

enter image description here

But if I call it with that same object but spread like {...store.data} then the error goes away.

How can I type useSubmitHandler properly so that it will accept any object that is JSON stringifyable?

I think the Json type is correct, but it needs something more to allow passing arbitrary types into the function.


Fee is:

interface Fee {
    id: number|null;
    name: string;
    type: string;
    amount: string;
    default: number;
    company_id: number;
    deleted_at?: any;
    active: number;
    fee_or_discount: string;
}

Of course, I'd like this to work with any type.

Upvotes: 4

Views: 1550

Answers (2)

Christopher Peisert
Christopher Peisert

Reputation: 24194

Option 1: Redefine Json type

type JsonPrimitive = string | number | boolean | null;
type JsonMap = {
    [key: string]: JsonPrimitive | JsonMap | JsonArray;
}
type JsonArray = Array<JsonPrimitive | JsonMap | JsonArray>;
type Json = JsonPrimitive | JsonMap | JsonArray;

Option 2: Add an index signature to the Fee interface

interface Fee {
    [property: string]: any;
    id: number|null;
    name: string;
    type: string;
    amount: string;
    default: number;
    company_id: number;
    deleted_at?: any;
    active: number;
    fee_or_discount: string;
}

Option 3: Add an inline type assertion for the index signature, such as:

useSubmitHandler(Router.route('fees.store')!, store.data as {[property: string]: any})

See also

TypeScript GitHub issue Please provide a json basic type #1897

Upvotes: 4

Linda Paiste
Linda Paiste

Reputation: 42288

One possible way to handle this is to wrap store.data in a function which applies an index signature.

const indexed = <T extends {}>(obj: T): T & {[key: string]: never} => obj;

This function returns the same object that it was given but adds additional typescript information. We keep all of the values of the previously known type T intact, but add an index signature {[key: string]: never} which tells typescript that any keys other than those in the type cannot be anything other than undefined.

You can call your handler like this

useSubmitHandler(url, indexed(store.data))

Playground Link

The other option is to change your definition of Json such that it doesn't have the index signature, which comes from using Record. But I'm assuming that would be less desirable since (I think) you would have to broaden what's allowed.

Upvotes: 0

Related Questions