Developing a secure private class member access function in TypeScript

One interesting feature of TypeScript is its ability to access instance properties and methods that have been declared as `private instanceProperty`, but not explicitly as `#instanceProperty`.

Despite this, TypeScript still performs type checking on this access, for example:

class SomeClass {
  private __someProperty: string;

  notPrivate: number;

  constructor() {
    this.__someProperty = "foo";
    this.notPrivate = 7;
  }
}

const instance = new SomeClass();

console.log(instance.notPrivate); // 7
console.log(instance.__someProperty); // compile error
// Property '__someProperty' is private and only accessible within class 'SomeClass'.

console.log(instance["notPrivate"]); // 7
console.log(instance["__someProperty"]); // "foo"
console.log(instance["nonExistentProp"]); // compile error w/ noImplicitAny
// Element implicitly has an 'any' type because expression of type
// '"nonExistentProp"' can't be used to index type 'SomeClass '.
// Property 'nonExistentProp' does not exist on type 'SomeClass '.

Due to this behavior, I began experimenting with a method to expose private properties explicitly. This is where I started:

export function unsafe_expose<Instance, InstanceProperty extends keyof Instance>(
  instance: Instance,
  property: InstanceProperty,
) {
  return instance as unknown as Omit<Instance, InstanceProperty> & {
    [key in InstanceProperty]: Instance[InstanceProperty];
  };
}

However, when attempting to use this function, I encountered an error:

const unprivate = unsafe_expose(instance, "__someProperty");
// Argument of type '"__someProperty"' is not assignable to parameter of type 'keyof SomeClass'.

This led me to realize that the type constraint for `Type["key"]` is broader than `keyof Type`, and I am wondering if there is a way to access that broader constraint within the TypeScript type system.

This was primarily a theoretical experiment and is not intended for production code at this time, so I would consider "Tl;Dr no it's not possible" to be a valid response.

Answer №1

Currently, the ability to iterate over private class members using the keyof operator is not supported. Requests for this feature, such as microsoft/TypeScript#22677 and microsoft/TypeScript#46802, have been made but have not been implemented in the language yet.

However, you can use indexed access types to access the types of these members. Here is an example of how you can structure your function:

function unsafe_expose<T, K extends PropertyKey>(
  instance: T,
  property: K,
) {
  return instance as unknown as Omit<T, K> & {
    [P in K]: (T & { [k: PropertyKey]: unknown })[K];
  };
}

It is important to note that due to the constraint on K not being limited to keyof T, accessing T[K] will result in an error. To avoid this error without using drastic measures like the //@ts-ignore directive, you can index into T & {[k: PropertyKey]: unknown}, allowing indexing with every possible key, leading to T[K] & unknown, which is equivalent to T[K].

This approach demonstrates the desired functionality as shown below:

const unprivate = unsafe_expose(instance, "__someProperty");
/* const unprivate: Omit<SomeClass, "__someProperty"> & {
    __someProperty: string;
} */

unprivate.__someProperty; // string

Additionally, this method allows access to non-existent properties:

const oops = unsafe_expose(instance, "oopsie");
/* const oops: Omit<SomeClass, "oopsie"> & {
    oopsie: unknown;
} */

The use of unknown as a type in this context is appropriate, as it restricts operations and correctly assigns the unknown type to T[K] when K is not a recognized key of T. While a conditional type could potentially restrict access to truly non-existent properties, it may be beyond the scope of a function designed to penetrate abstraction barriers.

A playground link to the code is available here.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Access Element in Array by Type using TypeScript

Within a TypeScript project, there exists an array of containers that possess a type attribute along with supplementary data based on their individual types. type Container<Type extends string> = { type: Type; } type AContainer = Container<" ...

Error: In Typescript, it is not possible to assign the type 'false' to type 'true'

Currently, I am exploring Angular 2 and encountered a situation where I set the variable isLoading to true initially, and then switch it to false after fetching required data. However, upon executing this process, I encountered the following error message: ...

How can one utilize JSON.parse directly within an HTML file in a Typescript/Angular environment, or alternatively, how to access JSON fields

Unable to find the answer I was looking for, I have decided to pose this question. In order to prevent duplicates in a map, I had to stringify the map key. However, I now need to extract and style the key's fields in an HTML file. Is there a solution ...

Is there a way to remove trigger characters in vscode api completion function?

I am developing a vscode extension that requires custom completion for json files. I would like to know if it is possible to hide the trigger character when using autocompletions. Let me explain further : Imagine the trigger character is '.' In ...

Uploading an image in Typescript on IE11 using Angular 4

I'm having an issue uploading an image to my web application using Angular 4. I convert the input file using readAsBinaryString() and extract the ASCII code using btoa() before passing it to a backend service. While this process works smoothly on Chro ...

Caution in NEXTJS: Make sure the server HTML includes a corresponding <div> within a <div> tag

Struggling with a warning while rendering pages in my Next.js and MUI project. Here's the code, any insights on how to resolve this would be greatly appreciated! import "../styles/globals.scss"; import { AppProps } from "next/app"; ...

Is there a way to make Typescript accept dot notation?

Is there a way to suppress the compile time error in Typescript when using dot notation to access a property that the compiler doesn't know about? Even though using bracket notation works, dot notation would be much more readable in my specific case. ...

Vue.js 3 with TypeScript is throwing an error: "Module 'xxxxxx' cannot be located, or its corresponding type declarations are missing."

I developed a pagination plugin using Vue JS 2, but encountered an error when trying to integrate it into a project that uses Vue 3 with TypeScript. The error message displayed is 'Cannot find module 'l-pagination' or its corresponding type ...

When I select a link on the current page, I would like the information in the input fields to be cleared

Currently using Angular 8, I recently included onSameUrlNavigation: 'reload' to my router. This change has successfully allowed the page to reload upon a second click on the same link. However, I've noticed that the input fields on the reloa ...

Error message: "Angular requires either importing or local installation"

For my ionic application development, I encountered an issue while trying to link pages together in the ionic creator. The error message on the .ts file is: typescript: src/pages/home/home.ts, line: 4 Individual declarations in merged declar ...

Include additional information beyond just the user's name, profile picture, and identification number in the NextAuth session

In my Next.js project, I have successfully integrated next-auth and now have access to a JWT token and session object: export const { signIn, signOut, auth } = NextAuth({ ...authConfig, providers: [ CredentialsProvider({ async authorize(crede ...

Encountering error "module fibers/future not found" while creating a meteor method in typescript

While working on a Meteor method for async function in my project that combines Meteor with Angular2 using Typescript ES6, I encountered an error. The issue is related to a sync problem in the insert query when inserting data with the same name. To resolve ...

`The utilization of a collective interface/data type within an Angular application`

I created a HeaderComponent that requires an object with the structure of {title: string, short_desc: string} as its input property. @Component({ selector: 'header', templateUrl: './header.component.html', styleUrls: ['./hea ...

Angular2 - the pipe was not located while organizing records

I've successfully fetched data from an API and displayed it in the view, but I'm struggling to organize the data by date. Whenever I attempt to do so, I encounter this error message: The pipe 'groupBy' could not be found pipe.ts impor ...

Combining various POST requests by matching the common value in each array. (Angular)

Here are the two different sets of data: "statusCode": 200, "data": [ { "color": { "id": "1111", "name": null, "hex&quo ...

Issue with react router v6: Component fails to render even though route has been changed

The router seems to be experiencing an issue where it does not render a component. Specifically, on the home page, the Private Route is only rendered once. Clicking on a NavLink changes the URL to "/agreements", but the component itself is not being render ...

Is it possible for prettier to substitute var with let?

One of the tools I utilize to automatically format my Typescript code is prettier. My goal is to find out if there is a way to have prettier replace all instances of 'var' with 'let' during the formatting process. Below is the script I ...

The attribute 'selectionStart' is not a valid property for the type 'EventTarget'

I'm currently utilizing the selectionStart and selectionEnd properties to determine the beginning and ending points of a text selection. Check out the code here: https://codesandbox.io/s/busy-gareth-mr04o Nevertheless, I am facing difficulties in id ...

What is the best way to develop a function that can take in either a promise or a value and output the same type as the input parameter?

I'm currently working on a TypeScript function that can handle either an A type or a Promise<A>. In this case, A represents a specific concrete type, and the function should return the same type. If it receives a Promise<A>, then the retur ...

Having trouble accessing the useState hook in React context value with TypeScript

export const TeamMemberContext = createContext<TeamMembersList[] | null>(null); export const TeamMemberProvider = ({ children }) => { const [teamMemberList, setTeamMemberList] = useState<TeamMembersList[] | null>(null); useEffect(( ...