2

I know some of typescript's advantages are enabling type-safe functions -
but is it possible to ensure my function can only get objects with specific keys, or in other words - objects of specific structure ?

I know of many elegant ways to test if a nested key exists, such as [this one][1] ,
and I can of course run a small check at the beginning of my function - but the reason I'm asking this is because my function will be used by many other programmers - and I want to ensure they can understand what input they should insert just from the function's signature.

Example:

function printName(user){
    console.log(user.details.name); // I want to ensure details.name exist
}

and I would wish to have some feature like:

function (user : {details: {name : string}}){
    //same ...
}


[1]: https://stackoverflow.com/questions/2631001/javascript-test-for-existence-of-nested-object-key#answer-4034468 "this one"
syoels
  • 154
  • 1
  • 10
  • 3
    Tried TypeScript `interface`? https://www.typescriptlang.org/docs/handbook/interfaces.html – Sayan Pal Jul 23 '17 at 08:11
  • 2
    I suggest running through the TS docs again. This is pretty basic. By the way, did you try the code you gave? What happened? Also, could you clarify if `details` is the only property, or one of several known in advance? –  Jul 23 '17 at 09:22

4 Answers4

3
interface User {
    details:{name: string}
}

function printName(user:User){
    console.log(user.details.name); // I want to ensure details.name exist
}
Richie Fredicson
  • 2,150
  • 3
  • 16
  • 19
0

This is exactly the feature you desire:

function printName(user: { [key: string]: any, details: { [key: string]: any, name: string }}) {
    console.log(user.details.name)
}

Allow any properties and require details + name.

More legible and protected against unintentional changes:

// oftentimes you put the following interfaces in extra files:
interface Details {
    readonly [key: string]: any // = allow other properties than "name"*
    readonly name: string
}
interface User {
    readonly [key: string]: any // = allow other properties than "details"*
    readonly details: Details
}
// *) consider explicitly specifying them

function printName(user: User) {
    console.log(user.details.name)
}

And use the following code if you know other developers may call your function from plain JavaScript code (not TypeScript code):

function printName(user: User) {
    const name = ((user || <User>{}).details || <Details>{}).name
    if(name == void 0) {
        console.error("user name missing")
    }
    console.log(name)
}

Code on TypeScript Playground

ideaboxer
  • 3,863
  • 8
  • 43
  • 62
  • You've assured nothing. `[key: string]: any` is a direct superset of `name: string` so it is swallowed. You've basically typed it `user: {[key: string]: any}`. – Madara's Ghost Jul 23 '17 at 08:59
  • I've added a Playground link so that you can try it out. – ideaboxer Jul 23 '17 at 09:07
  • I'll amend, in that case, `[key: string]: any` is redundant, because that's implied from the interface. – Madara's Ghost Jul 23 '17 at 09:09
  • It is not redundant because there might be further properties in the object. If you explicitly specify the object's shape, you can omit `[key: string]: any`. – ideaboxer Jul 23 '17 at 09:10
0

Use keyof if you want to retrieve a value from a specific property, this will highlight the incorrect property name.

You can also use Object.keys to check if the property exists.

interface UserProps {
  name: string;
  age: number;
}

export class User {
  constructor(private data: UserProps) {}

  get(propName: keyof UserProps): string | number {
    const dataKeys = Object.keys(this.data);
    if (!dataKeys.includes(propName)) {
      throw Error(`Property ${propName} does not exists on user object`);
    }
    return this.data[propName];
  }
}
-1

You can use interfaces in typescript

export interface Details{
  name:string,
  age: number
}
export interface User{
  details :  {
        [key: string]: Details
    };
}

function printName(user : User){}
Dinesh undefined
  • 5,490
  • 2
  • 19
  • 40