2

Sample JSON

{
  "files": {
    "group1": {
      "subgroup1": ["file1", "file2"]
    },
    "group2": {
      "subgroup2": ["file3", "file4"],
      "subgroup3": ["file5"]
    }
  }
}

I am trying to build a function that gives me a random file, given the group and subgroup names

Try 1

import data from "./data.json";

function getRandomImage(group: string, subgroup: string) {
  const files = data.files[group][subgroup];
  return files[Math.floor(Math.random() * files.length)];
}

The above code gives me this error

Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{ group1: { subgroup1: string[]; }; group2: { subgroup2: string[]; subgroup3: string[]; }; }'. No index signature with a parameter of type 'string' was found on type '{ group1: { subgroup1: string[]; }; group2: { subgroup2: string[]; subgroup3: string[]; }; }'.

Try 2

function getRandomImage(data: any, group: string, subgroup: string) {
  const files = data.files[group][subgroup];
  return files[Math.floor(Math.random() * files.length)];
}

i tried passing data also with any type, but that gives error

Unexpected any. Specify a different type.

Try 3

type groupType = keyof typeof data.files;
type subGroupType = keyof typeof data.files[groupType];

function getRandomImage(group: groupType, subgroup: subGroupType) {
  const files = data.files[group][subgroup];
  return files[Math.floor(Math.random() * files.length)];
}

that gives me error

Property 'length' does not exist on type 'never'.

I am very new to typescript (coming from python), so this has been a big hassle.. please help me out.

Priya
  • 23
  • 3

1 Answers1

3

If you want typed function for manual usage (ie it suggests available args), infer possible 2nd arg from 1st arg:

// imported json is const type
const json = {
  "files": {
    "group1": {
      "subgroup1": ["file1", "file2"]
    },
    "group2": {
      "subgroup2": ["file3", "file4"],
      "subgroup3": ["file5"]
    }
  }
} as const;
type jsonT = typeof json.files;

function getRandomImage<
  K1 extends keyof jsonT,
  K2 extends keyof jsonT[K1]
>(group: K1, subgroup: K2) {
  const files = json.files[group][subgroup] as string[];
  return files[Math.floor(Math.random() * files.length)];
}
// won't allow improper args
getRandomImage('group1', 'subgroup1')

If you don't care about keys being proper keys, widen the types to string:

// widen the type to {"a":{"b":["c"]}} with any strings
const filesRecord: Record<string, Record<string, string[]>> = json.files;
// or {[p: string]: {[p2: string]: string[]}} which means same


function getRandomImage(group: string, subgroup: string) {
  const files = data.files[group][subgroup];
  return files[Math.floor(Math.random() * files.length)];
}
Dimava
  • 7,654
  • 1
  • 9
  • 24
  • Great, any idea how i can extend this if i don't know exact depth of the json? – Priya Oct 07 '22 at 18:33
  • There is a common alike use case of text translations, see https://stackoverflow.com/questions/58277973/how-to-type-check-i18n-dictionaries-with-typescript on how it's implemented. That's advanced Typing magic, it takes hours to write properly if you are not copypasting someone else – Dimava Oct 07 '22 at 20:59
  • If that's not for `const` type with autocompletion but for , then I can edit answer for variable-length getter function – Dimava Oct 07 '22 at 21:01
  • Note that the code will be different for object with fixed-depth and object with multiple-depths so tell me the exact case – Dimava Oct 07 '22 at 21:02