How to type an object with generic keys of different types in typescript

How would you type this objects in typescript ?

I have one special "datetime" key that is a Date, the rest of the keys are numbers. But I don't know in advance which keys will be set on each object. Examples values:

type Metrics = ??????

const example1: Metrics = {
  datetime: new Date(),
  activity: 12.34,
  min: 12.34,
  max: 12.34,
}
const example2: Metrics = {
  datetime: new Date(),
  avg: 12.34,
}

Here is what I tried:

type Metrics = {
  datetime: Date,
  [key: string]: number,
}
// ERROR on the type definition:
// Property 'datetime' of type 'Date' is not assignable to string index type 'number'


type Metrics = { 
    datetime: Date 
} & {  
    [key: string]: number 
}
// ERROR on the variable assignment:
// Type '{ datetime: Date; activity: number; min: number; max: number; }' is not assignable to type 'Metrics'.
//   Type '{ datetime: Date; activity: number; min: number; max: number; }' is not assignable to type '{ [key: string]: number | null; }'.
//     Property 'datetime' is incompatible with index signature.
//       Type 'Date' is not assignable to type 'number'.

Upvotes: 5

Views: 1316

Answers (2)

William Neely
William Neely

Reputation: 2082

It's a bit hacky but I found a way to do this to add a type based on your object. This allows you to export a typed object.

  1. Define your object without a type
const example1 = {
  datetime: new Date(),
  activity: 12.34,
  min: 12.34,
  max: 12.34,
}
  1. Create a type from that object but exclude the specific keys you want to change
type AllKeys<T> = {
  [Property in keyof T]: number;
};
  1. Create an intersection type using the type from 2 and omit your datetime key. Note that "typeof example1" gives you all of the keys from example1.
type NewType = AllKeys<Omit<typeof example1, "datetime">> & { datetime: Date};
  1. export the typed object
export const example: NewType = example1;

Upvotes: 0

Tadhg McDonald-Jensen
Tadhg McDonald-Jensen

Reputation: 21453

You will want something that uses Record<Exclude<K,"datetime">, number> to map all present keys to type number but exclude the key "datetime". Since this won't necessarily be possible with a usual index type you are probably best off using a helper function that constrains the type as a generic:

type Metrics<K extends keyof any> = { datetime: Date } & Record<Exclude<K, "datetime">, number>;

function makeMetrics<T extends Metrics<keyof T>>(metrics: T): T {
    return metrics;
}

const example1 = makeMetrics({
    datetime: new Date(),
    activity: 12.34,
    min: 12.34,
    max: 12.34,
});
const example2 = makeMetrics({
    datetime: new Date(),
    avg: 12.34,
});

This means that both examples will only allow the keys that are present when it is defined, adding extra keys later would not, in order to allow that you'd need something like "all keys except datetime are numbers" which is not possible as far as I know.

Upvotes: 1

Related Questions