Auth Infant
Auth Infant

Reputation: 1975

How does one write the return type for a TypeScript method which either returns a given input or a member contained in the input?

If I have a function which will return either a provided value (if it is not a container) or the value within the container (if the provided value is a container), how do I properly declare the return type for that function? For example:

interface Container<TValue> {
  value: TValue;
}

declare function isContainer<TContained>(
  object: unknown
): object is Container<TContained>;

type ContainedTypeOrItself<T> = T extends Container<infer U> ? U : T

function getContainedValueOrSelf<T>(
  input: T,
): ContainedTypeOrItself<T> {
  if (isContainer<ContainedTypeOrItself<T>>(input)) {
    return input.value
  }

  return input
}

This approach does not appear to work because the TypeScript compiler raises an error for the return input statement:

Type 'T' is not assignable to type 'ContainedTypeOrItself<T>'.

Since I believe the if clause filters all objects of type Container, I would think that when the code gets to the return input statement, T is not a Container and so ContainedTypeOrItself<T> should just be typed of itself, T. Obviously my understanding of how this should work is not correct, given the compiler error.

In my other, related question, I learned why using the ternary operator with a careless use of any caused differing behavior from when using an if-else statement. However, I still guess I'm too dense to understand what is actually wrong with the code and I've read the comments and answers there multiple times. They did answer exactly the question asked there: why there was differing behavior between ternary operator and if-else and for that I am grateful -- it was exactly what I was looking for. However, in this question, I'm using a simpler example (thank you!) and am trying to be as specific as possible with the typing. I'm still have trouble wrapping my head around why it's not working, even when trying to apply the lessons learned about any usage, the ternary operator, and using the simplified code from that answer.

Thank you for taking the time to read this and for your help!

Upvotes: 0

Views: 27

Answers (1)

VRoxa
VRoxa

Reputation: 1051

I think this is a case of overengineering. Notice me if I didn't understand properly, and I will delete my answer.

Why I think your code is much complex than what it looks it tries to solve?
If your intention is to flat a contained object, the return type of the flat function should be always the raw type, T.
The use of the infer keyword complexifies your solution, so if the goal to achieve is to get a flat object T, don't try to return any other type than T.

Your type ContainedTypeOrItself<T> would be described as union like

type ContainedTypeOrItself<T> = Container<T> | T;

Then, the flat function would look like

const getContainedValueOrSelf = <T>(input: ContainedTypeOrItself<T>): T =>
  isContainer<T>(input)
  ? input.value
  : input;

And yes, ternary operation is farly supported and compiler understands perfectly.

A couple of lines show the flat behavior

const container: Container<number> = { value: 6 };

const decontained: number = getContainedValueOrSelf(container);
const raw: number = getContainedValueOrSelf(6);

It makes suspicious to me that what I am trying to answer looks quite simpler than your code. So, warn me if I missunderstood your question.

Upvotes: 1

Related Questions