Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

How to pick only one type from list?

There is single interface IStorage where every key has two types:

interface IStorage {
    key: boolean | CustomStore<boolean>,
    key2: number | CustomStore<number>
}

type CustomStore<T> = {
    value: T,
    init: (value: T) => void;
    set: (value: T) => void;
}

The class LiveStorage have defaultStorage (first type of IStorage) and storage (second):

class LiveStorage<D = { [key: string]: any }, S = { [key: string]: CustomStore<any> }> {

    private defaultStorage: D
    storage: S

    constructor(defaultStorage: D) {
        this.defaultStorage = defaultStorage as D
        this.storage = {} as S
    }
}

When create instance, key has both types:

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

let liveStorage = new LiveStorage<IStorage, IStorage>({})
liveStorage.storage.key // boolean | CustomStore<boolean>

Are there ways to key will have only CustomStore<boolean> by default? With single interface and without (liveStorage.storage.key as CustomStore<boolean>). I know that is only compile time, just want to get suggestion from IDE without (x as type) every time.

>Solution :

Essentially you want defaultStorage to be of a "base" key-value type, and then you want storage to be a transformed version of that type. For each property key K of the base type T, you want to take the property value type T[K] and map it to CustomStore<T[K]>. So we can define a mapped type that does this:

type CustomStorageFor<T> =
    { [K in keyof T]: CustomStore<T[K]> };

For your example then, you wouldn’t want to use your IStorage type directly, with unions of base and mapped properties. Instead, you would define IStorage to be the base type

interface IStorage {
    key: boolean
    key2: number
}

And then you get the CustomStorage version by mapping it:

type CustomStorageForIStorage = CustomStorageFor<IStorage>;
/* type CustomStorageForIStorage = {
    key: CustomStore<boolean>;
    key2: CustomStore<number>;
} */

Now your LiveStorage class can be generic in just the base type T, and storage will be given type CustomStorageFor<T>:

class LiveStorage<T extends object> {

    private defaultStorage: T
    storage: CustomStorageFor<T>;

    constructor(defaultStorage: T) {
        this.defaultStorage = defaultStorage
        this.storage = {} as CustomStorageFor<T>; // you need to implement this
    }
}

Now when you call the LiveStorage constructor, the compiler will infer the type argument T to be that of the constructor argument (and I’ve constrained T to the object type so that you’ll get an error if you try to pass in a string or something):

const defaultStorage: IStorage = { key: true, key2: 1 };
let liveStorage = new LiveStorage(defaultStorage);
// let liveStorage: LiveStorage<IStorage>
liveStorage.storage.key // CustomStore<boolean>

In fact you don’t even need the IStorage type if you don’t want to give it a name; the compiler will happily synthesize an equivalent object type from the constructor argument:

let liveStorage = new LiveStorage({ key: true, key2: 1 });
// let liveStorage: LiveStorage<{ key: boolean; key2: number; }>
liveStorage.storage.key // CustomStore<boolean>

Either way will work.

Playground link to code

Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading