首页 > 解决方案 > Defining a Record object with a single known key and its value

问题描述

Apologies, this is kind of complicated, and I've been unable to create a simpler reproduction than what I've got here. Feel free to edit the question title with something a bit more specific or descriptive.

I want to define a base data type of the shape:

type AbstractBaseTypes = {
    [key: string] : {
        inputTypes: Record<string, unknown>; 
        outputType: unknown; 
    }
}

That is, there is a list of keys, each have corresponding record of input data, as well as a corresponding output datatype.

As an example, one would then create a 'concrete' version of this data type, like such:

type KnownBaseTypes = {

    "text": {
         inputTypes: {
            label: string; 
            maxLength: number; 
        }
        outputType: string; 
    }, 
    "number": {
        inputTypes: {
            label: string; 
            min: number; 
            max: number;
            
        }, 
        outputType: number; 
    } 
}

The ultimate goal I'm going for is I want to create map, that for each of those keys, we create a function that takes three parameters:

ie. Here's how I would do this in plain javascript.

const myDataManipulationMap = {

    "text": (data, controlObject, callback) => {

        const {
            key, inputData
        } = controlObject; 

        const {
            label, maxLength 
        } = inputData


        const newString = (label + data[key]).substr(0, maxLength); 

        const newData = {
            ...data, 
            [controlObject.key]: newString
        }; 

        callback(newData); 
    }, 

    "number": (data, controlObject, callback) => {

        const {
            key, inputData
        } = controlObject; 

        const {
            min, max, label 
        } = inputData


        const newNumber = min + Math.random() * (max - min); 

        const newData = {
            ...data, 
            [controlObject.key]: newString
        }; 

        callback(newData); 
    }
}

My approach for doing typings on this goes like this:

My control object:

type ControlObject<T extends AbstractBaseTypes, K extends keyof T> = {
    key: string; 
    inputData: T[K]["inputTypes"];
}; 

And my partially known data references the key of the control object:

type PartiallyKnownDataObject<T extends AbstractBaseTypes, K extends keyof T >  = {
    [ key in ControlObject<T, K>["key"] ] : T[K]["outputType"]
}

Putting it all together:

type DataGenerationMap<T extends AbstractBaseTypes> = {
    [K in keyof T]: (
        data: PartiallyKnownDataObject<T,K>,
        controlObject: ControlObject<T,K>,
        callback: (data: PartiallyKnownDataObject<T,K>) => void
    ) => void; 
}

This almost does what I want.

The problem is, inside my functions 'partially known data' is asserted to have any number of keys, all with the outputType data type.

Whereas, I want to be able to only make assumptions about the type of the data accessible by controlObject.key. Attempts to access other keys should reveal an unknown data type.

const myMap : DataGenerationMap<KnownBaseTypes> = {
    "text": (data, controlObject, callback) => {

        //snip
    }, 

    "number": (data, controlObject, callback) => {

        const {
            key, inputData
        } = controlObject; 

        const {
            min, max 
        } = inputData


        const a : string = min; // expected error 

        const b: number = data.foo;  //<-- should error

        callback(); //expected error 
        console.log(controlObject.inputData.foo); //expected error;  

        const newNumber = min + Math.random() * (max - min); 

        const newData = {
            ...data, 
            [controlObject.key]: newNumber
        }; 

        callback(newData); 
    }
}

TypeScript Playground

What's going on here?

标签: typescript

解决方案


看看这里:

type AbstractBaseTypes = {
    [key: string]: {
        inputTypes: Record<string, unknown>;
        outputType: unknown;
    }
}


type KnownBaseTypes = {
    "text": {
        inputTypes: {
            label: string;
            maxLength: number;
        }
        outputType: string;
    },
    "number": {
        inputTypes: {
            min: number;
            max: number;
        },
        outputType: number;
    }
}

type ControlObject<T extends AbstractBaseTypes, K extends keyof T> = {
    key: keyof KnownBaseTypes; // <-- you used `string` as a key type, that's why you ended up with {[index:string]:number} type
    inputData: T[K]["inputTypes"];
};

type PartiallyKnownDataObject<T extends AbstractBaseTypes, K extends keyof T> = {
    [key in ControlObject<T, K>["key"]]: T[K]["outputType"]
}

type DataGenerationMap<T extends AbstractBaseTypes> = {
    [K in keyof T]: (
        data: PartiallyKnownDataObject<T, K>,
        controlObject: ControlObject<T, K>,
        doSomethingWithdData: (data: PartiallyKnownDataObject<T, K>) => void
    ) => void;
}

type Result = PartiallyKnownDataObject<KnownBaseTypes, "number">


const myMap: DataGenerationMap<KnownBaseTypes> = {
    "text": (data, controlObject, callback) => {

        const {
            key, inputData
        } = controlObject;

        const {
            label, maxLength
        } = inputData


        const newString = (label + data[key]).substr(0, maxLength);

        const newData = {
            ...data,
            [controlObject.key]: newString
        };

        callback(newData);
    },

    "number": (data, controlObject, callback) => {

        const {
            key, inputData
        } = controlObject;

        const {
            min, max
        } = inputData


        const a: string = min; // expected error 

        console.log(data.foo); // <-- should error

        callback(); //expected error 
        console.log(controlObject.inputData.foo); //expected error;  

        const newNumber = min + Math.random() * (max - min);

        const newData = {
            ...data,
            [controlObject.key]: newNumber
        };

        callback(newData);
    }
}

它对你有用吗?


推荐阅读