2

I am using the nested object and trying to get to the properties using Object.keys() and forEach(). The problem is when I want to get to the nested keys filteringState[item][el].

How to type function like this?

interface InitialStateTypes {
    country: {
        "italy": boolean,
    },
    board: {
        "full": boolean,
    },
    transport: {
        "bus": boolean,
        "own": boolean
    },
}

interface FilteringDataTypes {
    country: string[],
    board: string[],
    transport: string[],
}

const collectFilteringData = (filteringState: InitialStateTypes) => {
    let filteringData = <FilteringDataTypes>{};

    Object.keys(filteringState).forEach((item) => {
        Object.keys(filteringState[item]).forEach((el) => {
            if (filteringState[item][el]) {
                if (!filteringData[item]) {
                    filteringData[item] = [el];
                } else {
                    filteringData[item] = [...filteringData[item], el];
                }
            }
        });
    });
    return filteringData;
};


export default collectFilteringData;
BartD
  • 53
  • 5

2 Answers2

3

You can use the keyof operator.

Example :

type Point = { x: number; y: number };
type P = keyof Point;

Here is more details : https://www.typescriptlang.org/docs/handbook/2/keyof-types.html

Nested data :

type Point = { test: {x: number; y: number} };
type P = keyof Point["test"];
Johan
  • 2,088
  • 2
  • 9
  • 37
  • Yes keyof is working but only for first level of properties inside the InitialStateTypes. How can I type next level of nesting. – BartD Dec 08 '22 at 10:14
  • 1
    @BartD I added info in my post. Could this do the trick for you ? – Johan Dec 08 '22 at 10:17
2

It can get a little messy when using the keys method, because it only expects to output strings. (It is not unreasonable since JS Object keys are considered strings, of course TypeScript worries about this more)

Below is a possible way it could be done:

interface InitialStateTypes {
  country: {
      "italy": boolean,
  },
  board: {
      "full": boolean,
  },
  transport: {
      "bus": boolean,
      "own": boolean
  },
}

interface FilteringDataTypes {
  country: string[],
  board: string[],
  transport: string[],
}

const collectFilteringData = (filteringState: InitialStateTypes):FilteringDataTypes => {
  let filteringData = {} as FilteringDataTypes

  (Object.keys(filteringState) as Array<keyof InitialStateTypes>).forEach((item) => {
      (Object.keys(filteringState[item]) as Array<keyof InitialStateTypes[typeof item]>).forEach((el) => {
          if (filteringState[item][el]) {
              if (!filteringData[item]) {
                  filteringData[item] = [el];
              } else {
                  filteringData[item] = [...filteringData[item], el];
              }
          }
      });
  });
  return filteringData;
};


export default collectFilteringData
  • Here the type assertion acts to tell TypeScript the types you're actually expecting.
  • It then allows the correct types to be passed through to the forEach method
  • The tricky part is that it's nested, so you have to do another type assertion, but you also want to pass in the value for the typeof item from the first forEach

Formatted (with Prettier), it ends up looking like this:

const collectFilteringData = (
  filteringState: InitialStateTypes
): FilteringDataTypes => {
  let filteringData = {} as FilteringDataTypes;

  (Object.keys(filteringState) as Array<keyof InitialStateTypes>).forEach(
    (item) => {
      (
        Object.keys(filteringState[item]) as Array<
          keyof InitialStateTypes[typeof item]
        >
      ).forEach((el) => {
        if (filteringState[item][el]) {
          if (!filteringData[item]) {
            filteringData[item] = [el];
          } else {
            filteringData[item] = [...filteringData[item], el];
          }
        }
      });
    }
  );
  return filteringData;
};

Note

I've written this answer as of TypeScript 4.6.2, the keys method is typed like so:

keys(o: object): string[];
Harrison
  • 1,654
  • 6
  • 11
  • 19