1

I'm trying create function, that allows me specify some objects that extend type Record<string, Record<string, number>>) and defaults values for some of the properties for some of the objects.

For example, for given inputs I need to construct specified types that will be later used in return value:

[ { obj: { test: { a: 1, b: 2 } }, defaults: {} } ] ---> { test: "a" | "b" }
[ { obj: { test: { a: 1, b: 2 } }, defaults: { test: "b" } } ] ---> { test?: "a" | "b" }
[
  { obj: { test: { a: 1, b: 2 } }, defaults: {} },
  { obj: { test2: { a: 2, d: 5 } }, defaults: { test2: "a" } }
] ---> { test: "a" | "b", test2?: "a" | "d" }

Basically, if for given object I specify default values for some properties, I want to make this properties optional in the resulting type.

The main problem is that I can't get typescript to narrow inferred type for the second argument(note that I'm not trying to generate resulting type yet, just trying to infer input type correctly to be able to construct it later):

type Idx<T, K> = K extends keyof T ? T[K] : never

const g = <
    Objs extends Record<string, Record<string, number>>[],
    Defaults extends { [I in keyof Objs]: { [K in keyof Objs[I]]: keyof Objs[I][K] } }
>(...props: 
    { [I in keyof Objs]: { obj: Objs[I], defaults: Idx<Defaults, I> } } &
    { [I in keyof Defaults]: { obj: Idx<Objs, I>, defaults: Defaults[I] } }
) => {}

g({
    obj: { test: { a: 1, b:2 } },
    defaults: {  }
})

This example code does not compile because Defaults is being inferred as [{test: "a" | "b" }] instead of [{}] It is heavily inspired by this anwer

I assume I'm making some simple mistake in the Defaults type but can't get it to work.

Niik
  • 23
  • 5
  • Is it just [this change](https://tsplay.dev/w6zXYw) you need to make? If so I could write up an answer explaining; if not, what am I missing? – jcalz Jun 25 '23 at 03:51
  • Yes, that appears to be working as expected – Niik Jun 25 '23 at 07:24

1 Answers1

1

If you want to be able to omit properties of the defaults properties (that is, allow {} instead of {test: "b"}, then you need to make sure that the elements of Defaults are Partial-like and give their keys the optionalizing "?" modifier when mapping:

Defaults extends { [I in keyof Objs]:
    { [K in keyof Objs[I]]?: keyof Objs[I][K] }
    // -----------------> ^ <------------------
}

Then things should work as expected:

g({ obj: { test: { a: 1, b: 2 } }, defaults: {} }); // okay
// const g: <[{ test: { a: number; b: number; };}], [{}]>

Playground link to code

jcalz
  • 264,269
  • 27
  • 359
  • 360