7

As the title says, I'm trying to create an interface that will have required fields but only if another field is of a certain value.

For example :

const schema = {
    str: { type: 'string' },
    nbr: { type: 'number' },
    bool: { type: 'boolean' },
    date: { type: 'date' },
    strs: { type: ['string'] },
    obj: { type: 'object' },
} as ISchema;

I want this code to tell me that the field obj is missing a property because the value of type is 'object'.

I succeeded to do so with this code :

interface SchemaOptionsObject {
    type: 'object' | ['object'] ;
    properties: ISchema;
}
interface SchemaOptionsString {
    type: 'string' | ['string'] ;
}
interface SchemaOptionsNumber {
    type: 'number' | ['number'] ;
}
interface SchemaOptionsBoolean {
    type: 'boolean' | ['boolean'];
}
interface SchemaOptionsDate {
    type: 'date' | ['date'] ;
}

type SchemaOptions = SchemaOptionsString | SchemaOptionsNumber | SchemaOptionsBoolean | SchemaOptionsDate | SchemaOptionsObject;

export interface ISchema {
    [key: string]: SchemaOptions;
}

But this solution is way too repetitive. I tried to factorise it and ended up with a problem :

export type SchemaAllowedTypes = 'string' | 'number' | 'boolean' | 'date' | 'object';

type SchemaOptionsObject<T extends SchemaAllowedTypes> =
    T extends 'object' ?
        { properties: ISchema } :
        {};

type SchemaOptions<T extends SchemaAllowedTypes> = {
    type: T | T[];
} & SchemaOptionsObject<T>;

export interface ISchema {
    [key: string]: SchemaOptions<SchemaAllowedTypes>;
}

I know it doesn't work because of the T extends 'object' but I have no idea on how to check the value of T, is there a keyword that could do that ?

Am I doing this the wrong way ?

Thanks for your help !

2
  • I don't believe there is a way to do the kind of determinations you need to in an interface. It seems like creating a method to check the properties of an object, and from that determining the interface to use, may be your best bet; though it seems like an awful lot for type safety. Perhaps a journey into the land of type guards would be useful, it may make interface determination code more readable: typescriptlang.org/docs/handbook/advanced-types.html Anyways, best of luck, hopefully you find the answer. There's always a way... one way or another!
    – iamaword
    Commented May 9, 2020 at 2:56
  • Well I guess I'll stick with my ugly solution for now. I still hope someone will come and tell me to just use the keyword hasvalue ahah. Thanks anyway !
    – GaldanM
    Commented May 9, 2020 at 14:08

1 Answer 1

1

How about this?

export type SchemaAllowedTypes = 'string' | 'number' | 'boolean' | 'date' | 'object';

interface SchemaOptionsGeneric<T extends SchemaAllowedTypes>{
    type: T | [T] ;
}
interface SchemaOptionsObject extends SchemaOptionsGeneric<"object">{
    properties: ISchema;
}
type SchemaOptions = SchemaOptionsGeneric<"string"|"number"|"boolean"|"date"> | SchemaOptionsObject

export interface ISchema {
    [key: string]: SchemaOptions;
}

Not the answer you're looking for? Browse other questions tagged or ask your own question.