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;

2 Answers 2

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"];
Sign up to request clarification or add additional context in comments.

2 Comments

Yes keyof is working but only for first level of properties inside the InitialStateTypes. How can I type next level of nesting.
@BartD I added info in my post. Could this do the trick for you ?
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[];

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.