10

I'm trying to write an interface but it keeps popin' an error:

"Property 'success' of type 'boolean' is not assignable to string index type 'PageableLaravel'

export interface PageableLaravel {
    path: string;
    current_page: number;
    from: number;   
}


export interface Pageable {
    success: boolean; // <-"Property 'success' of type 'boolean' is not assignable to string index type 'PageableLaravel'
    message: string; // "Property 'message' of type 'string' is not assignable to string index type 'PageableLaravel'
    [key: string]: PageableLaravel
}

The [key: string] will always change, for example: Below is an example of a JSON returned from an API

{
    success: true,
    message: 'success',
    pages: {//<-key name always will change
        path: "XX"
        //etc
    }
}

{
    success: true,
    message: 'success',
    products: {//<-key name always will change
        path: "XX"
        //etc
    }
}

{
    success: true,
    message: 'success',
    brands: {//<-key name always will change
        path: "XX"
        //etc
    }
}

Is there a way to declare a key that may/may not always change?

carlos
  • 591
  • 1
  • 5
  • 15
  • Somewhat off topic, but that is not valid JSON you are getting. – fredrik Sep 03 '19 at 15:09
  • Possible duplicate of [arbitrary type definition gives: Property 'x' of type 'boolean' is not assignable to string index type 'string'](https://stackoverflow.com/questions/55244493/arbitrary-type-definition-gives-property-x-of-type-boolean-is-not-assignabl) – jcalz Sep 03 '19 at 15:26

3 Answers3

12

Unfortunately that isn't allowed with the index signature syntax that you're using. When you add the index signature ([key: string]: PageableLaravel) that means that all properties must be PageableLaravel, not just all unspecified properties.

You can solve this with an intersection type though:

export interface PageableLaravel {
    path: string;
    current_page: number;
    from: number;   
}

export type Pageable = {
    success: boolean;
    message: string;
} & {
    [key: string]: PageableLaravel
}

TypeScript playground demo

Tim Perry
  • 11,766
  • 1
  • 57
  • 85
  • 3
    You might want to mention that the intersection type is unsound and you'll have a hard time assigning a concrete value to it. It's one of the ways people deal work around this issue, but it is a workaround with caveats. – jcalz Sep 03 '19 at 15:24
  • @tim-perry, why can't I get it to work? Even in your example: – A-S Aug 07 '20 at 12:06
  • https://www.typescriptlang.org/play?#code/KYDwDg9gTgLgBASwHY2FAZgQwMbDgBUwHNhMAjAG2ABlMpMA3YCuAbwFgAoOHuMTGAAsAXHADOMKMiIBuLrzjYArlCjAUAfX4lRSJQFsyaOd17ooEfboNGoM3lwC+XLqEiw4MAJ5g8hEuRUcAC8bPK8YkrYuGJiomQQEFSYSCYK+sCxxMCiElJIsk5wAGRhpjwA2gDWwF65ktIAuqL+pJQ0dIzMTi6cACbA2BR0eAx0fC3ZgcAm2BBIEuIhfAB0kdGZYvYA9NtwCUmkSFxzC-D6y2ArGVkkO3t50ifzixCXKxBCxnC7BFPttHoTAovSo8Fa00BXQoAEkzilcJMAgDOsDlhxynwBCI4AAifhCXEAGnCPGUqnUMC02VEAGYSZjzJZRABWIk8HqcMF-ZFUOESBE5HltIKhDEKdYxOKeKBKYAM9KbGl4zCq4mkrFCJEijpA5j8mCChnOTgQ9oGwXXJUkYIAcgAUkpFmI3l88QgxHABkMRn19kp4AgYJ6xhQ5YhPUhgEwoHA1Jg+ri4AB3BAUFh9D2YMC+Oi2mRAA – A-S Aug 07 '20 at 12:06
  • I'm getting the following error: `Type '{ success: true; message: string; path: PageableLaravel; }' is not assignable to type 'Pageable'. Type '{ success: true; message: string; path: PageableLaravel; }' is not assignable to type '{ [key: string]: PageableLaravel; }'. Property 'success' is incompatible with index signature. Type 'true' is not assignable to type 'PageableLaravel'.` – A-S Aug 07 '20 at 12:07
  • Your solution doesn't work :/ – jhujhul Oct 28 '22 at 14:23
3

There's no concrete type in TypeScript that exactly matches what you're asking for. Maybe one day TypeScript will support arbitrary index signatures and negated types, at which point we'd be able to represent something like a "default" property value. But for now, there are only workarounds.


The intersection type in the other answer is one common workaround which works fairly well for reading properties, but not so much for writing them.


There is, however, a reasonably straightforward generic type that represents the concept of "some unknown other keys":

interface BasePageable {
  success: boolean;
  message: string;
}

type Pageable<K extends keyof any> = BasePageable &
  Record<Exclude<K, keyof BasePageable>, PageableLaravel>;

Here Pageable<K> is generic in K, the name(s) of the extra key(s) whose values you want to be PageableLaravel types. To use this type you might want a helper function so you don't need to manually specify the value of K:

const asPageable = <P extends Pageable<keyof P>>(p: P) => p;

And here it is in action:

const works = asPageable({
  success: true,
  message: "hey",
  foo: {
    path: "x",
    current_page: 123,
    from: 1
  }
});

const flagsImproperValue = asPageable({
  success: true,
  message: "hey",
  foo: 123 // error!
});
// Type 'number' is not assignable to type 'PageableLaravel'.

Note that this does not capture the number of extra keys as exactly one. It allows more than one and less than one:

const allowsExtraKeys = asPageable({
  success: true,
  message: "hey",
  foo: {
    path: "x",
    current_page: 123,
    from: 1
  },
  bar: {
    path: "x",
    current_page: 123,
    from: 1
  }
});

const allowsMissingKeys = asPageable({
  success: true,
  message: "hey"
});

Of course, your original index signature would have that issue also. The restriction that there be exactly one key in addition to success and message is expressible in TypeScript but it is ugly and hard to work with. This is probably good enough for your purposes.

This is still a workaround because it's generic where you might have expected a concrete type. Okay, hope that helps; good luck!

Link to code

jcalz
  • 264,269
  • 27
  • 359
  • 360
0

It's not really possible, see this Github issue: https://github.com/microsoft/TypeScript/issues/17867

jhujhul
  • 1,527
  • 12
  • 11