0

I have a data structure that contains various optional fields with different types that have to be fetched individually and formatted. The formatting functions take concrete values.

I tried doing something like the following minimal working example, but it errors with with Property 'value' does not exist on type 'Data[P]'.

I understand why the problem happens, but I want to make this work. I quick hack would be to cast data[idx][key] as Optional<T>, but can this be done without type casts?

// In my code this is more complex, a simple `T | undefined` doesn't work.
type Optional<T> = { kind: 'none' } | { kind: 'value', value: T};

interface Data {
  name: Optional<string>;
  id: Optional<number>;
}

function computeIndex(): number {
  // Some logic here
  return 0;
}

// Format field `key` of `data` using `formatter`.
// Use default `def` if the value is `none`.
function printField<
  P extends keyof Data,
  T extends Data[P] extends Optional<infer V> ? V : never
>(data: Data[], key: P, formatter: (val: T) => string, def: T): string {
  const idx = computeIndex();

  const field = data[idx][key];

  switch (field.kind) {
    case 'none':
      return formatter(def);

    case 'value':
      return formatter(field.value);
  }
}

1 Answer 1

1

I don't believe there is a cast-free solution at present.

Ultimately, TS seems to have trouble inferring Optional<string | number> from Optional<string> | Optional<number> in the field declaration, so we give it the extra nudge in the form of the Optional<T> cast.

Is this a hack? If you view it as working around the TS checker's inference limitations, maybe. I would agree it was a hack if it opened the door to mistyping values, or if the result didn't work as well as desired

But the end result works as well as a "non-hack" solution, in that it correctly type-checks formatter given key. And I don't see how the cast could result in the wrong type of value making it through.

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks for the answer! I decided to not waste any more time on this and just use a cast :D

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.