'How to convert array with properties to typed object

Here is a MWE:

type Obj = {n: number}
type Arr = [number] & Obj

const arr = [1] as Arr
arr.n = 1;

console.log(arr) // [1]
console.log(arr as Obj) // [1]
const b = {};
Object.assign(b, arr);
console.log(b as Obj) // {"0": 1, "n": 1}

// expected result: {n: 1}

Playground Link

In other words, when receiving a type Arr I'd like to be able to cast it to Obj.

I'm looking for an automatic cast, ie a solution only using types and high-level functions, not based on this specific type. In other words

b = {n: arr.n}

is not acceptable but I would also be pleased with something like

b = {k: arr[k] for k in Obj} // does not make sense but just to illustrate the diff with above

Some more context

In my problem the type I'm receiving (using ethers.js) is always like an array with named properties that are exactly the same as the one in the array, but with a name. In my test (using chai=4.3.4), I don't want to do

expect({n: arr.n}).to.deep.eq(expectedValue)

for each property.

For example, function signature could be

  tokenMetadata(
    pointer: string,
    items: BigNumberish[],
    overrides?: CallOverrides
  ): Promise<
    [
      string,
      string,
      string,
      ([string, string] & { trait_type: string; value: string })[]
    ] & {
      image: string;
      description: string;
      name: string;
      attributes: ([string, string] & { trait_type: string; value: string })[];
    }
  >

and the arrayish part of it just bothers me



Solution 1:[1]

It sounds like at runtime you want to extract the non-array properties of an augmented array so you can use them with:

expect(theObjectPartsOnly).to.deep.eq(expectedValue)

...without having to write each one out individually:

// Not like this:
expect({x: arr.x}).to.deep.eq(expectedValue)
expect({y: arr.y}).to.deep.eq(expectedValue)
expect({z: arr.z}).to.deep.eq(expectedValue)

Since the type of the object probably isn't important at that point, I think I'd probably not worry about the TypeScript part and just handle it at the runtime level, something like this:

const rexDigits = /^\d+$/;
const check = Object.fromEntries(
    Object.entries(arr).filter(([key]) => !rexDigits.test(key))
);
expect(check).to.deep.equal(expectedValue);

That filters out the array index properties (roughly speaking, that's not an exact match for the definition of an array index but it's probably good enough for our purposes here) before doing the to.deep.equal check.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 T.J. Crowder