lodash - Er det mulig å typisere _.invert presist i TypeScript?
I lodash inverterer _.invert-funksjonen et objekts nøkler og verdier:
var object = { 'a': 'x', 'b': 'y', 'c': 'z' };
_.invert(object);
// => { 'x': 'a', 'y': 'b', 'z': 'c' }
Lodash-typene erklærer for øyeblikket dette til alltid å returnere en string→string-mapping:
_.invert(object); // type is _.Dictionary<string>
Men noen ganger, spesielt hvis du bruker en const-påstand, ville en mer presis type være passende:
const o = {
a: 'x',
b: 'y',
} as const; // type is { readonly a: "x"; readonly b: "y"; }
_.invert(o); // type is _.Dictionary<string>
// but would ideally be { readonly x: "a", readonly y: "b" }
Er det mulig å få typene så presise? Denne erklæringen kommer nær:
declare function invert<
K extends string | number | symbol,
V extends string | number | symbol,
>(obj: Record<K, V>): {[k in V]: K};
invert(o); // type is { x: "a" | "b"; y: "a" | "b"; }
Nøklene er riktige, men verdiene er foreningen av inngangsnøklene, det vil si at du mister spesifisiteten i koblingen. Er det mulig å få dette perfekt?
Svar #1
Rediger
Siden innføringen av as-setningen i kartlagte typer kan du skrive denne typen som:
Du kan bruke en kartlagt type med en as-klausul:
type InvertResult<T extends Record<PropertyKey, PropertyKey>> = {
[P in keyof T as T[P]]: P
}
Lekeromlenke
Opprinnelig svar
Du kan gjøre det ved å bruke en mer komplisert kartlagt type som bevarer riktig verdi:
const o = {
a: 'x',
b: 'y',
} as const;
type AllValues<T extends Record<PropertyKey, PropertyKey>> = {
[P in keyof T]: { key: P, value: T[P] }
}[keyof T]
type InvertResult<T extends Record<PropertyKey, PropertyKey>> = {
[P in AllValues<T>['value']]: Extract<AllValues<T>, { value: P }>['key']
}
declare function invert<
T extends Record<PropertyKey, PropertyKey>
>(obj: T): InvertResult<T>;
let s = invert(o); // type is { x: "a"; y: "b"; }
Lekeromlenke
AllValues lager først en union som inneholder alle key, value-par (så for eksemplet ditt vil dette være { key: "a"; value: "x"; } | { key: "b"; value: "y"; }). I den kartlagte typen mapper vi deretter over alle value-typene i unionen, og for hver value henter vi den opprinnelige key ved hjelp av Extract. Dette vil fungere godt så lenge det ikke finnes dupliserte verdier (hvis det finnes dupliserte verdier vil vi få en union av nøklene der verdien forekommer)
Svar #2
Løsningen av Titian Cernicova-Dragomir er virkelig kul. I dag fant jeg et annet alternativ for å bytte objektets nøkler og verdier med betingede typer:
type KeyFromValue<V, T extends Record<PropertyKey, PropertyKey>> = {
[K in keyof T]: V extends T[K] ? K : never
}[keyof T];
type Invert<T extends Record<PropertyKey, PropertyKey>> = {
[V in T[keyof T]]: KeyFromValue<V, T>
};
Test med const o:
const o = {
a: "x",
b: "y"
} as const;
// type Invert_o = {x: "a"; y: "b";}
type Invert_o = Invert<typeof o>;
// works
const t: Invert<typeof o> = { x: "a", y: "b" };
// Error: Type '"a1"' is not assignable to type '"a"'.
const t1: Invert<typeof o> = { x: "a1", y: "b" };
Deklarer invert-funksjonen på samme måte som i svaret over med returtype Invert<T>.
Lekeplass
