Home > Software design >  Typescript: Spreading Objects into a typed Object contains keys not part of the type
Typescript: Spreading Objects into a typed Object contains keys not part of the type

Time:12-03

I have two Objects with unknown keys I want to merge into a new object that has a type and all keys not part of that type should be omitted.

To make a practical example: I have URL query parameters (exposed and editable by the user) and cookie values which I want to merge. I want to omit all unwanted parameters to just the ones I allow/need (defined by a type)

type RequestAdditionalParameters = {
    session?: string | null;
    searchInput?: string | null;
    id?: string | null;
}

//I use a generic because RequestAdditionalParameters actually has multiple possible
//types, but I think that shouldn't matter
export const mergeParameters = <T extends RequestAdditionalParameters>(
    cookies: Record<string, string | null>,
    queryParams: Record<string, string | null>
): T => {
    const allowedParams: T = { ...cookies, ...queryParams };
    return allowedParams;
};

const additionalParameters = mergeParameters<SearchParameters>(
    { session, id },
    { searchInput, anotherParam }
);

result = {
    session: 'kjn33fbf4fkl3w3ff3f3ffuu',
    searchInput: 'stackoverflow',
    id: '345644783',
    anotherParam: 'should not be here',
}

expectedResult = {
    session: 'kjn33fbf4fkl3w3ff3f3ffuu',
    searchInput: 'stackoverflow',
    id: '345644783',
}

If I log the output, I still get unwanted kesa that are not part of the type

How can I achieve this?

CodePudding user response:

Like @ghybs's comment, typescript types don't exist at runtime (compile-time only) so you can't use type information to filter actual data. This is because you don't actually run "typescript" code. Typescript gets compiled into Javascript, which completely strips away the types as if they didn't exist in the first place.

There are, however, ways to get runtime type data in other ways. They are a lot more complex, and are typically more useful when building large scale systems with typescript:

  1. Using a schema validation library such as Zod: https://github.com/colinhacks/zod. This let's you define a "schema" (something you want your runtime code to be able to read), and at the same time generates you a typescript definition.
  2. Use Reflect Metadata, which under the hood does a lot of magic with Decorators and Classes. NestJS has a great example of how this is done: https://docs.nestjs.com/techniques/validation.

CodePudding user response:

Kevin Bai and Kruben are right, you cannot do that at compile time.

without add zod or yup or other like this you can do something like this, if it can match.

This is not really clean, it works with your snippet, but might not work with your real code...

const requestAdditionalParametersKeys = ["session", "searchInput", "id"] as const;
type RequestAdditionalParametersKeys = typeof requestAdditionalParametersKeys[number];
type RequestAdditionalParameters = { [key in RequestAdditionalParametersKeys]?: string | null };

// inherits is done  like this : 
const searchParametersKeys = [...requestAdditionalParametersKeys, "search"] as const;
type SearchParametersKeys = typeof searchParametersKeys[number];
type SearchParameters = { [key in SearchParametersKeys]?: string | null };


const mergeParameters = <T extends RequestAdditionalParameters, K extends keyof T>(
  allAllowedKeysOfT: Array<string>,
  cookies: Record<string, string | null>,
  queryParams: Record<string, string | null>
): T => {
  const allowedParamsCandidate = { ...cookies, ...queryParams };
  const result:  { [key in keyof T]?: string | null } = {}; 
  for (const key in allowedParamsCandidate) {
    if (allowedParamsCandidate[key] != null)
      if (allAllowedKeysOfT.includes(key)) {
        result[key as K] = allowedParamsCandidate[key];
      }
  }
  return result as T;
};

const additionalParameters = mergeParameters<SearchParameters, keyof SearchParameters>(
  searchParametersKeys as unknown as string[],
  { session: 'kjn33fbf4fkl3w3ff3f3ffuu', id: '345644783' },
  { searchInput: 'stackoverflow', anotherParam: 'should not be here' }
);

console.log(JSON.stringify(additionalParameters, null, 2))

Hope that could help

  • Related