Balázs Édes
Balázs Édes

Reputation: 13807

Typescript type arbitrary amount of generic fields

First, I'm not 100% sure if what I want to do is possible, but I try to describe the best possible.

interface Property<T> {
  value: T
}

interface PropertyBag {
  [key: string]: Property<???>
}

function toPlainObject(props: PropertyBag): ??? {
  return Object.keys(props)
    .reduce((acc, key) => Object.assign(acc, { [key]: props[key].value }), {})
}

An example demonstrating what I'd like to do:

interface Person {
  name: string
  age: number
}

const name: Property<string> = { value: 'John Doe' }
const age: Property<number> = { value: 35 }

const props: PropertyBag = { name, age }

const person: Person = toPlainObject(props)

What I would like to know is how could I go about typing the return type of toPlainObject and PropertyBag (where types are ???). Is this even possible using TS?

Upvotes: 6

Views: 1313

Answers (1)

Titian Cernicova-Dragomir
Titian Cernicova-Dragomir

Reputation: 249536

You can do something close to this, if you add an extra generic parameter to PropertyBag and use a mapped type instead:

interface Property<T> {value: T }
type PropertyBag<T> = { [P in keyof T]: Property<T[P]> }

Basically T will serve as the holder of property names and property types for the property bag. You can define an instance explicitly like so:

const name: Property<string> = { value: 'John Doe' }
const age: Property<number> = { value: 0 }
let bag : PropertyBag<{ name : string, age: number}> = { age, name };

interface Person { name : string, age: number}
let personBag : PropertyBag<Person > = { age, name };

You can also create a function that helps with the type so you don't have to manually specify all the properties and types

function createBag<T>(props: PropertyBag<T>):PropertyBag<T> {
    return props;
}


const name: Property<string> = { value: 'John Doe' }
const age: Property<number> = { value: 0 }
let bag  = createBag({ age, name }); // infered as PropertyBag<{age: number;name: string;}>

And you can of course use this for your function:

function toPlainObject<T>(props: PropertyBag<T>): T {
    return (Object.keys(props) as Array<keyof T>)
        .reduce((acc, key) => Object.assign(acc, { [key]: props[key].value }), {}) as any;
}

const name: Property<string> = { value: 'John Doe' }
const age: Property<number> = { value: 0 }

const person:Person  = toPlainObject({ name, age })

Upvotes: 4

Related Questions