0

I want to create an object using default values for an interface, but at the same time changing the structure of the object. For example, for this interface:

interface IPerson {
  name: string
  age: number
}

I want to create an object like this:

const person: IPerson = {
  name: {
    type: String,
    required: true
  },
  age: {
    type: Number,
    required: true
  }
}

The only way I found is to add the type for the object to name and age of the IPerson interface, like so:

interface IPerson {
  name: string | IProp
  age: number | IProp
}

interface IProp {
  type: any
  required?: boolean
  // ...
}

I do not want to change the original interface IPerson though. So I was thinking of something like this:

const person: IProperties<IPerson> = {
  // use properties of IPerson to determine which key/value pairs are valid in this object
}
1
  • 1
    I am pretty sure I have answered this before .. but finding it is challenging... Commented Apr 8, 2019 at 8:27

2 Answers 2

2

You can use a mapped type:

interface IPerson {
  name: string
  age: number
}

type IProperties<T> = {
    [K in keyof T]: IProp
}

interface IProp {
  type: any
  required?: boolean
  // …
}

const person: IProperties<IPerson> = {
    // …
}
Sign up to request clarification or add additional context in comments.

3 Comments

Yes, this is exactly it! Thanks!
@Johannes maybe I misunderstood the question, but wouldn't it be useful for required and type to be a refection of the actualul type passed in ?
@TitianCernicova-Dragomir Yes, you're right. This is just an example, it doesn't really matter what required and type is... I just needed a way to have an objects conforming to the keys of the IPerson interface.
2

You can do this using conditional types and mapped types.

interface IPerson {
name: string
age: number
ocupation?: string
}

type PropertyType<T> = 
    [T] extends [string] ? typeof String :
    [T] extends [number] ? typeof Number :
    [T] extends [Date] ? typeof Date :
    [T] extends [Function] ? typeof Function : // add any other types here
    new (...a:any[]) => T // default must be a constructor for whatever T is 

type IfRequired<T, K extends keyof T, IfYes, IfNo> =
    Pick<T, K> extends Required<Pick<T, K>> ? IfYes : IfNo

type PropertyDefinition<T> = {
    [P in keyof T]-?: {
        type: PropertyType<Exclude<T[P], null | undefined>> // will not work for unions!
        required: IfRequired<T, P, true, false>
    }
}

let personDefinition: PropertyDefinition<IPerson> ={
    age : {
        required: true,
        type: Number
    },
    name: {
        required: true,
        type: String,
    },
    ocupation: {
        required: false,
        type: String
    }
} 

5 Comments

So this actually checks if, e.g. age: number uses type: Number in the mapped type?
The TypeScript syntax can be terrible, but it is a stricter answer than mine.
@Johannes yup, for age the only valid value is { type: Number, require: true } any other value would be a compiler error. I agree with paleo, it may be harder to read as far as syntax goes, his answer is also good just less strict, totally your call which you use :)
@TitianCernicova-Dragomir This might be a different question, but just for completeness sake: Is it also possible to omit required if it's set to false? Because undefined is already a falsy value.
@Johannes this should work %type PropertyDefinition<T> = { [P in keyof T]-?: { type: PropertyType<Exclude<T[P], null | undefined>> } & IfRequired<T, P, { required: true }, { required?: false }> }

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.