6

From a legacy api I am getting a JSON response like so:

const someObject = {
    "general": {
        "2000": 50,
        "4000": 100,
        "8000": 200,
    },
    "foo": [
        0,
        1,
        2,
    ],
    "bar": [
        5,
        7,
    ],
    "baz": [
        8,
        9,
    ],
};

Keep in mind that all the indexes except "general" are dynamic and might not be in the response, I cannot type for each property but have to use an index signature.

I wanted to achieve that via typescript@2.9.2:

interface ISomeObject {
    general: {
        [index: string]: number;
    };

    [index: string]?: number[];
}

as general will always be in the response, yet the other indexes might or might not be in there.

Issue that I am facing:

  • I cannot make the [index: string]?: number[] optional as it will complain that number is used as a value here.
  • [index: string]: number[] will override the definition of general: number and hence tsc will complain:

    Property 'general' of type '{ [index: string]: number; }' is not assignable to string index type 'number[]'.`
    

Can I even type for this format with a TypeScript interface?

k0pernikus
  • 60,309
  • 67
  • 216
  • 347
  • 2
    You did a good job coming up with a title for this, potentially confusing, question! – John Nov 29 '18 at 12:13
  • @John The power of [rubber duck problem solving](https://en.wikipedia.org/wiki/Rubber_duck_debugging) ;) And thank you for the kind words. – k0pernikus Nov 29 '18 at 14:36
  • Does this answer your question? [How to define Typescript type as a dictionary of strings but with one numeric "id" property](https://stackoverflow.com/questions/61431397/how-to-define-typescript-type-as-a-dictionary-of-strings-but-with-one-numeric-i) – kaya3 Dec 15 '21 at 14:27

1 Answers1

5

This is a variation of the TypeScript Dictarray concept.

The cheat-fix is to tell TypeScript that everything is fine and you know what you're doing:

interface ISomeObject {
    [index: string]: number[];
    // @ts-ignore: I'm creating a Dictarray!
    general: {
        [index: string]: number;
    };
}

The compiler will correctly infer the return types, which are numbers in this case:

let x: ISomeObject;

const a = x.general['idx'];
const b = x['idx'];

The linked article has more information, but this is the gist of it in your specific case.

Fenton
  • 241,084
  • 71
  • 387
  • 401
  • 1
    I think it's crazy lucky that I just ran into this problem and you happen to have provided an answer within the hour. Thanks! – John Nov 29 '18 at 11:58
  • This answer seems to run into a problem in that typescript doesn't like `let x: ISomeObject = {general: {}}`. Basically, it's the same limitation as outlined in [this answer](https://stackoverflow.com/a/44358719/5490505). Therefore, perhaps using a union type (as described in the linked answer) is a better solution than `@ts-ignore` ? – John Nov 29 '18 at 12:06
  • 3
    It is best to use `let x = { general: {}} as ISomeObject;` - the limitation of the union is constant shimming into one of the two narrower types when you come to use it. I prefer early-gnarl to late-gnarl :) – Fenton Nov 29 '18 at 12:09
  • @Fenton: Is this still the best solution using TS 3.7.2? I'm asking since I've another interface type for the `general` property. Using `as` in the object generation suppresses all checks and makes the compiler ignore wrong assignments in the value of `general`. – Newlukai Dec 13 '19 at 15:01