Arrays, Objekte, Fetch API, JSON, Europeana & IIIF
Webentwicklung & Agentic Coding | Sommersemester 2026
Was wir in Einheit 05 gelernt haben:
const und let für Werte, die sich ändern könnendocument.querySelector(), .textContent, .innerHTMLaddEventListener('click', ...) für Benutzerinteraktionfunction und Arrow Functions () => {}if/else, for-SchleifenHeute bauen wir darauf auf: Wir arbeiten mit Daten — laden, verarbeiten, filtern, anzeigen.
Arrays sind geordnete Listen von Werten. Wir kennen sie schon — jetzt vertiefen wir sie:
const briefe = ['Brief an Ascoli', 'Brief an Meyer-Lübke', 'Brief an Vossler'];
// Laenge abfragen
console.log(briefe.length); // 3
// Element am Ende hinzufügen
briefe.push('Brief an Baudouin de Courtenay');
console.log(briefe.length); // 4
// Letztes Element entfernen und zurückgeben
const letzter = briefe.pop();
console.log(letzter); // 'Brief an Baudouin de Courtenay'
// Erstes Element entfernen
const erster = briefe.shift();
console.log(erster); // 'Brief an Ascoli'
// Element am Anfang hinzufügen
briefe.unshift('Neuer erster Brief');
// Zugriff über Index (beginnt bei 0)
console.log(briefe[0]); // 'Neuer erster Brief'
forEach() führt eine Funktion für jedes Element im Array aus:
const sprachen = ['Romanisch', 'Baskisch', 'Kreolisch', 'Keltisch'];
// Mit forEach über alle Elemente iterieren
sprachen.forEach((sprache, index) => {
console.log(`${index + 1}. ${sprache}`);
});
// Ausgabe:
// 1. Romanisch
// 2. Baskisch
// 3. Kreolisch
// 4. Keltisch
Vorteile gegenüber einer for-Schleife:
Merke: forEach() gibt nichts zurück — es führt nur eine Aktion aus.
map() erstellt ein neues Array, indem es jedes Element transformiert:
const briefe = [
{ titel: 'Brief an Ascoli', jahr: 1882 },
{ titel: 'Brief an Meyer-Lübke', jahr: 1887 },
{ titel: 'Brief an Vossler', jahr: 1904 }
];
// Nur die Titel extrahieren
const titel = briefe.map(brief => brief.titel);
console.log(titel);
// ['Brief an Ascoli', 'Brief an Meyer-Lübke', 'Brief an Vossler']
// HTML-Elemente erzeugen
const htmlListe = briefe.map(brief =>
`<li>${brief.titel} (${brief.jahr})</li>`
);
console.log(htmlListe.join('\n'));
// <li>Brief an Ascoli (1882)</li>
// <li>Brief an Meyer-Lübke (1887)</li>
// <li>Brief an Vossler (1904)</li>
Merke: map() verändert das Original-Array nicht — es gibt ein neues zurück.
filter() erstellt ein neues Array mit allen Elementen, die eine Bedingung erfüllen:
const briefe = [
{ titel: 'Brief an Ascoli', jahr: 1882, ort: 'Graz' },
{ titel: 'Brief an Meyer-Lübke', jahr: 1887, ort: 'Graz' },
{ titel: 'Brief an Baudouin', jahr: 1890, ort: 'Wien' },
{ titel: 'Brief an Vossler', jahr: 1904, ort: 'Graz' }
];
// Briefe nach 1885 filtern
const nachricht1885 = briefe.filter(brief => brief.jahr > 1885);
console.log(nachricht1885.length); // 3
// Briefe aus Graz filtern
const ausGraz = briefe.filter(brief => brief.ort === 'Graz');
console.log(ausGraz.length); // 3
// Kombination: Briefe aus Graz nach 1885
const grazNach1885 = briefe
.filter(brief => brief.ort === 'Graz')
.filter(brief => brief.jahr > 1885);
console.log(grazNach1885.length); // 2
Tipp: filter() kann verkettet werden — ideal für mehrstufige Filterung.
find() gibt das erste Element zurück, das eine Bedingung erfüllt:
const briefe = [
{ id: 1, titel: 'Brief an Ascoli', jahr: 1882 },
{ id: 2, titel: 'Brief an Meyer-Lübke', jahr: 1887 },
{ id: 3, titel: 'Brief an Vossler', jahr: 1904 }
];
// Einen bestimmten Brief finden
const brief = briefe.find(b => b.id === 2);
console.log(brief.titel); // 'Brief an Meyer-Lübke'
// Brief nach Titel suchen
const vossler = briefe.find(b => b.titel.includes('Vossler'));
console.log(vossler.jahr); // 1904
// Wenn nichts gefunden wird: undefined
const keiner = briefe.find(b => b.id === 99);
console.log(keiner); // undefined
Unterschied zu filter():
filter() gibt ein Array mit allen Treffern zurückfind() gibt ein einzelnes Element zurück (oder undefined)Objekte können andere Objekte und Arrays enthalten — das ist in realen Daten die Regel:
const brief = {
id: 42,
titel: 'Brief an Karl Vossler',
datum: '1904-01-03',
absender: {
name: 'Hugo Schuchardt',
ort: 'Graz',
institution: 'Universität Graz'
},
themen: ['Romanistik', 'Sprachphilosophie'],
sprache: 'Deutsch'
};
// Dot Notation (Punktnotation)
console.log(brief.absender.name); // 'Hugo Schuchardt'
// Bracket Notation (Klammernotation)
const feld = 'sprache';
console.log(brief[feld]); // 'Deutsch'
// Verschachtelter Zugriff auf Array im Objekt
console.log(brief.themen[0]); // 'Romanistik'
Bracket Notation ist nötig, wenn der Schlüssel in einer Variablen steht oder Sonderzeichen enthält.
Fast alle realen Daten kommen als Array von Objekten — eine Liste mit strukturierten Einträgen:
const korrespondenz = [
{
id: 1,
datum: '1882-01-15',
absender: 'Hugo Schuchardt',
empfaenger: 'Graziadio Ascoli',
thema: 'Kreolische Sprachen',
ort: 'Graz'
},
{
id: 2,
datum: '1885-05-03',
absender: 'Gustav Meyer',
empfaenger: 'Hugo Schuchardt',
thema: 'Albanische Grammatik',
ort: 'Graz'
},
{
id: 3,
datum: '1890-11-22',
absender: 'Hugo Schuchardt',
empfaenger: 'Jan Baudouin de Courtenay',
thema: 'Sprachkontakt',
ort: 'Graz'
}
];
Auf dieses Muster wenden wir map(), filter() und find() an!
Destrukturierung lässt Sie Eigenschaften eines Objekts direkt in Variablen übernehmen:
const brief = {
titel: 'Brief an Vossler',
datum: '1904-01-03',
absender: 'Hugo Schuchardt',
ort: 'Graz'
};
// Ohne Destrukturierung
const titel1 = brief.titel;
const datum1 = brief.datum;
// Mit Destrukturierung (kürzer!)
const { titel, datum, absender } = brief;
console.log(titel); // 'Brief an Vossler'
console.log(datum); // '1904-01-03'
console.log(absender); // 'Hugo Schuchardt'
// Besonders nützlich in map/filter Callbacks:
korrespondenz.forEach(({ absender, empfaenger, datum }) => {
console.log(`${absender} an ${empfaenger} am ${datum}`);
});
// Array-Destrukturierung
const [erster, zweiter] = ['Romanisch', 'Baskisch', 'Kreolisch'];
console.log(erster); // 'Romanisch'
console.log(zweiter); // 'Baskisch'
fetch() ist die moderne Art, Daten aus dem Internet (oder aus lokalen Dateien) zu laden:
// fetch() gibt ein Promise zurück
const promise = fetch('https://api.example.com/daten');
console.log(promise); // Promise { <pending> }
Warum Promise?
Analogie: Ein Promise ist wie eine Bestellung im Restaurant — Sie bestellen (fetch), bekommen eine Nummer (Promise) und werden gerufen, wenn das Essen fertig ist.
async/await macht asynchronen Code lesbar wie normalen Code:
// OHNE async/await (mit .then-Ketten):
fetch('daten.json')
.then(response => response.json())
.then(data => {
console.log(data);
})
.catch(error => {
console.error('Fehler:', error);
});
// MIT async/await (viel lesbarer!):
async function ladeDaten() {
try {
const response = await fetch('daten.json');
const data = await response.json();
console.log(data);
} catch (error) {
console.error('Fehler:', error);
}
}
ladeDaten();
Regeln: await darf nur innerhalb einer async-Funktion stehen.
Fehler werden mit try/catch abgefangen.
So laden und verarbeiten Sie JSON-Daten in der Praxis:
async function ladeUndZeigeBriefe() {
try {
// 1. Daten laden
const response = await fetch('briefe.json');
// 2. Prüfen, ob die Anfrage erfolgreich war
if (!response.ok) {
throw new Error(`HTTP-Fehler: ${response.status}`);
}
// 3. JSON parsen
const briefe = await response.json();
// 4. Daten in HTML umwandeln
const container = document.querySelector('#briefe-liste');
container.innerHTML = briefe
.map(brief => `
<article class="brief-karte">
<h3>${brief.titel}</h3>
<p>Von: ${brief.absender} | ${brief.datum}</p>
</article>
`)
.join('');
} catch (error) {
console.error('Fehler beim Laden:', error);
document.querySelector('#briefe-liste').innerHTML =
'<p class="fehler">Daten konnten nicht geladen werden.</p>';
}
}
// Beim Laden der Seite aufrufen
ladeUndZeigeBriefe();
JSON = JavaScript Object Notation — das Standardformat für Datenaustausch im Web:
{
"titel": "Brief an Karl Vossler",
"datum": "1904-01-03",
"absender": {
"name": "Hugo Schuchardt",
"ort": "Graz"
},
"themen": ["Romanistik", "Sprachphilosophie"],
"seiten": 4,
"digitalisiert": true
}
Regeln:
"text"), Zahlen (42), Booleans (true/false), Arrays ([]), Objekte ({}), nullJavaScript bietet eingebaute Methoden für die Arbeit mit JSON:
// 1. response.json() — bei fetch-Antworten
const response = await fetch('daten.json');
const data = await response.json(); // JSON -> JS-Objekt
// 2. JSON.parse() — String in Objekt umwandeln
const jsonString = '{"name": "Schuchardt", "jahr": 1842}';
const objekt = JSON.parse(jsonString);
console.log(objekt.name); // 'Schuchardt'
// 3. JSON.stringify() — Objekt in String umwandeln
const person = { name: 'Schuchardt', stadt: 'Graz' };
const text = JSON.stringify(person);
console.log(text); // '{"name":"Schuchardt","stadt":"Graz"}'
// Hübsch formatiert ausgeben (mit Einrückung):
const huebsch = JSON.stringify(person, null, 2);
console.log(huebsch);
// {
// "name": "Schuchardt",
// "stadt": "Graz"
// }
<!-- HTML -->
<div id="sammlung"></div>
<script>
async function zeigeSammlung() {
const response = await fetch('objekte.json');
const objekte = await response.json();
const container = document.querySelector('#sammlung');
container.innerHTML = objekte.map(obj => `
<div class="objekt-karte">
<h3>${obj.name}</h3>
<p><strong>Datierung:</strong> ${obj.datierung}</p>
<p>${obj.beschreibung}</p>
</div>
`).join('');
}
zeigeSammlung();
</script>
// objekte.json
[
{
"name": "Richtbeil",
"datierung": "1798",
"beschreibung": "Instrument des Strafvollzugs aus dem 18. Jh."
},
{
"name": "Schandmaske",
"datierung": "ca. 1650",
"beschreibung": "Ehrenstrafe für öffentliche Vergehen."
}
]
Eine REST-API (Representational State Transfer) ist eine Schnittstelle, über die man Daten von einem Server abrufen kann:
?// Beispiel: Europeana Search API
https://api.europeana.eu/record/v2/search.json
?query=Schuchardt
&rows=10
&profile=standard
&wskey=DEIN_API_KEY
// Aufbau:
// Basis-URL: api.europeana.eu/record/v2/search.json
// Suchbegriff: ?query=Schuchardt
// Anzahl Ergebnisse: &rows=10
// Profil: &profile=standard
// API-Schlüssel: &wskey=DEIN_API_KEY
Wichtig: Viele APIs benötigen einen API-Key (Schlüssel) zur Authentifizierung.
Europeana bietet Zugang zu über 50 Millionen Kulturerbe-Objekten aus ganz Europa.
Wichtige Suchparameter:
query — Suchbegriff (z.B. Schuchardt)qf — Filter (z.B. TYPE:IMAGE, COUNTRY:austria)rows — Anzahl Ergebnisse (max. 100)start — Offset für PaginierungResponse-Struktur (vereinfacht):
{
"success": true,
"totalResults": 245,
"items": [
{
"id": "/12345/object_ABC",
"title": ["Brief von Hugo Schuchardt"],
"edmPreview": ["https://...thumbnail.jpg"],
"year": ["1887"],
"dataProvider": ["Universität Graz"]
}
]
}
async function sucheEuropeana(suchbegriff) {
const ergebnisContainer = document.querySelector('#ergebnisse');
ergebnisContainer.innerHTML = '<p>Suche läuft...</p>';
try {
const url = `https://api.europeana.eu/record/v2/search.json`
+ `?query=${encodeURIComponent(suchbegriff)}`
+ `&rows=12&profile=standard`
+ `&wskey=DEIN_API_KEY`;
const response = await fetch(url);
const data = await response.json();
if (!data.items || data.items.length === 0) {
ergebnisContainer.innerHTML = '<p>Keine Ergebnisse.</p>';
return;
}
ergebnisContainer.innerHTML = data.items.map(item => `
<div class="ergebnis-karte">
<img src="${item.edmPreview?.[0] || ''}"
alt="${item.title?.[0] || 'Kein Titel'}">
<h3>${item.title?.[0] || 'Kein Titel'}</h3>
<p>${item.year?.[0] || 'Unbekannt'} |
${item.dataProvider?.[0] || ''}</p>
</div>
`).join('');
} catch (error) {
ergebnisContainer.innerHTML =
'<p class="fehler">Fehler bei der Suche.</p>';
}
}
sucheEuropeana('Schuchardt');
Was ist IIIF?
IIIF Image API — URL-Aufbau:
{server}/{identifier}/{region}/{size}/{rotation}/{quality}.{format}
// Beispiel: Ganzes Bild, maximale Größe
https://example.org/image-service/abc123/full/max/0/default.jpg
// Beispiel: Ausschnitt (x,y,w,h), 800px breit
https://example.org/image-service/abc123/100,200,500,300/800,/0/default.jpg
Vorteil: Ein Bild, viele Möglichkeiten — ohne verschiedene Dateien speichern zu müssen.
Ein IIIF Manifest beschreibt ein digitales Objekt (z.B. ein Buch, ein Manuskript):
{
"@context": "http://iiif.io/api/presentation/3/context.json",
"id": "https://example.org/manifest/1",
"type": "Manifest",
"label": { "de": ["Handschrift MS 42"] },
"items": [
{
"type": "Canvas",
"label": { "de": ["Seite 1"] },
"width": 3000,
"height": 4000,
"items": [{
"type": "AnnotationPage",
"items": [{
"type": "Annotation",
"body": {
"type": "Image",
"id": "https://example.org/img/p1/full/max/0/default.jpg",
"format": "image/jpeg"
}
}]
}]
}
]
}
Wichtige Begriffe: Manifest (ganzes Objekt), Canvas (einzelne Seite), Annotation (Bild auf der Seite)
Das Muster für eine Live-Suche mit input-Event und filter():
<input type="text" id="suche" placeholder="Suchen...">
<div id="ergebnisse"></div>
<script>
const daten = [
{ titel: 'Brief an Ascoli', jahr: 1882 },
{ titel: 'Brief an Meyer-Lübke', jahr: 1887 },
{ titel: 'Brief an Vossler', jahr: 1904 }
];
function zeigeErgebnisse(liste) {
const container = document.querySelector('#ergebnisse');
container.innerHTML = liste.map(item =>
`<div><strong>${item.titel}</strong> (${item.jahr})</div>`
).join('');
}
// Initial alle anzeigen
zeigeErgebnisse(daten);
// Bei Eingabe filtern
document.querySelector('#suche')
.addEventListener('input', (event) => {
const suchbegriff = event.target.value.toLowerCase();
const gefiltert = daten.filter(item =>
item.titel.toLowerCase().includes(suchbegriff)
);
zeigeErgebnisse(gefiltert);
});
</script>
Context Engineering bedeutet: Dem LLM gezielt den richtigen Kontext geben, damit es besseren Code generiert.
Was ist Kontext für ein LLM?
Regel: Je mehr relevanter Kontext, desto besser das Ergebnis. Aber: Irrelevanter Kontext verwirrt das LLM!
Ein effektiver Prompt für API-basierte Aufgaben:
{"items": [{"title": ["Brief..."], "edmPreview": ["https://..."], "year": ["1887"]}]}
Bestandteile des Prompts:
Heute gelernt:
forEach, map, filter, findasync/awaitfilter()Assignment 2: Erstellen Sie eine interaktive DH-Datenansicht, die JSON-Daten lädt, als Karten darstellt und eine Suchfunktion bietet.
Fragen? Dann bis nächste Woche!