#StackBounty: #typescript Forcing a discriminated union match

Bounty: 500

Scenario

I’m a typescript beginner trying to port the mapProps function below to typescript

const addOne = x => x + 1
const upperCase = x => x.toUpperCase()

const obj = {
  entry: 'legend',
  fauna: {
    unicorns: 10,
    zombies: 3
  },
  other: {
    hat: 2
  }
}

const fns = {
  entry: upperCase,
  fauna: {
    unicorns: addOne
  },
  other: obj => ({
      hat: addOne(obj.hat)
  })
}

const mapProps = (obj, fns) => 
    Object.keys(fns).reduce(
        (acc, cur) => ({
            ...acc,
            [cur]: fns[cur] instanceof Function
                ? fns[cur](obj[cur])
                : mapProps(obj[cur], fns[cur])
        }),
        obj
    )

mapProps(obj, fns) 
// ​​​​​{ entry: 'LEGEND',​​​​​ fauna: { unicorns: 11, zombies: 3 },​​​​​ other: { hat: 3 } }​​​​​

Current attempt

type MapPropFuncsOf<T> = {
    [P in keyof T]?: ((x:T[P]) => T[P]) | MapPropFuncsOf<T[P]>
}

const mapProps = <T>(obj:T, fns: MapPropFuncsOf<T>) => 
    (Object.keys(fns) as (keyof T)[]).reduce(
        (acc, cur) => ({
            ...acc,
            [cur]: fns[cur] instanceof Function
                ? fns[cur]!(obj[cur]) // compiler warning
                : mapProps(obj[cur], fns[cur]) // compiler warning
        }),
        obj
  )

Issues

Even though I have checked that fns[cur] instanceof Function I can’t seem to call fns[cur], the typescript compiler complains with

Cannot invoke an expression whose type lacks a call signature. Type
‘MapPropFuncsOf | ((x: T[keyof T]) => T[keyof T])’ has no
compatible call signatures.

The mapProps(obj[cur], fns[cur]) call also fails with

Argument of type ‘MapPropFuncsOf | ((x: T[keyof T]) =>
T[keyof T]) | undefined’ is not assignable to parameter of type
‘MapPropFuncsOf’. Type ‘undefined’ is not assignable to
type ‘MapPropFuncsOf’.

Playground Link


Get this bounty!!!

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.