Infer type of Object using a schema (an array of objects)

clock icon

asked 3 months ago Asked

message

2 Answers

eye

12 Views

I'm trying to infer the types of objects using a schema. This schema is an array of a objects containing all properties of the field.

type Field = {
    name: string
    kind: 'string' | 'integer' | 'float' | 'bool'
    required?: boolean
}
type Schema = { fields: Field[] };

const schema: Schema = {
    fields: [
        { name: 'id', kind: 'integer', required: true },
        { name: 'name', kind: 'string', required: true },
        { name: 'debits', kind: 'float' },
    ]
}

const customer: Record<?> = {
  id: 1,
  name: 'John Doe',
  debits: 50
}

 

I'm trying to get the inferred type, but I had no success.

2 Answers

It's not going to resolve to Field since you gave it a literal object with no declared type. Its type is literally that object. You need to give some hint or force it with a type assertion. 

You can define the following types. Don't use T extends Schema in type SchemaToRecord<T>!

If you use T extends Schema, you will hide the actual value of each Field. TypeScript will infer the types using the type Field.

Not using T extends Schema, means that you are delaying the inference the type of a concrete Field value is provided for T.

type Field = {
    kind: 'string' | 'integer' | 'float' | 'bool';
    required?: boolean;
};

type KindToType<T> =  T extends 'string' ? string: 
  T extends 'integer' ? number : 
  T extends 'float' ? number :
  T extends 'bool' ? boolean : never;

type GetMandatoryKeys<T> = {
  [P in keyof T]: T[P] extends Exclude<T[P], undefined> ? P : never
}[keyof T]

type UndefinedToOptional<T> = Partial<T> & Pick<T, GetMandatoryKeys<T>>

type SchemaToRecord<T> = UndefinedToOptional<{
  [K in keyof T]: 'required' extends keyof T[K] ?
    ('kind' extends keyof T[K] ? KindToType<T[K]['kind']> : never) :
    ('kind' extends keyof T[K] ? KindToType<T[K]['kind']> | undefined : never)
}>;

You can use the above types as follows:

const customerSchema = {
  id: { kind: 'integer', required: true } as const,
  name: { kind: 'string', required: true } as const,
  debits: { kind: 'float' } as const,
}

type CustomerSchema = typeof customerSchema;
type CustomerRecord = SchemaToRecord<CustomerSchema>;

// OK
const customer: CustomerRecord = {
  id: 1,
  name: 'John Doe',
  debits: 50
}

// OK
const customer: CustomerRecord = { id: 1, name: 'John Doe', debits: 50 };

// OK
const customer: CustomerRecord = { id: 1, name: 'John Doe' };

// ERROR Type 'string' is not assignable to type 'number'
const customer2: CustomerRecord = { id: 1, name: 'John Doe', debits: '50' };

// ERROR Type 'number' is not assignable to type 'string'
const customer3: CustomerRecord = { id: 1, name: 50 };

Don't use const customerSchema: Schema = {... in const customerSchema = {! use as const (in each field) instead.

If you use : Schema, TypeScript will infer the types using the Schema (type), not the customerSchema value. This will lead to less accurate type inference.

Write your answer here

Top Questions