Skip to content

Interpolated Key Names in Mapped Objects #39350

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
5 tasks done
bradennapier opened this issue Jun 30, 2020 · 3 comments
Closed
5 tasks done

Interpolated Key Names in Mapped Objects #39350

bradennapier opened this issue Jun 30, 2020 · 3 comments
Labels
Duplicate An existing issue was already created

Comments

@bradennapier
Copy link

bradennapier commented Jun 30, 2020

Search Terms

Suggestion

So I run into this quite a bit. While usually you can just work around this and type generally, I think enough libraries have situations where this makes sense that it would make for a powerful feature. Essentially the idea is to allow something similar to template strings when defining a type.

I am sure this potentially may have implications that are larger than I am picturing, but the issue comes up whenever a library provides the ability to use strings in unconventional ways.

Use Cases

For example, the Intercom API defines the ability to search contacts by location.country or custom_attributes.${attribute_name}. Potentially if thought out further this could also be expanded to properly handling cases where a string like path.to.key is used in functions such as lodash or similar - but potentially the ability to use this simply opens the door to being type safe across many other interfaces and apis.

Starting with simple interpolation, perhaps including via (or exclusively during) mapped objects.

This seems like it theoretically wouldn't be that difficult to implement (just allow interpolation during the mapping of keys) and would fix a number of situations that would otherwise not be type safe at all.

I've run into quite a few cases where this pattern would provide a solution, although I am having a hard time remembering exactly. I know there were cases in React, Lodash, ElasticSearch and a number of others. If others have examples it would be great if you could comment as well. I will update as I find the other examples where this would fix.

Examples

Basic Example - Replacement / Substitution

type Example = {
  one: string;
  [`some.*`]: any
}

The above would solve the simple cases where we dont know how to define the types of keys that start this way but only those can be any rather than needing to change the type in this case to { [key: string]: any }

Most Powerful Option - Full Interpolation

There are a number of considerations:

Given the following for Contact model:

// shortened a bit for simplicity
export type Contact<
  ATTR extends { [key: string]: any }
> = {
  external_id: string;
  /** The contact's email */
  email: string;
  /** An object showing location details of the contact. */
  location: Location;
  tags: Tag[];
  segments: Segment[];
  /** The custom attributes which are set for the contact. */
  custom_attributes: ATTR;
};

Now if we need define the type Intercom allows searching by a number of keys plus some interpolated keys potentially, one of these is custom_attributes.${key} and the other is location.${key}:

It would seem the best way to do this and perhaps exclusively allowed way would be with mapped objects:

type SearchableAttributes<O extends { [key: string]: any }> = {
  [`custom_attributes.${K in keyof O}`]: O[K]
}

type SearchableLocation = {
  [`location.${K in keyof Location}`]: Location[K]
}

type SearchableContact<
  ATTR extends { [key: string]: any }, 
  C extends Contact<ATTR> = Contact<ATTR>
> = 
  Omit<C, 'custom_attributes' | 'location' | 'tags' | 'segments'> 
  & SearchableAttributes<C['custom_attributes']> 
  & SearchableLocation 
  & {
      tag_id: Tag['id'];
      segment_id: Segment['id'];
   };

Potentially even allowing a extendable version of this, but would probably be a bigger change

type MappedProperty<PREFIX extends string, O extends { [key: string]: any }> = {
   [`${PREFIX}.${K in keyof O}`]: O[K]
}

Since the above would probably need to indicate that string must be const this may be more difficult...

type MappedProperty<PREFIX extends const, O extends { [key: string]: any }> = {
   [`${PREFIX}.${K in keyof O}`]: O[K]
}

type SearchableLocation = MappedProperty<'location', Contact['location]>

Checklist

My suggestion meets these guidelines:

  • This wouldn't be a breaking change in existing TypeScript/JavaScript code
  • This wouldn't change the runtime behavior of existing JavaScript code
  • This could be implemented without emitting different JS based on the types of the expressions
  • This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, etc.)
  • This feature would agree with the rest of TypeScript's Design Goals.
@bradennapier bradennapier changed the title Interpolated / Template Types Interpolated Key Names in Mapped Objects Jun 30, 2020
@MartinJohns
Copy link
Contributor

Related / duplicate: #12754 and #6579.

@bradennapier
Copy link
Author

Related / duplicate: #12754 and #6579.

Ahh ok, thanks! I was surprised it wasn't requested but wasn't sure what to search for, thanks @MartinJohns

@RyanCavanaugh RyanCavanaugh added the Duplicate An existing issue was already created label Jun 30, 2020
@typescript-bot
Copy link
Collaborator

This issue has been marked as a 'Duplicate' and has seen no recent activity. It has been automatically closed for house-keeping purposes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Duplicate An existing issue was already created
Projects
None yet
Development

No branches or pull requests

4 participants