Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mapped types: remove private interface

In TypeScript, private attributes are considered part of the shape (or interface) of a type.

class Person {
  constructor(private name: string, public age: number) { }
}
const p: Person = { age: 42 };
// Error: Property 'name' is missing.

This is valid, because TypeScript needs to keep track of privates.

class Person {
  constructor(private name: string, public age: number) { }
  equals(other: Person) {
    return this.name === other.name && this.age === other.age;
    // This is valid, because TypeScript kept track of the private `name` property!
  }
}

However, there usually you want to ignore the private interface. For example, when you're using dependency injection and unit testing.

class HttpClient {
   constructor(private log: Logger) {
   }
   async doGet(url: string) { 
      return (await fetch(url)).json();
   }
}

class MyService {
  constructor(private http: HttpClient) {
  }
  // Implementation
}

// Unit test for MyService:
describe('MyService', () => {
  it('should work', () => {
    const httpMock: HttpClient = { // ERROR: Property 'log' is missing
      doGet(url: string) {
         return Promise.resolve({ name: 'Han' });
      }
    };
    const sut = new MyService(httpMock);
  });
});

I know we could solve this by adding an interface IHttpClient which describes the public interface for an HttpClient and use that instead of the class type directly, but that is a lot of work and it need to be kept in sync manually.

Is there a way to remove all non-public properties from a type using a mapped type?

Something like:

type PublicInterface<T> = {
    [P in PublicNames<T>]: T[P];
}

So it could be used in places where you do not care about privates:

class MyService {
  constructor(private http: PublicInterface<HttpClient>) {
  }
  // Implementation
}
like image 914
nicojs Avatar asked Apr 01 '26 19:04

nicojs


1 Answers

keyof is smart enough to peek only public keys:

class Person {
  constructor(private name: string, public age: number) { }
}

type PublicInterface<T> = {
    [P in keyof T]: T[P];
}

const p: PublicInterface<Person> = { age: 42 }; // no error

Playground


Or even shorter with Pick utility (the result is equivalent to above mapped type):

type PublicInterface<T> = Pick<T, keyof T>;

Playground

like image 50
Aleksey L. Avatar answered Apr 03 '26 17:04

Aleksey L.