typescripttype-conversionindex-signature

How to remap an index signature to make keys detectable in IDE


So, I'm trying to create an object that can have dynamic attributes, but I want that the IDE can be able to detect the keys that I'm already gave to this object.

if I try this example:

interface Person {
    name: string
    age: number
}


type dynamicObject = { [key: string]: Person }

const maria: dynamicObject = {
    maria: {
        name: "Maria",
        age: 37
    }
}

If I use maria. to try to detect the keys from the object maria the IDE will not show me anything.

I know that using generic types I can workaround this problem using [P in keyof T]

interface Person {
    name: string
    age: number
}

interface Panel<T> {
    label: string
    fields: (keyof T)[]
}

interface Field<T> {
    label: string
    value: T
}

interface Form<T> {
    panels: { [key : string] : Panel<T> } 
    fields: { [P in keyof T]?: Field<T[P]> }
}

const form: Form<Person> = {
    panels: {
        main: {
            label: "Panel",
            fields: ["name", "age"]
        }
    },
    fields: {
        name: {
            label: "Name",
            value: "Test"
        }
    }
}

form.panels // not detecting panel keys
form.fields // detecting fields keys

In the example above I'm able to detect the keys from form.fields because I'm using a type with pre-defined keys. But my goal is to be able to transform this Form type into the same thing but with the possibility of detecting the keys from the form.fields

So, there is any function, remap type, or way to transform this index signature into string literals to accomplish this?

I know that this is possible because there are libraries like stiches.io where you create an object with custom variants and the function generates a new object with the custom keys being detectable by the IDE. I tried to see the magic behind that but the typing is over complicated to understand.


Solution

  • You can't do it with only a type, there needs to be a function with an infered return type (that's what stitches is also doing).

    export type Form = { <T>(values?: U): U }
    
    declare const buildForm: Form;
    
    const form = buildForm({
        panels: {
            main: {
                label: "Panel",
                fields: ["name", "age"]
            }
        },
        fields: {
            name: {
                label: "Name",
                value: "Test"
            }
        }
    })
    
    form.panels // OK
    form.fields // OK
    

    Playground