1

I have some declared data interface:

export interface RegisterData {
    email: string
    password: string
    firstName?: string
    lastName?: string
}

And would like to create and object having only non-empty fields of an instance of the interface (and avoiding some non-empty special fields like password):

const nonEmptyData: any = { };

for(const itemId in registerData) {
  if(itemId !== "password" && registerData[itemId]) {
    nonEmptyData[itemId] = registerData[itemId];
  }
}

However with "noImplicitAny" check active I get the next error:

error TS7017: Element implicitly has an 'any' type because type 'RegisterData' has no index signature.

How 'for in' can be used with the check active?

Arsenii Fomin
  • 3,120
  • 3
  • 22
  • 42
  • Possible duplicate of [How do I prevent the error "Index signature of object type implicitly has an 'any' type" when compiling typescript with noImplicitAny flag enabled?](https://stackoverflow.com/questions/32968332/how-do-i-prevent-the-error-index-signature-of-object-type-implicitly-has-an-an) – bugs Apr 24 '18 at 13:51
  • @bugs I saw that question. There are a lot of background information concerning this error in it but I'm not sure I could figure out which is good way to rewrite "for in" using that material (look for accepted answer, it is there). – Arsenii Fomin Apr 24 '18 at 14:07

2 Answers2

3

You could use Object.keys with an assertion that the keys array is in fact an array of keys of RegisterData:

for (const itemId of Object.keys(registerData) as Array<keyof RegisterData>) {
    if (itemId !== "password" && registerData[itemId]) {
        nonEmptyData[itemId] = registerData[itemId];
    }
}

Adding the index signature is also an option but not a great one, that will allow indexing by any string.

Edit

Or better yet, depending on your target and polyfills you could also use Object.entries

for (const [itemId, value] of Object.entries(registerData)) {
    if (itemId !== "password" && value) {
        nonEmptyData[itemId] = value;
    }
}
Titian Cernicova-Dragomir
  • 230,986
  • 31
  • 415
  • 357
  • The first approach looks very overburdened for comon use especially not providing anything useful. The second approach looks great, but I need to figure out how to make it work (I use ts 2.7 and VS Code) – Arsenii Fomin Apr 24 '18 at 14:04
  • 1
    @ArseniiFomin it's not about ts version it' about js target, `entries` is defined for `es2017`. You can target that and use a polyfill from here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/entries Either use the JS version or convert in to TS (will probably need some any assertions but I have not checked ) – Titian Cernicova-Dragomir Apr 24 '18 at 14:06
1

You can extend the interface adding the index signature to iterate when needed:

interface IterableRegisterData extends RegisterData {
  [key: string]: string | undefined
}

const iterableRegisterData = registerData as IterableRegisterData;
for(const itemId in iterableRegisterData) {
  if(itemId !== "password" && iterableRegisterData[itemId]) {
    nonEmptyData[itemId] = iterableRegisterData[itemId];
  }
}

If you don't need to mutate registerData inside the loop, you can add readonly on the index signature to prevent adding new properties to iterableRegisterData:

interface IterableRegisterData extends RegisterData {
  readonly [key: string]: string | undefined
}

Original answer (bad practice)

Just add the index signature:

export interface RegisterData {
    email: string
    password: string
    firstName?: string
    lastName?: string
    [key: string]: string | undefined
}

Note that you must use a union type because some of your properties could be undefined. In case you add more properties with different types, you must add that types to the index signature:

export interface RegisterData {
    email: string
    age: number
    name?: string
    data: AdditionalData
    [key: string]: string | number | AdditionalData | undefined
}

Warning

As pointed in the comments, this could be a bad practice, because adding the index signature will let you add new properties not defined in the interface, and probably you don't want to let that:

const userData: RegisterData = { 
    email: 'user@domain.com,
    password: '1234'
}
userData['foo'] = 'baz'
Camilo
  • 2,844
  • 2
  • 29
  • 44
  • You are suggesting to involve two bad practices - the first is the necessity to modify interface anytime I need to iterate some object (and this interface can be provided by library etc), the second one is losing almost all of the type checking. – Arsenii Fomin Feb 15 '19 at 11:54
  • Btw if I could upvote the accepted answer above any time I use the pattern provided there in my real code, it would already have had a score above hundred. :) – Arsenii Fomin Feb 15 '19 at 11:57
  • @ArseniiFomin you're right. About the first point, obviously it will only work if you can modify your interface. About your second point, that's why I added the warning, it's not the best way, but it's the only one I found to use for...in instead of for...of without typescript crying. I found it weird that I can't use a javascript feature because typescript doesn't like it, so I think it's a trade off. – Camilo Feb 15 '19 at 12:14
  • @ArseniiFomin just found another way, see my edit. I think it will be less problematic. – Camilo Feb 15 '19 at 13:07
  • Your second variant looks much better. I think it can be an alternative when using "Object.entries()" is not allowed. Also, I see some other use cases when it can be better than just doing the following: "const someTypeObject: SomeTypeObject = { ... }; const hackObject: any = someTypeObject; // ... dirty things with hackObject here ... ", which I have to do rather often – Arsenii Fomin Feb 15 '19 at 17:49