最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

typescript - How to type a function to preserve input "arrayness" in the output? - Stack Overflow

programmeradmin1浏览0评论

How to type a function (in typescript) so that the input "arrayness" preserves in the output.

If input is array, output is array too. If input is a single value, output is a single value too.

Now there's an error saying that the output type can be both an array or a single value, and one of the options doesn't match. However it's clear what that type should be. How can it be expressed?

TS Playground

interface Target {
    src: string | string[];
    dest: string;
}

const forwardSlash = (s: string) => s.replace(/\\/g, '/');
const fixTargetPathsForViteStaticCopyOnWindows = (targets: Target[]) => {
  const transformListOrSingleValue = <T,>(x: T | T[], f: (a: T) => T): T | T[] => {
    return Array.isArray(x) ? x.map(f) : f(x);
  };

  const fixSingleTarget = (target: Target): Target => {
    return {
      ...target,
      src: transformListOrSingleValue(target.src, forwardSlash),
      dest: transformListOrSingleValue(target.dest, forwardSlash), //Type 'string | string[]' is not assignable to type 'string'.
    };
  };

  return targets.map(fixSingleTarget);
};

The error:

Type 'string | string[]' is not assignable to type 'string'.
  Type 'string[]' is not assignable to type 'string'.ts(2322)
index.d.ts(28, 5): The expected type comes from property 'dest' which is declared here on type 'Target

PS Although this variation resolves my problem,

const forwardSlash = (s: string) => s.replace(/\\/g, '/');
const transformTargetsToForwardSlashFormat = (targets: Target[]) => {
  const transformListOrSingleValue = <T>(x: T | T[], f: (a: T) => T): T | T[] => {
    return Array.isArray(x) ? x.map(f) : f(x);
  };

  const fixSingleTarget = (target: Target): Target => {
    return {
      ...target,
      src: transformListOrSingleValue(target.src, forwardSlash),
      dest: forwardSlash(target.dest),
    };
  };

  return targets.map(fixSingleTarget);
};

I still would like to know if it's doable in the initial way.

How to type a function (in typescript) so that the input "arrayness" preserves in the output.

If input is array, output is array too. If input is a single value, output is a single value too.

Now there's an error saying that the output type can be both an array or a single value, and one of the options doesn't match. However it's clear what that type should be. How can it be expressed?

TS Playground

interface Target {
    src: string | string[];
    dest: string;
}

const forwardSlash = (s: string) => s.replace(/\\/g, '/');
const fixTargetPathsForViteStaticCopyOnWindows = (targets: Target[]) => {
  const transformListOrSingleValue = <T,>(x: T | T[], f: (a: T) => T): T | T[] => {
    return Array.isArray(x) ? x.map(f) : f(x);
  };

  const fixSingleTarget = (target: Target): Target => {
    return {
      ...target,
      src: transformListOrSingleValue(target.src, forwardSlash),
      dest: transformListOrSingleValue(target.dest, forwardSlash), //Type 'string | string[]' is not assignable to type 'string'.
    };
  };

  return targets.map(fixSingleTarget);
};

The error:

Type 'string | string[]' is not assignable to type 'string'.
  Type 'string[]' is not assignable to type 'string'.ts(2322)
index.d.ts(28, 5): The expected type comes from property 'dest' which is declared here on type 'Target

PS Although this variation resolves my problem,

const forwardSlash = (s: string) => s.replace(/\\/g, '/');
const transformTargetsToForwardSlashFormat = (targets: Target[]) => {
  const transformListOrSingleValue = <T>(x: T | T[], f: (a: T) => T): T | T[] => {
    return Array.isArray(x) ? x.map(f) : f(x);
  };

  const fixSingleTarget = (target: Target): Target => {
    return {
      ...target,
      src: transformListOrSingleValue(target.src, forwardSlash),
      dest: forwardSlash(target.dest),
    };
  };

  return targets.map(fixSingleTarget);
};

I still would like to know if it's doable in the initial way.

Share Improve this question edited Feb 3 at 15:50 danronmoon 3,8735 gold badges35 silver badges58 bronze badges asked Feb 1 at 23:18 yevtyevt 8162 gold badges8 silver badges23 bronze badges 8
  • 1 Please edit the code to be a self-contained minimal reproducible example without external/private/undeclared things. Like, what's Target? Is that something we need for this to make sense and for us to see the same error you're seeing? If so, please provide it. If not, please replace with something else. – jcalz Commented Feb 1 at 23:54
  • @jcalz Fixed. Added playground link – yevt Commented Feb 3 at 15:27
  • I don't quite understand why you don't just call forwardSlash(target.dest) as mentioned... but if you need to represent the "array-in-array-out" in the call signature it's going to be more complicated. TS can't analyze the body of the function to determine this, so you'll have to manually curate the call signature either as a generic with a lot of conditional types, or as a series of overloads covering all the possible cases of how it's called. This playground link shows both approaches. Does that fully address the q? If so I'll write an a; if not, what's missing? – jcalz Commented Feb 3 at 16:29
  • @jcalz Great job first of all! And I did as mentioned, I'm just curious Line14: T[number][] how we got to the second dimension. Even if this is just a typo, why it still works if i remove []? Lines34-37: Why we need to cover all 3 cases: T, T[], T | T[] (i tried similar approach and it seemed like first two cases should be enough). Why lines 36 and 37 are duplicated? Also, how do we choose which variation of overload to use as a function declaration? Looking forward for you a's – yevt Commented Feb 3 at 23:04
  • It doesn't "work" if you remove [], the output will always be string that way, never string[], which is apparently fine for src and fine for dest, but wouldn't be fine if you had an array-only type as shown here. Do you need the other questions answered before you can be sure if my suggestion fully addresses the question? Or can I just write up an actual answer which touches on these points. – jcalz Commented Feb 3 at 23:38
 |  Show 3 more comments

1 Answer 1

Reset to default 1

If you want the return type of a function to depend on whether or not one of its inputs is an array, you will either need to use conditional types with generics, or you will need to use overloads to represent the different allowable ways to call the function. Note that all of this is more complex than just having separate code paths for arrays and non-arrays. Trying to put those two fundamentally distinct operations in one function involves type juggling.


Here's how you'd use generics with conditional types:

declare function transformListOrSingleValue<T>(
  x: T,
  f: (a: T extends readonly any[] ? T[number] : T) => T extends readonly any[] ? T[number] : T
): T extends readonly any[] ? T[number][] : T; 

Here T always corresponds to the type of the x input. If it is an array, then the conditional types of the form T extends readonly any[] ?

发布评论

评论列表(0)

  1. 暂无评论