(o: O): O | null;
validateObject({name: "Klaus"}); // OK
validateObject("Klaus"); // ERR: Argument of type 'string' is not
// assignable to parameter of type 'object'
```
* Beispiel: nur Objekte mit bestimmter Struktur erlaubt
* ```typescript
type Person = { firstname: string | null };
declare function getOrDefault(p: P): P;
getOrDefault( { } ); // ERR: Property 'firstname' is missing in type '{}'
// but required in type 'Person'
```
---
### Generics mit Union Typen
* Wenn man einen Union Type als Constraint angibt, muss der übergebene Typ einem
der im Union-Typen enthaltenen Typen entsprechen:
* ```typescript
type Color = "red" | "blue" | "green";
declare function bgColor (c: Color): { backgroundColor: C };
bgColor("red"); // OK
bgColor("white"); // Argument of type '"white"' is not
// assignable to parameter of type 'Color'
```
* ```typescript
type ListOfStringsOrBooleans = Array;
const c1: ListOfStringsOrBooleans = ["a"]; // Ok
const c2: ListOfStringsOrBooleans = [true]; // Ok
const c3: ListOfStringsOrBooleans = [true, "jo!"]; // Ok
const c4: ListOfStringsOrBooleans = [4]; // ERR Type 'number' does not satisfy
// the constraint 'string | boolean'
declare function validateNumberOrString(o: O): O | null
```
---
### Übung Generics
* Beschreibe eine Typsichere `createSetter`-Funktion. Die Funktion soll eine fiktive Setter-Funktion für ein Property eines
beliebigen Objektes zurückliefern.
* Fachliche Idee: in der Setter-Funktion können z.B. Überprüfungen durchgeführt werden oder die Setter-Funktion könnte den Wert in eine DB schreiben o.ä.
* Die Funktion soll zwei Parameter haben:
1. Ein beliebiges Objekt (`someObject`), 2. Den Namen eines Keys aus dem Objekt (`aKey`)
* Der Rückgabe-Typ soll eine Funktion sein, die ihrerseits ein Argument hat, das vom Typ des Properties aus dem übergebenen
Objekt (`someObject`) ist, so dass diese Funktion aufgerufen werden kann, um den Wert des Objektes zu setzen.
* In JavaScript sähe das so aus:
* ```javascript
function createSetter(someObject, aKey) { /* ... */ }
const ageSetter = createSetter({firstname: "Klaus", age: 32}, "firstname");
ageSetter(33);
```
* Die Übung kannst Du in `src/290_uebung_generics.ts` machen. Dort findest Du weitere Hinweise.
* Mögliche Lösungen findest Du in `src/290s_uebung_generics.ts` (`s` wie `solution`)
---
## Mapped Types
---
### Mapped Types
* Mit einem [Mapped Type](https://www.typescriptlang.org/docs/handbook/2/mapped-types.html) kannst Du aus einem bestehenden TypeScript Typen einen anderen erzeugen.
* Du kannst damit Logik, die in JavaScript auf Werte-Ebene funktioniert auf Typ-Ebene nachbauen
* Dazu ein Beispiel: eine Funktion, die jedes Feld eines Objekts validiert und ein neues Objekt mit dem Validierungsergebnis zurückliefert:
* ```javascript
function validateField(value) {
// Validierungslogik...
return true; // oder false
}
function validateObject(object) {
if (object == null || typeof object !== "object") {
throw new Error("invalid type");
}
const result = {};
Object.keys(object).forEach(k => {
result[k] = validateField(object[k]);
});
return result;
}
```
* Die Funktion nimmt ein beliebiges Objekt entgegen und liefert ein neues Objekt zurück:
* in diesem sind dieselben Keys wie im Ausgangsobjekt vorhanden
* Die Werte sind aber in diesem Fall jeweils `true` oder `false` (je nachdem, was die fiktive
`validateField`-Funktion zurückgegeben hat.)
---
### Mapped Types
* Eine - unzureichende - Typ-Definition für diese Funktion könnte so aussehen:
* ```typescript
declare function validateObject(o: object): object;
const result = validateObject( { firstname: "Klaus", age: 32} );
// ^? result: object
```
* So käme ein _beliebiges_ oder _allgemeines_ Objekt zurück. Wir wissen aber eigentlich genauer, wie das Objekt aussieht, das zurückgeliefert wird.
* ```typescript
const result = validateObject( { firstname: "Klaus", age: 32} );
// präziser Rückgabetype wäre: { firstname: boolean, age: boolean }
```
* Mit einem Mapped Type können wir diese Regel auf Typ-Ebene dynamisch umsetzen, und so diesen präzisen Rückgabe-Typ erzeugen.
---
### Mapped Types
* Um den Rückgabe-Typ von `validateObject` zu beschreiben, können wir einen eigenen Typen definieren (`ValidatedObject`)
* Der `ValidatePerson`-Typ kann generisch beschrieben werden, so dass er _automatisch_ alle Properties
aus einem Typen enthält, deren Typ aber nun jeweils `boolean` ist. Für den konkreten Typen `Person` sähe das so aus:
* ```typescript
type Person = { firstname: string; age: number };
type ValidatedObject = {
[Key in keyof Person]: boolean
}
// ^? { firstname: boolean; age: boolean }
```
* Den Ausdruck `keyof Person` kennen wir schon: hier wird ein Union Typ zurückgeliefert, der aus allen Keys des Objekts besteht
* Mit `[Key in keyof Person]` wird an dieser Stelle über alle Keys in dem Objekt "iteriert" und der Typ des jeweiligen Keys in die Typ Variable `Key` geschrieben
* im Beispiel der `Person` wäre das also: `firstname` und dann `age`
* `Key` ist ein Variablenname, den ihr frei vergeben könnt.
* Auf der rechten Seite vom Doppelpunkt steht (wie gewohnt) der Typ für das jeweilige Property (hier also: `boolean`)
---
### Mapped Types
* Natürlich können wir alle möglichen Veränderungen im Ziel-Objekt vorgenommen werden, z.B. auch `readonly` hinzugefügt, oder Felder optional gemacht werden:
* ```typescript
type ValidatedPerson = {
readonly [Key in keyof Person]?: boolean
}
// ^? { readonly firstname?: boolean, readonly age?: boolean }
```
* Es gibt bereits fertige [Utility Types](https://www.typescriptlang.org/docs/handbook/utility-types.html) für eine Vielzahl typischer Anwendungsfälle,
z.B. [Partial](https://www.typescriptlang.org/docs/handbook/utility-types.html#partialtype), [Required](https://www.typescriptlang.org/docs/handbook/utility-types.html#requiredtype) oder [Readonly](https://www.typescriptlang.org/docs/handbook/utility-types.html#readonlytype)
---
### Mapped Types #2
* Im vorherigen Beispiel haben wir allen Einträgen eines Objekts einen fixen neuen Typen (`boolean`) zu gewiesen.
* Es gibt aber auch Fälle, in denen der Typ eines Eintrags aus dem Typen des Originals abgeleitet werden soll.
* In einer modifizierten Variante der `validate`-Funktion, liefert diese nun nicht mehr `boolean` zurück, sondern den Original-Wert eines Feldes oder `null`, falls dieser ungültig ist:
* ```javascript
function validateField(value) {
// Validierungslogik...
return value; // oder null falls ungültig
}
function validateObject(object) {
// unverändert...
}
```
* Wenn wir ein Objekt des `Person`-Typen validieren lassen, müsste der Rückgabe-Typ folglich so aussehen:
* ```typescript
const result = validateObject( { firstname: "Klaus", age: 32} )
// ^? { firstname: string | null, age: number | null }
```
---
### Mapped Types #2
* Über den Index Access können wir beim Erzeugen des neuen Typen auf die ursprünglichen Typen der Properties des Original-Typen zugreifen
* Damit können wir zum Beispiel einen Typen bauen, der alle Properties aus dem ursprünglichen Typen hat, diese sind aber im neuen Typen nullable:
* ```typescript
type Person = {
firstname: string,
age: number
}
type ValidatedObject = {
[Key in keyof Person]: Person[Key] | null
}
// ^? { firstname: string | null, age: number | null }
declare function validate(p: Person): ValidatedObject;
```
---
### Mapped Types mit Generics
* Unsere `validate`-Funktion soll eigentlich _beliebige_ Objekte validieren und nicht nur `Person`-Objekte.
* Daher muss unser `ValidatedObject`-Typ _generisch_ sein und eine Typ-Variable verwenden
* Die Typ-Variable gibt den zu transformierenden Typen an (z.B. `Person`)
* Damit kannst Du _beliebige_ Typen in andere Typen transformieren:
* ```typescript
type ValidatedObject