#Struggling with method overloading

33 messages · Page 1 of 1 (latest)

vernal pasture
#

Hey,

I'm trying to implement a method that match the following :

test() {
this.get<ArrayBuffer>('/test'); // NOT OK: expect error -> expected 2 arguments, got 1 (responseType is missing)
this.get<ArrayBuffer>('/test', 'arraybuffer'); // OK

this.get<Blob>('/test'); // NOT OK: expect error -> expected 2 arguments, got 1 (responseType is missing)
this.get<Blob>('/test', 'blob'); // OK

this.get<string>('/test'); // NOT OK: expect error -> expected 2 arguments, got 1 (responseType is missing)
this.get<string>('/test', 'text'); // OK

this.get<{ test: string }>('/test', 'json'); // OK
this.get<{ test: string }>('/test'); // OK (responseType is optional, defaults to 'json')
}

public get<R, T = R extends ResponseTypeValues ? never : 'json'>(
  url: string,
  responseType?: T,
): Observable<R>;

public get<R, T = R extends ResponseTypeValues ? ResponseTypeKey<R> : never>(
  url: string,
  responseType: T,
): Observable<R>;

public get(url: string, responseType?: 'json' | ResponseTypeKeys): Observable<unknown> {
  return this.http.get(url, { headers: this.DEFAULT_HEADERS, responseType: (responseType ?? 'json') as 'json' });
}

Is there anything I do wrongly ? 🤔

(I'm on typescript 5.1)

sage barn
#

it's not possible

#

types don't exist at runtime

#

you're calling the same method with the same arguments in these 2 lines

this.get<string>('/test'); // NOT OK: expect error -> expected 2 arguments, got 1 (responseType is missing)
this.get<{ test: string }>('/test'); // OK (responseType is optional, defaults to 'json')
```these compile down to
```js
this.get('/test'); // NOT OK: expect error -> expected 2 arguments, got 1 (responseType is missing)
this.get('/test'); // OK (responseType is optional, defaults to 'json')
```so you want different outcomes for the same call, that's just not possible
#

you're looking at this kind of pattern:

#

!:thats-no%

mossy nebulaBOT
#
tjjfvi#0
`!t6:thats-no-generic-its-a-type-cast`:

You can use generics in objects, but usually they should be inferable from the structure of the object.

The classic example of a "misused generic" is a function like:

function getMeA<T>(): T {
   /* magic */
}

Since the generic has no relation to anything in the call signature, it can't be inferred and also there's no way to actually implement that function safely: it's not really a generic, it's a type-cast that's pretending to be a generic.

sage barn
#

which is not a very good pattern

vernal pasture
#

So I can't "request" a parameter depending of the generic type ? 😢

sage barn
#

you want this to occur in runtime?

vernal pasture
#

no, only for type safety

sage barn
#

sorry, typo'd

#

you want this to occur at compiletime*?

vernal pasture
#

yeah

sage barn
#

well you can't get both at once

vernal pasture
#

wait i think there is a quiproquo xd

sage barn
#

which then wouldn't make the first case error

#

ooh wait you could put the default in the body...

vernal pasture
#

👀

sage barn
#

or it's overloaded anyway, maybe that doesn't matter

#

because structural typing, Blob and ArrayBuffer are technically valid targets for 'json'

mossy nebulaBOT
#
that_guy977#0

Preview:```ts
function test() {
get<ArrayBuffer>("/test") // NOT OK: expect error -> expected 2 arguments, got 1 (responseType is missing)
get<ArrayBuffer>("/test", "arraybuffer") // OK

get<Blob>("/test") // NOT OK: expect error -> expected 2 arguments, got 1 (responseType is missing)
...```

sage barn
#

so this is best i got with your interface

#

if you change them to literal strings, you could get the desired results

sage barn
# mossy nebula

but it still is this pattern. you can just infer from the second argument instead of doing all this

#

you wouldn't need overloads, wouldn't need conditionals

coarse torrent
#

It seems you're attempting to enforce that certain types (ArrayBuffer, Blob, string) must have a specific corresponding responseType argument provided. To achieve this, you can use overloading and type constraints.

I don't see the type definitions for ResponseTypeValues, ResponseTypeKey, or ResponseTypeKeys in the provided code. I'll assume these are something you've defined elsewhere.

Based on the constraints you provided, I'll suggest an approach:

Use overloads to map specific return types to specific responseType values.
Provide a generic fallback signature for cases that don't match any of the overloads.
Here's a simplified example of what this could look like:

type JsonResponse = { test: string };

// Overloads for specific response types
public get(url: string, responseType: 'arraybuffer'): Observable<ArrayBuffer>;
public get(url: string, responseType: 'blob'): Observable<Blob>;
public get(url: string, responseType: 'text'): Observable<string>;
public get<R extends JsonResponse>(url: string, responseType?: 'json'): Observable<R>;

// Implementation must be compatible with all overloads
public get(url: string, responseType: 'arraybuffer' | 'blob' | 'text' | 'json' = 'json'): Observable<any> {
// ... implementation
}
Now:

Calling this.get<ArrayBuffer>('/test'); will cause an error because the responseType is missing.
Calling this.get<ArrayBuffer>('/test', 'arraybuffer'); will be valid.

The same rules apply for Blob, string, etc.
For the JSON type, since the responseType is optional, it will default to 'json' if not provided.

sage barn
#

please dont' use chatgpt answers

#

or any generative ai answers