Home > Enterprise >  Type 'undefined' cannot be used as an index type using Generic type
Type 'undefined' cannot be used as an index type using Generic type

Time:09-26

Am trying to type a function that update a pair key value by a given path, am using Generics type for the object, because i do not know what type in advance and ive using also Generic type for the parameter value , but i can't figure out why ive got an error message : Type 'undefined' cannot be used as an index type and No overload matches this call:

const updateObjetValueByPath = <A, B>(obj: { [key: string]: A }, path: string, value: B) => {
  const keys = path.split('.')
  const lastKey = keys.pop()
  const lastObj = keys.reduce((obj, key) => obj[key], obj)
  lastObj[lastKey] = value
}

Example :

const obj = {
  firstName:'Taha',
  lastName: 'Az',
  work:{
    company:'Whitoo',
    contrat:{
      lib:'CDI',
    }
  }
}
updateObjetValueByPath(obj,'work.contrat.lib','Freelance')

enter image description here

CodePudding user response:

Type signature of your function is not safe, it allows you to use any path, even foo.bar.baz fro any object, which is not safe.

Please see this example:


type Foo = {
  user: {
    description: {
      name: string;
      surname: string;
      age: number
    }
  }
}

declare var foo: Foo;

type Primitives = string | number | symbol;

type Values<T> = T[keyof T]

type Elem = string;

type Acc = Record<string, any>

// (acc, elem) => hasProperty(acc, elem) ? acc[elem] : acc
type Predicate<Accumulator extends Acc, El extends Elem> =
  El extends keyof Accumulator ? Accumulator[El] : Accumulator

type Reducer<
  Keys extends Elem,
  Accumulator extends Acc = {}
> =
  Keys extends `${infer Prop}.${infer Rest}`
  ? Reducer<Rest, Predicate<Accumulator, Prop>>
  : Keys extends `${infer Last}`
  ? Predicate<Accumulator, Last>
  : never


const hasProperty = <Obj, Prop extends Primitives>(obj: Obj, prop: Prop)
  : obj is Obj & Record<Prop, any> =>
  Object.prototype.hasOwnProperty.call(obj, prop);

type KeysUnion<T, Cache extends string = ''> =
  T extends Primitives ? Cache : {
    [P in keyof T]:
    P extends string
    ? Cache extends ''
    ? KeysUnion<T[P], `${P}`>
    : Cache | KeysUnion<T[P], `${Cache}.${P}`>
    : never
  }[keyof T]

type O = KeysUnion<Foo>

type ValuesUnion<T, Cache = T> =
  T extends Primitives ? T : Values<{
    [P in keyof T]:
    | Cache | T[P]
    | ValuesUnion<T[P], Cache | T[P]>
  }>


function updateObjetValueByPath<Obj, Keys extends KeysUnion<Obj>>
  (obj: ValuesUnion<Obj>, path: Keys & string, value: Reducer<Keys, Obj & Acc>) {
  const keys = path.split('.')
  const lastKey = keys.pop()!
  const lastObj = keys.reduce((o, key) => o[key], obj as Record<string, {}>)
  lastObj[lastKey] = value
}

/**
 * Ok
 */

const result1 = updateObjetValueByPath(foo, 'user.description.name', 'John') // ok
const result2 = updateObjetValueByPath(foo, 'user.description.age', 2) // ok

/**
 * Expected errors
 */
const result3 = updateObjetValueByPath(foo, 'surname', 'Doe') // expected error, path is not full
const result4 = updateObjetValueByPath(foo, 'user.description.name', 42) // expecte derror, it should be a string

Playground

If you are interested in explanation please see my article and my answer1 , answer2, answer3, answer4

As for missing link, please see links on the very bottom of my article https://catchts.com/deep-pick.

P.S. "add comment" button does not work on my mobile phonr

  • Related