Home > Blockchain >  How to properly type a recursive function in TS?
How to properly type a recursive function in TS?

Time:09-22

I need to do a pretty simple task as crawl input arguments and stringify all Big Numbers (BN's) if there are any. I want to specify proper types but can't find a way to do that so currently my code looks like that

function stringifyBN(value: unknown): unknown {
  if (isBN(value)) {
    return value.toString();
  }

  if (Array.isArray(value)) {
    return value.map((v) => stringifyBN(v));
  }

  return value;
}

unknown basically does nothing regarding guarding from errors.

I tried the approach below since my input is either a primitive value or a recursive array of primitive values and it's always an array on the top level.

function stringifyBN(
  value: number | string | boolean | BN | number[] | string[] | boolean[] | BN[]
): number | string | boolean | number[] | string[] | boolean[] {
  if (isBN(value)) {
    return value.toString();
  }

  if (Array.isArray(value)) {
    return value.map((v) => stringifyBN(v));
  }

  return value;
}

but got an error

Type '(string | number | boolean | string[] | number[] | boolean[])[]' is not assignable to type 'string | number | boolean | string[] | number[] | boolean[]'.
  Type '(string | number | boolean | string[] | number[] | boolean[])[]' is not assignable to type 'string[]'.
    Type 'string | number | boolean | string[] | number[] | boolean[]' is not assignable to type 'string'.
      Type 'number' is not assignable to type 'string'.  TS2322

Any other approaches don't work neither since I get similar errors and already ran out of ideas. TS should basically help writing code instead creating troubles but I have the second option here.

Could someone help with that?

Thank you in advance!

CodePudding user response:

unknown basically does nothing regarding guarding from errors.

unknown forces you to have runtime checks on such values, which can already prevent many errors. It is definitely very useful. Do not confuse it with any which is the one doing nothing regarding guarding from errors.

Of course it might be possible to get a better type (although in your case I'm not fully convinced that it would be very helpful), but for that we need to know more about the input.

Is it always either a primitive value or an array of similarly types primitive values? Can it be an heterogeneous array? Can it be a tuple? Can it contain deeply nested arrays? Can it contain objects?

EDIT (answer after clarifications)

If it can be nested arrays, the type needs to be defined as a recursive type. Here is how you can do it:

// Represents the type of values of type T or potentially nested
// arrays of such values.
type NestedArray<T> = T | NestedArray<T>[];

function stringifyBN(
  value: NestedArray<number | string | boolean | BN>
): NestedArray<number | string | boolean> {
  [...]
}

CodePudding user response:

Without knowing any further requirements, I suspect that the input won't be ever an array of arrays something like below, and therefore the function doesn't need to be recursive in this case:

[
   '123',
   [
      [
          //...more nested arrays
      ] 
   ]
]

If that's so, you can move the checking for arrays outside the function. Here is the idea:

class BN {
    // dummy
}

function isBN(value: inputType): boolean {
    // dummy implementation
    return Math.random() > 0.49;
}


type outputType = string | number | boolean;
type inputType = outputType | BN;

function stringifyBN(value: inputType): outputType {
    if (isBN(value)) {
        return value.toString();
    }

    // type BN isn't part of the outputType
    if (typeof (value) !== typeof (BN)) {
        return value as outputType;
    }

    return ''
}

let value: inputType | inputType[] = []; //You can change this to be a single value instead of array.

if (Array.isArray(value)) {
    value.forEach(v => v = stringifyBN(v));
} else {
    value = stringifyBN(value);
}
  • Related