Home > Net >  How to type nested objects using Object.keys()?
How to type nested objects using Object.keys()?

Time:12-09

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;

CodePudding user response:

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[];

CodePudding user response:

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"];
  • Related