Aufwärmen · Aufgabe 1 von 7
Aufwärmen mit JavaScript: JSON.parse liefert, was auch immer im Text steht. Was gibt dieser Code aus?
const data = JSON.parse('{ "price": "25" }');
console.log(data.price * 2, typeof data.price);// B5.5 · ca. 38 Min. · Einstieg
Nach dieser Lektion modellieren Sie verschachtelte Daten mit allen Objekttyp-Werkzeugen dieses Moduls, laden sie sicher aus JSON und berechnen Auswertungen, ohne sie zu verändern.
Lektion 5 von 5 in B5 Objekttypen
Danach können Sie
Aufwärmen · Aufgabe 1 von 7
const data = JSON.parse('{ "price": "25" }');
console.log(data.price * 2, typeof data.price);Vorhersagen · Aufgabe 2 von 7
interface Item {
name: string;
}
const raw = JSON.parse('{ "items": [{ "name": 42 }] }');
const items: Item[] = raw.items;
console.log(items[0].name.toUpperCase());Üben · Aufgabe 3 von 7
interface Inventory {
items: string[];
}
const raw: ____ = JSON.parse('{ "items": ["lamp"] }');
// @ts-expect-error: check raw before you use it
console.log(raw.items);Üben · Aufgabe 4 von 7
function tagsOf(value: unknown) {
if (Array.isArray(value)) {
// here
return value;
}
return [];
}Üben · Aufgabe 5 von 7
interface Product {
readonly sku: string;
name: string;
note?: string;
}
interface Item extends Product {
tags: readonly string[];
location: readonly [string, number];
}
const lamp: Item = { sku: "L-1", name: "Lamp", tags: ["light"], location: ["A", 3] };
lamp.name = "Desk lamp";
lamp.note = "new";
lamp.tags = [...lamp.tags, "sale"];
lamp.location[1] = 4;
lamp.sku = "L-2";Denksport · Aufgabe 6 von 7
function isRecord(value: unknown): value is { [key: string]: unknown } {
return typeof value === "object";
}
function skuOf(value: unknown): string {
if (!isRecord(value)) throw new Error("not an object");
const { sku } = value;
return typeof sku === "string" ? sku : "no sku";
}
console.log(skuOf(JSON.parse("null")));Anwenden · Aufgabe 7 von 7
Prüfen Sie Ihr Ergebnis anhand dieser Liste
Lesen Sie das ausgearbeitete Beispiel und lösen Sie dann die Übungen. Ihr Code läuft in Ihrem Browser oder auf Ihrem Computer und wird nie hochgeladen.
Ausgearbeitetes Beispiel
Das ganze Modul in einem Programm. DATA ist der JSON-Text. Das Modell nutzt jedes Werkzeug aus B5. Die Predicates und parseItem prüfen einen unknown-Wert Feld für Feld; parseInventory prüft das äußere Objekt und sein Array items. totalStock und der Wert lesen die Daten und ändern nichts. Der letzte Aufruf lädt einen Artikel, dessen price "cheap" ist. Führen Sie npx tsc und dann node main.ts aus. Ändern Sie danach "B" in DATA in 2 und führen Sie es erneut aus.
main.ts
// The data, as it might arrive from a file or a server.
const DATA = `{
"updated": "2026-10-04",
"items": [
{ "sku": "L-1", "name": "Desk lamp", "price": 25, "location": ["A", 3],
"tags": ["light"], "stock": { "berlin": 4, "bonn": 2 } },
{ "sku": "C-7", "name": "Chair", "price": 80, "note": "assembly needed",
"location": ["B", 1], "tags": ["office", "seating"], "stock": { "berlin": 1 } }
]
}`;
// The model: every idea from module B5.
interface Product {
readonly sku: string;
name: string;
price: number;
note?: string;
}
interface Stock {
readonly [warehouse: string]: number;
}
type Location = readonly [string, number]; // aisle, shelf
interface Item extends Product {
location: Location;
tags: readonly string[];
stock: Stock;
}
interface Inventory {
readonly updated: string;
readonly items: readonly Item[];
}
// Checks: from unknown to the model, one property at a time.
function fail(message: string): never {
throw new Error(message);
}
function isRecord(value: unknown): value is { [key: string]: unknown } {
return typeof value === "object" && value !== null && !Array.isArray(value);
}
function isLocation(value: unknown): value is Location {
return Array.isArray(value) && value.length === 2 && typeof value[0] === "string" && typeof value[1] === "number";
}
function isStringArray(value: unknown): value is readonly string[] {
return Array.isArray(value) && value.every((v) => typeof v === "string");
}
function isStock(value: unknown): value is Stock {
return isRecord(value) && Object.values(value).every((n) => typeof n === "number");
}
function parseItem(value: unknown): Item {
if (!isRecord(value)) fail("an item must be an object");
const { sku, name, price, note, location, tags, stock } = value;
if (typeof sku !== "string") fail("sku must be a string");
if (typeof name !== "string") fail("name must be a string");
if (typeof price !== "number") fail("price must be a number");
if (!isLocation(location)) fail("location must be [aisle, shelf]");
if (!isStringArray(tags)) fail("tags must be strings");
if (!isStock(stock)) fail("stock must map warehouses to numbers");
const item: Item = { sku, name, price, location, tags, stock };
if (note !== undefined) {
if (typeof note !== "string") fail("note must be a string");
item.note = note;
}
return item;
}
function parseInventory(text: string): Inventory {
const raw: unknown = JSON.parse(text);
if (!isRecord(raw)) fail("the inventory must be an object");
const { updated, items } = raw;
if (typeof updated !== "string") fail("updated must be a string");
if (!Array.isArray(items)) fail("items must be an array");
return { updated, items: items.map(parseItem) };
}
// Summaries: read the data, never change it.
function totalStock(item: Item): number {
let total = 0;
for (const count of Object.values(item.stock)) total += count;
return total;
}
const inventory = parseInventory(DATA);
for (const item of inventory.items) {
const [aisle, shelf] = item.location;
console.log(item.sku + " " + item.name + ": " + totalStock(item) + " in stock, aisle " + aisle + " shelf " + shelf + (item.note ? " (" + item.note + ")" : ""));
}
let value = 0;
for (const item of inventory.items) value += item.price * totalStock(item);
console.log("value: " + value + ", updated " + inventory.updated);
try {
parseInventory('{ "updated": "2026-10-04", "items": [{ "sku": "X-1", "name": "Shelf", "price": "cheap" }] }');
} catch (error) {
console.log("rejected: " + (error instanceof Error ? error.message : String(error)));
}
Ausführen mit
npx tsc
node main.tsAusgabe
L-1 Desk lamp: 6 in stock, aisle A shelf 3
C-7 Chair: 1 in stock, aisle B shelf 1 (assembly needed)
value: 230, updated 2026-10-04
rejected: price must be a numberTab rückt ein, Umschalt+Tab rückt aus. Um den Editor mit der Tastatur zu verlassen, drücken Sie Esc und dann Tab.
Beim ersten Ausführen lädt Ihr Browser den TypeScript-Compiler herunter (bis zu 2.1 MB) und speichert ihn im Cache. Ihr Code bleibt auf Ihrem Gerät.
Übung 1 von 3
Korrigieren Sie das Modell, sodass lamp dazu passt und jedes // @ts-expect-error in noEdits seinen Fehler findet. note wird optional. stock wird ein exportiertes Interface Stock mit einer readonly-Indexsignatur: Lagernamen auf Bestände. tags wird ein readonly string[], location ein readonly-Tupel aus Gang und Regal. Lassen Sie dann totalStock die Bestände aller Lager addieren, sodass die Lampe auf 6 kommt. Führen Sie die Tests aus: Der erste ist die Typprüfung.
Tab rückt ein, Umschalt+Tab rückt aus. Um den Editor mit der Tastatur zu verlassen, drücken Sie Esc und dann Tab.
Beim ersten Ausführen lädt Ihr Browser den TypeScript-Compiler herunter (bis zu 2.1 MB) und speichert ihn im Cache. Ihr Code bleibt auf Ihrem Gerät.
export interface Stock { readonly [warehouse: string]: number; } und dann stock: Stock in Item.
readonly steht vor beiden Array-Typen: tags: readonly string[]; location: readonly [string, number];
let total = 0; for (const count of Object.values(item.stock)) total += count; return total;
Ein möglicher Lösungsweg. Ihrer kann anders aussehen und trotzdem alle Prüfungen bestehen.
export interface Product {
readonly sku: string;
name: string;
price: number;
note?: string;
}
export interface Stock {
readonly [warehouse: string]: number;
}
export interface Item extends Product {
location: readonly [string, number];
tags: readonly string[];
stock: Stock;
}
export function totalStock(item: Item): number {
let total = 0;
for (const count of Object.values(item.stock)) total += count;
return total;
}
export function stockValue(items: readonly Item[]): number {
let value = 0;
for (const item of items) value += item.price * totalStock(item);
return value;
}
export const lamp: Item = {
sku: "L-1",
name: "Desk lamp",
price: 25,
location: ["A", 3],
tags: ["light"],
stock: { berlin: 4, bonn: 2 }
};
function noEdits(item: Item) {
// @ts-expect-error: tags are read-only
item.tags.push("sale");
// @ts-expect-error: the stock is read-only
item.stock.berlin = 0;
// @ts-expect-error: a location cannot change
item.location[1] = 4;
}
Installieren Sie TypeScript 7.0 oder neuer. Speichern Sie diese Dateien in einem Ordner, öffnen Sie dort ein Terminal und führen Sie die Befehle unten aus.
main.ts
export interface Product {
readonly sku: string;
name: string;
price: number;
note: string;
}
export interface Item extends Product {
location: [string, number];
tags: string[];
stock: number;
}
export function totalStock(item: Item): number {
return item.stock;
}
export function stockValue(items: readonly Item[]): number {
let value = 0;
for (const item of items) value += item.price * totalStock(item);
return value;
}
export const lamp: Item = {
sku: "L-1",
name: "Desk lamp",
price: 25,
location: ["A", 3],
tags: ["light"],
stock: { berlin: 4, bonn: 2 }
};
function noEdits(item: Item) {
// @ts-expect-error: tags are read-only
item.tags.push("sale");
// @ts-expect-error: the stock is read-only
item.stock.berlin = 0;
// @ts-expect-error: a location cannot change
item.location[1] = 4;
}
main.test.ts
import {test} from 'node:test';
import assert from 'node:assert/strict';
import {totalStock, stockValue, lamp} from './main.ts';
const chair = {sku: 'C-7', name: 'Chair', price: 80, location: ['B', 1] as const, tags: [], stock: {berlin: 1}};
const empty = {sku: 'E-0', name: 'Empty box', price: 2, location: ['C', 2] as const, tags: [], stock: {}};
test('totalStock addiert die Bestände aller Lager', () => {
assert.equal(totalStock(lamp), 6, `totalStock(lamp) ergab ${totalStock(lamp)}`);
});
test('ein Artikel ohne Bestandseinträge hat den Bestand 0', () => {
assert.equal(totalStock(empty), 0, `totalStock(empty) ergab ${totalStock(empty)}`);
});
test('stockValue addiert Preis mal Bestand über alle Artikel', () => {
const got = stockValue([lamp, chair, empty]);
assert.equal(got, 230, `stockValue([lamp, chair, empty]) ergab ${got}`);
});
package.json
{
"type": "module"
}
tsconfig.json
{
"compilerOptions": {
"target": "esnext",
"module": "nodenext",
"lib": [
"esnext",
"dom"
],
"types": [],
"strict": true,
"noEmit": true,
"erasableSyntaxOnly": true,
"verbatimModuleSyntax": true,
"allowImportingTsExtensions": true
},
"include": [
"**/*.ts"
],
"exclude": [
"**/*.test.ts"
]
}
npx tsc braucht TypeScript im Ordner: Führen Sie dort einmal npm install --save-dev typescript aus. tsc prüft die Typen; Node.js führt main.ts aus, indem es sie entfernt.
Programm ausführen:
npx tsc
node main.tsPrüfungen ausführen (learnrun.js muss im selben Ordner liegen):
npx tsc
node --testlearnrun.js herunterladenÜbung 2 von 3
parseItem prüft sku, name und price, aber isLocation und isStock prüfen zu wenig und geben einen einfachen boolean zurück, also kann tsc location und stock nicht verengen, und tags wird gar nicht geprüft. Machen Sie beide zu Type Predicates. isLocation: genau zwei Elemente, ein String und eine Zahl. isStock: ein Record, dessen Werte alle Zahlen sind. Ergänzen Sie ein Predicate für tags, ein Array von Strings, und brechen Sie mit tags must be strings ab, wenn es nicht zutrifft.
Tab rückt ein, Umschalt+Tab rückt aus. Um den Editor mit der Tastatur zu verlassen, drücken Sie Esc und dann Tab.
Beim ersten Ausführen lädt Ihr Browser den TypeScript-Compiler herunter (bis zu 2.1 MB) und speichert ihn im Cache. Ihr Code bleibt auf Ihrem Gerät.
Ein Predicate ist ein Rückgabetyp: function isLocation(value: unknown): value is Location {
Array.isArray(value) && value.length === 2 && typeof value[0] === "string" && typeof value[1] === "number"
isRecord(value) && Object.values(value).every((count) => typeof count === "number") prüft jedes Lager.
Ein möglicher Lösungsweg. Ihrer kann anders aussehen und trotzdem alle Prüfungen bestehen.
export interface Product {
readonly sku: string;
name: string;
price: number;
note?: string;
}
export interface Stock {
readonly [warehouse: string]: number;
}
export type Location = readonly [string, number];
export interface Item extends Product {
location: Location;
tags: readonly string[];
stock: Stock;
}
function fail(message: string): never {
throw new Error(message);
}
function isRecord(value: unknown): value is { [key: string]: unknown } {
return typeof value === "object" && value !== null && !Array.isArray(value);
}
export function isLocation(value: unknown): value is Location {
return Array.isArray(value) && value.length === 2 && typeof value[0] === "string" && typeof value[1] === "number";
}
export function isStock(value: unknown): value is Stock {
return isRecord(value) && Object.values(value).every((count) => typeof count === "number");
}
function isStringArray(value: unknown): value is readonly string[] {
return Array.isArray(value) && value.every((tag) => typeof tag === "string");
}
export function parseItem(value: unknown): Item {
if (!isRecord(value)) fail("an item must be an object");
const { sku, name, price, location, tags, stock } = value;
if (typeof sku !== "string") fail("sku must be a string");
if (typeof name !== "string") fail("name must be a string");
if (typeof price !== "number") fail("price must be a number");
if (!isLocation(location)) fail("location must be [aisle, shelf]");
if (!isStringArray(tags)) fail("tags must be strings");
if (!isStock(stock)) fail("stock must map warehouses to numbers");
return { sku, name, price, location, tags, stock };
}
Installieren Sie TypeScript 7.0 oder neuer. Speichern Sie diese Dateien in einem Ordner, öffnen Sie dort ein Terminal und führen Sie die Befehle unten aus.
main.ts
export interface Product {
readonly sku: string;
name: string;
price: number;
note?: string;
}
export interface Stock {
readonly [warehouse: string]: number;
}
export type Location = readonly [string, number];
export interface Item extends Product {
location: Location;
tags: readonly string[];
stock: Stock;
}
function fail(message: string): never {
throw new Error(message);
}
function isRecord(value: unknown): value is { [key: string]: unknown } {
return typeof value === "object" && value !== null && !Array.isArray(value);
}
export function isLocation(value: unknown): boolean {
return Array.isArray(value);
}
export function isStock(value: unknown): boolean {
return isRecord(value);
}
export function parseItem(value: unknown): Item {
if (!isRecord(value)) fail("an item must be an object");
const { sku, name, price, location, tags, stock } = value;
if (typeof sku !== "string") fail("sku must be a string");
if (typeof name !== "string") fail("name must be a string");
if (typeof price !== "number") fail("price must be a number");
if (!isLocation(location)) fail("location must be [aisle, shelf]");
if (!isStock(stock)) fail("stock must map warehouses to numbers");
return { sku, name, price, location, tags, stock };
}
main.test.ts
import {test} from 'node:test';
import assert from 'node:assert/strict';
import {isLocation, isStock, parseItem} from './main.ts';
const lamp = {sku: 'L-1', name: 'Desk lamp', price: 25, location: ['A', 3], tags: ['light'], stock: {berlin: 4, bonn: 2}};
test('isLocation akzeptiert einen Gang und ein Regal, sonst nichts', () => {
assert.equal(isLocation(['A', 3]), true, "isLocation(['A', 3]) sollte true sein");
assert.equal(isLocation(['A', '3']), false, "isLocation(['A', '3']) sollte false sein: das Regal ist eine Zahl");
assert.equal(isLocation(['A', 3, 1]), false, "isLocation(['A', 3, 1]) sollte false sein: genau zwei Teile");
});
test('isStock akzeptiert nur Zahlen pro Lager', () => {
assert.equal(isStock({berlin: 4}), true, 'isStock({berlin: 4}) sollte true sein');
assert.equal(isStock({berlin: 'four'}), false, "isStock({berlin: 'four'}) sollte false sein");
assert.equal(isStock([4]), false, 'isStock([4]) sollte false sein: ein Array ist keine Zuordnung von Lagern');
});
test('ein gültiger Artikel kommt mit allen Eigenschaften zurück', () => {
assert.deepEqual(parseItem(lamp), lamp, 'parseItem(lamp) sollte ein Objekt gleich lamp zurückgeben');
});
test('tags muss ein Array von Strings sein', () => {
assert.throws(() => parseItem({...lamp, tags: ['light', 7]}), {message: 'tags must be strings'}, 'ein Tag 7 sollte "tags must be strings" werfen');
});
test('ein falscher Ort oder Bestand wird mit seiner Meldung abgelehnt', () => {
assert.throws(() => parseItem({...lamp, location: ['A']}), {message: 'location must be [aisle, shelf]'}, "location ['A'] sollte \"location must be [aisle, shelf]\" werfen");
assert.throws(() => parseItem({...lamp, stock: {berlin: '4'}}), {message: 'stock must map warehouses to numbers'}, "stock {berlin: '4'} sollte \"stock must map warehouses to numbers\" werfen");
});
package.json
{
"type": "module"
}
tsconfig.json
{
"compilerOptions": {
"target": "esnext",
"module": "nodenext",
"lib": [
"esnext",
"dom"
],
"types": [],
"strict": true,
"noEmit": true,
"erasableSyntaxOnly": true,
"verbatimModuleSyntax": true,
"allowImportingTsExtensions": true
},
"include": [
"**/*.ts"
],
"exclude": [
"**/*.test.ts"
]
}
npx tsc braucht TypeScript im Ordner: Führen Sie dort einmal npm install --save-dev typescript aus. tsc prüft die Typen; Node.js führt main.ts aus, indem es sie entfernt.
Programm ausführen:
npx tsc
node main.tsPrüfungen ausführen (learnrun.js muss im selben Ordner liegen):
npx tsc
node --testlearnrun.js herunterladenÜbung 3 von 3
parseInventory vertraut JSON.parse, und lowStock sortiert die readonly-Artikel an Ort und Stelle, was tsc mit error TS2339 ablehnt. Lassen Sie parseInventory den Text als unknown lesen und prüfen: Brechen Sie mit the inventory must be an object, updated must be a string oder items must be an array ab, und prüfen Sie jeden Artikel mit parseItem. lowStock gibt die SKUs zurück, deren Gesamtbestand höchstens die Grenze erreicht, sortiert, ohne die Reihenfolge der Artikel zu ändern.
Tab rückt ein, Umschalt+Tab rückt aus. Um den Editor mit der Tastatur zu verlassen, drücken Sie Esc und dann Tab.
Beim ersten Ausführen lädt Ihr Browser den TypeScript-Compiler herunter (bis zu 2.1 MB) und speichert ihn im Cache. Ihr Code bleibt auf Ihrem Gerät.
const raw: unknown = JSON.parse(text); dann if (!isRecord(raw)) fail("the inventory must be an object");
Nach if (!Array.isArray(items)) fail(…) prüft items.map(parseItem) jeden Artikel und ergibt ein Item[].
toSorted gibt eine sortierte Kopie zurück und lässt inventory.items unverändert.
Ein möglicher Lösungsweg. Ihrer kann anders aussehen und trotzdem alle Prüfungen bestehen.
export interface Product {
readonly sku: string;
name: string;
price: number;
note?: string;
}
export interface Stock {
readonly [warehouse: string]: number;
}
export type Location = readonly [string, number];
export interface Item extends Product {
location: Location;
tags: readonly string[];
stock: Stock;
}
export interface Inventory {
readonly updated: string;
readonly items: readonly Item[];
}
function fail(message: string): never {
throw new Error(message);
}
function isRecord(value: unknown): value is { [key: string]: unknown } {
return typeof value === "object" && value !== null && !Array.isArray(value);
}
function isLocation(value: unknown): value is Location {
return Array.isArray(value) && value.length === 2 && typeof value[0] === "string" && typeof value[1] === "number";
}
function isStock(value: unknown): value is Stock {
return isRecord(value) && Object.values(value).every((count) => typeof count === "number");
}
function isStringArray(value: unknown): value is readonly string[] {
return Array.isArray(value) && value.every((tag) => typeof tag === "string");
}
export function parseItem(value: unknown): Item {
if (!isRecord(value)) fail("an item must be an object");
const { sku, name, price, location, tags, stock } = value;
if (typeof sku !== "string") fail("sku must be a string");
if (typeof name !== "string") fail("name must be a string");
if (typeof price !== "number") fail("price must be a number");
if (!isLocation(location)) fail("location must be [aisle, shelf]");
if (!isStringArray(tags)) fail("tags must be strings");
if (!isStock(stock)) fail("stock must map warehouses to numbers");
return { sku, name, price, location, tags, stock };
}
export function totalStock(item: Item): number {
let total = 0;
for (const count of Object.values(item.stock)) total += count;
return total;
}
export function parseInventory(text: string): Inventory {
const raw: unknown = JSON.parse(text);
if (!isRecord(raw)) fail("the inventory must be an object");
const { updated, items } = raw;
if (typeof updated !== "string") fail("updated must be a string");
if (!Array.isArray(items)) fail("items must be an array");
return { updated, items: items.map(parseItem) };
}
export function lowStock(inventory: Inventory, limit: number): readonly string[] {
const sorted = inventory.items.toSorted((a, b) => a.sku.localeCompare(b.sku));
return sorted.filter((item) => totalStock(item) <= limit).map((item) => item.sku);
}
Installieren Sie TypeScript 7.0 oder neuer. Speichern Sie diese Dateien in einem Ordner, öffnen Sie dort ein Terminal und führen Sie die Befehle unten aus.
main.ts
export interface Product {
readonly sku: string;
name: string;
price: number;
note?: string;
}
export interface Stock {
readonly [warehouse: string]: number;
}
export type Location = readonly [string, number];
export interface Item extends Product {
location: Location;
tags: readonly string[];
stock: Stock;
}
export interface Inventory {
readonly updated: string;
readonly items: readonly Item[];
}
function fail(message: string): never {
throw new Error(message);
}
function isRecord(value: unknown): value is { [key: string]: unknown } {
return typeof value === "object" && value !== null && !Array.isArray(value);
}
function isLocation(value: unknown): value is Location {
return Array.isArray(value) && value.length === 2 && typeof value[0] === "string" && typeof value[1] === "number";
}
function isStock(value: unknown): value is Stock {
return isRecord(value) && Object.values(value).every((count) => typeof count === "number");
}
function isStringArray(value: unknown): value is readonly string[] {
return Array.isArray(value) && value.every((tag) => typeof tag === "string");
}
export function parseItem(value: unknown): Item {
if (!isRecord(value)) fail("an item must be an object");
const { sku, name, price, location, tags, stock } = value;
if (typeof sku !== "string") fail("sku must be a string");
if (typeof name !== "string") fail("name must be a string");
if (typeof price !== "number") fail("price must be a number");
if (!isLocation(location)) fail("location must be [aisle, shelf]");
if (!isStringArray(tags)) fail("tags must be strings");
if (!isStock(stock)) fail("stock must map warehouses to numbers");
return { sku, name, price, location, tags, stock };
}
export function totalStock(item: Item): number {
let total = 0;
for (const count of Object.values(item.stock)) total += count;
return total;
}
export function parseInventory(text: string): Inventory {
const raw = JSON.parse(text);
return { updated: raw.updated, items: raw.items };
}
export function lowStock(inventory: Inventory, limit: number): readonly string[] {
const sorted = inventory.items.sort((a, b) => a.sku.localeCompare(b.sku));
return sorted.filter((item) => totalStock(item) <= limit).map((item) => item.sku);
}
main.test.ts
import {test} from 'node:test';
import assert from 'node:assert/strict';
import {parseInventory, lowStock} from './main.ts';
const item = (sku, berlin) => ({sku, name: sku, price: 1, location: ['A', 1], tags: [], stock: {berlin}});
const text = JSON.stringify({updated: '2026-10-04', items: [item('L-1', 6), item('C-7', 1), item('B-2', 3)]});
test('ein gültiger Bestand wird mit allen Artikeln geladen', () => {
const inventory = parseInventory(text);
assert.equal(inventory.updated, '2026-10-04', `updated ist ${JSON.stringify(inventory.updated)}`);
assert.deepEqual(inventory.items.map((i) => i.sku), ['L-1', 'C-7', 'B-2'], 'die drei Artikel sollten in ihrer Reihenfolge geladen werden');
});
test('der Bestand selbst wird geprüft', () => {
assert.throws(() => parseInventory('null'), {message: 'the inventory must be an object'}, 'null sollte "the inventory must be an object" werfen');
assert.throws(() => parseInventory('{"updated": "2026-10-04", "items": 3}'), {message: 'items must be an array'}, 'items: 3 sollte "items must be an array" werfen');
});
test('auch jeder Artikel wird geprüft', () => {
const bad = JSON.stringify({updated: '2026-10-04', items: [{...item('X-1', 1), price: 'cheap'}]});
assert.throws(() => parseInventory(bad), {message: 'price must be a number'}, 'ein Artikel mit price "cheap" sollte "price must be a number" werfen');
});
test('lowStock listet die SKUs bis zur Grenze, sortiert', () => {
const got = lowStock(parseInventory(text), 3);
assert.deepEqual(got, ['B-2', 'C-7'], `lowStock(inventory, 3) ergab ${JSON.stringify(got)}`);
});
test('lowStock lässt die Reihenfolge der Artikel unverändert', () => {
const inventory = parseInventory(text);
lowStock(inventory, 3);
assert.deepEqual(inventory.items.map((i) => i.sku), ['L-1', 'C-7', 'B-2'], 'nach lowStock sollten die Artikel noch in ihrer Reihenfolge sein');
});
package.json
{
"type": "module"
}
tsconfig.json
{
"compilerOptions": {
"target": "esnext",
"module": "nodenext",
"lib": [
"esnext",
"dom"
],
"types": [],
"strict": true,
"noEmit": true,
"erasableSyntaxOnly": true,
"verbatimModuleSyntax": true,
"allowImportingTsExtensions": true
},
"include": [
"**/*.ts"
],
"exclude": [
"**/*.test.ts"
]
}
npx tsc braucht TypeScript im Ordner: Führen Sie dort einmal npm install --save-dev typescript aus. tsc prüft die Typen; Node.js führt main.ts aus, indem es sie entfernt.
Programm ausführen:
npx tsc
node main.tsPrüfungen ausführen (learnrun.js muss im selben Ordner liegen):
npx tsc
node --testlearnrun.js herunterladeninterface Inventory {
items: { sku: string }[];
}
const inventory: Inventory = JSON.parse('{ "products": [] }');
console.log(inventory.items.length);
Was tsc oder Node.js ausgibt
TypeError: Cannot read properties of undefined (reading 'length')Warum, und die Lösung
JSON.parse gibt any zurück, also akzeptiert tsc die Annotation, ohne den Text anzusehen, und der Fehler zeigt sich erst beim Ausführen. Hier enthält der Text products, nicht items. Lesen Sie das Ergebnis als unknown und prüfen Sie es, bevor Sie daraus ein Inventory bauen, wie parseInventory es tut; dann stoppt eine falsche Datei mit einer klaren Meldung wie items must be an array.
function skuOf(value: unknown): string {
return value.sku;
}
Was tsc oder Node.js ausgibt
main.ts(2,10): error TS18046: 'value' is of type 'unknown'.Warum, und die Lösung
unknown erlaubt nichts, bis Sie geprüft haben, was es ist; dafür ist es da. Prüfen Sie zuerst mit einem Predicate wie isRecord, dass es ein Objekt ist, dann die Eigenschaft selbst: if (!isRecord(value)) fail("not an object"); const { sku } = value; if (typeof sku !== "string") fail("sku must be a string"); return sku;
interface Item {
sku: string;
}
interface Inventory {
readonly items: readonly Item[];
}
function addItem(inventory: Inventory, item: Item): void {
inventory.items.push(item);
}
Was tsc oder Node.js ausgibt
main.ts(9,19): error TS2339: Property 'push' does not exist on type 'readonly Item[]'.Warum, und die Lösung
Der geladene Bestand ist absichtlich readonly: Auswertungen dürfen ihn nicht ändern. Um einen Artikel hinzuzufügen, bauen Sie stattdessen einen neuen Bestand: function addItem(inventory: Inventory, item: Item): Inventory { return { ...inventory, items: [...inventory.items, item] }; }. Der alte bleibt, wie er war, und damit bleiben auch frühere Auswertungen gültig.
TypeScript im Browser: der Compiler von TypeScript 6.0.3, Apache-2.0, danach die eigene Engine Ihres Browsers. Lizenz und Quellcode
5 Fragen, ohne Hinweise. Ab 80 % ist die Lektion abgeschlossen.
Erledigen Sie zuerst alle Aufgaben oben, um das Abschlussquiz freizuschalten.