JSON Toolkit

JSON für Konfiguration, Fixtures und lokale Daten verwalten

Fazit: Konfiguration muss geschichtet und validiert werden; Fixtures müssen deterministisch, lesbar und diffbar sein. Werden diese beiden Dateiarten schlecht verwaltet, bleiben die Troubleshooting-Kosten lange hoch.

Einsteiger~10 Min. LesezeitVeröffentlicht 2026-10-30Aktualisiert 2026-10-30
Titelbild für JSON für Konfiguration, Fixtures und lokale Daten verwalten

Kurz gesagt: Bauen Sie Konfiguration als Standardwerte, Umgebung und lokale Überschreibungen mit Validierung beim Start auf, bevor Sie sich um das Dateiformat kümmern; machen Sie Fixtures in Zeit, IDs, Schlüsselreihenfolge und Einrückung deterministisch, damit sie diffbar bleiben, und automatisieren Sie erst dann die Maskierung.

Diese beiden JSON-Arten nehmen nicht an der Geschäftslogik teil, doch sie bestimmen, „wie lange ein neuer Kollege braucht, um loszulegen“ und „wie lange es dauert, ein Produktionsproblem zu lokalisieren“.

1. Konfiguration: eine dreischichtige Struktur

Schicht Quelle Gepflegt von Committet
Standardwerte Konstanten im Code / default.json Entwicklern Ja
Umgebungskonfiguration config/production.json, Umgebungsvariablen Betrieb Ja (ohne Geheimnisse)
Lokale Überschreibungen .env.local, config/local.json Einzelpersonen Nein (gitignored)

Merge-Reihenfolge: Standardwerte → Umgebung → lokal, wobei jede spätere Schicht die vorherige überschreibt. Führen Sie nur so tief zusammen, wie es die Geschäftslogik braucht; nicht blind tief zusammenführen – tiefes Zusammenführen macht Probleme wie „Arrays wurden unerwartet verkettet“ extrem schwer zu erkennen.

/**
 * Merges configuration layers by priority; later layers override earlier ones, and arrays are replaced wholesale rather than concatenated.
 * @param layers Configuration layers from lowest to highest priority
 * @returns The merged configuration object
 */
function mergeConfigLayers(layers: Array<Record<string, unknown>>): Record<string, unknown> {
  return layers.reduce<Record<string, unknown>>((mergedConfig, currentLayer) => {
    for (const [configKey, configValue] of Object.entries(currentLayer)) {
      const isPlainObject =
        typeof configValue === 'object' && configValue !== null && !Array.isArray(configValue);
      const existingValue = mergedConfig[configKey];

      if (isPlainObject && typeof existingValue === 'object' && existingValue !== null) {
        mergedConfig[configKey] = mergeConfigLayers([
          existingValue as Record<string, unknown>,
          configValue as Record<string, unknown>,
        ]);
        continue;
      }

      // Arrays and scalars are always replaced wholesale to avoid accidental element concatenation
      mergedConfig[configKey] = configValue;
    }
    return mergedConfig;
  }, {});
}

2. Beim Start validieren, nicht bis zur Laufzeit warten

Die Kosten eines Konfigurationsfehlers wachsen exponentiell damit, wie spät er entdeckt wird:

Wann er entdeckt wird Kosten
Beim Start Eine Logzeile, neu starten und weitermachen
Bei der ersten Anfrage Einige Funktionen scheitern, eine Stunde Debugging
Wenn ein geplanter Job feuert Alarme mitten in der Nacht
Am Abrechnungstag Ein Abgleichsvorfall
import Ajv2020 from 'ajv/dist/2020';
import configSchema from './config.schema.json' with { type: 'json' };

const ajv = new Ajv2020({ allErrors: true, strict: true });

/**
 * Validates the configuration and throws immediately on failure, implementing fail fast.
 * @param rawConfig The raw merged configuration
 * @returns The validated configuration
 */
export function assertConfig(rawConfig: unknown): Record<string, unknown> {
  const validate = ajv.compile(configSchema);
  if (!validate(rawConfig)) {
    const messages = (validate.errors ?? []).map(
      (errorItem) => `${errorItem.instancePath || '/'} ${errorItem.message ?? ''}`,
    );
    throw new Error(`配置校验失败:\n${messages.join('\n')}`);
  }
  return rawConfig as Record<string, unknown>;
}

Unterstützende Prinzipien:

  • Sofort beenden, wenn ein Pflichtelement fehlt; keinen „plausibel aussehenden“ Standardwert liefern (etwa die Datenbankadresse auf localhost setzen);
  • Sofort bei Typfehlern beenden; der String "false" ist nicht der Boolean false, Umgebungsvariablen müssen nach dem Lesen also ausdrücklich konvertiert werden;
  • Geheimnisse kommen nie ins Repository; lesen Sie sie nur aus Umgebungsvariablen oder einem Secret-Management-Dienst.

3. Welches Format für Konfigurationsdateien

Datei Format Grund
package.json Standard-JSON Der Paketmanager akzeptiert nur das
tsconfig.json JSONC Kommentare erlaubt, offiziell unterstützt
.vscode/settings.json JSONC Wie oben
Geschäftskonfiguration YAML oder JSONC Braucht Kommentare, die jeden Eintrag erklären
Umgebungsvariablenliste .env Flache Schlüssel-Wert-Paare, leicht zu injizieren

Ein praktischer Test: Wird diese Datei von nicht-menschlichen Werkzeugen gelesen? Ja → Standard-JSON; liest nur Ihr eigener Code sie → ein Dialekt ist in Ordnung.

4. Test-Fixtures: Verzeichnisse und Benennung

tests/fixtures/
├─ valid/
│  ├─ order-minimal.json
│  ├─ order-full.json
│  └─ order-with-null-fields.json
├─ invalid/
│  ├─ order-missing-id.json
│  ├─ order-price-as-number.json
│  └─ order-nested-broken.json
└─ edge/
   ├─ big-integer.json
   ├─ emoji-and-surrogate.json
   └─ deep-nesting-64.json

Eine gute Benennungskonvention ist objekt-szenario: order-missing-field ist weit nützlicher als test1.json. Nicht-ASCII-Dateinamen verursachen gelegentlich Kodierungsprobleme in plattformübergreifenden Toolchains, halten Sie Dateinamen also englisch, während der Inhalt chinesisch bleiben kann.

4.1 Drei Arten von Fällen, drei Arten von Erwartungen

Verzeichnis Erwartung Zweck
valid/ Validierung besteht Hauptfluss, Typgenerierung
invalid/ Validierung scheitert, und der Fehler zeigt auf das erwartete Feld Fehlerbehandlung, Meldungstext
edge/ Verhalten ist ausdrücklich (bestehen oder scheitern ist egal, muss aber geprüft werden) Regressionsschutz

JSONTestSuite verwendet die Präfixe y_ / n_ / i_ für dieselben drei Arten (soll akzeptieren / soll ablehnen / implementierungsabhängig), und Sie können diese Benennungskonvention direkt übernehmen.

5. Determinismus: Fixtures diffbar machen

Instabile Fixtures machen Code-Review nutzlos:

Rauschquelle Behandlung
Aktuelle Zeit Auf eine Konstante festlegen, etwa 2026-01-01T00:00:00Z
Zufällige IDs / UUIDs Eine aufsteigende Sequenz oder feste Werte verwenden
Schlüsselreihenfolge Zur Generierungszeit alphabetisch sortieren
Float-Formatierung Einheitlich zwei Dezimalstellen behalten oder in Strings umwandeln
Zeitzone Überall UTC verwenden
Einrückung Fest auf 2 Leerzeichen, in CI von Prettier geprüft
/**
 * Recursively sorts keys to produce stable, diffable JSON text.
 * @param value The value to serialize
 * @param indentWidth Indentation width
 * @returns A JSON string with a stable order
 */
function toStableJson(value: unknown, indentWidth = 2): string {
  /**
   * Recursively sorts object keys.
   * @param input Any JSON value
   * @returns A new value with sorted keys
   */
  const sortKeysDeep = (input: unknown): unknown => {
    if (Array.isArray(input)) {
      return input.map((item) => sortKeysDeep(item));
    }
    if (typeof input === 'object' && input !== null) {
      return Object.fromEntries(
        Object.keys(input)
          .sort()
          .map((entryKey) => [entryKey, sortKeysDeep((input as Record<string, unknown>)[entryKey])]),
      );
    }
    return input;
  };

  return JSON.stringify(sortKeysDeep(value), null, indentWidth);
}

6. Maskierung und Compliance

Echte Daten können nicht direkt als Fixtures verwendet werden:

  • Namen → Platzhalter wie „John Doe“ und „Jane Roe“;
  • Telefonnummern → offensichtlich fiktive Nummern wie 13800138000;
  • Ausweisnummern → gefälschte Nummern mit korrektem Format, aber ungültiger Prüfziffer;
  • Adressen → die Provinz-/Stadtstruktur behalten, die Hausnummer weglassen;
  • Beträge → die Größenordnung behalten, aber niemals echte Kontoinformationen behalten.

Am besten hat das Team ein scripts/mask-fixture.mjs, das über ein exportiertes Produktionsbeispiel läuft und eine maskierte Version erzeugt, damit es nicht heißt „drei Stellen von Hand ändern, den Rest vergessen“.

7. Migration und Versionen

Auch Fixtures veralten. Ergänzen Sie ein Feld schemaVersion im Fixture oder legen Sie eine VERSION-Datei ins Verzeichnis:

{
  "schemaVersion": 3,
  "id": "1001",
  "amount": "89.50"
}

Wenn der API-Vertrag aktualisiert wird:

  1. Ein neues Fixture-Verzeichnis ergänzen (v3/); alte Fixtures nicht in place ändern;
  2. Die alten Fixtures für Regression behalten, bis die alte API-Version ausgemustert wird;
  3. Beide Versionen in CI ausführen, um Rückwärtskompatibilität zu bestätigen.

8. Checkliste

  • [ ] Die dreischichtige Konfigurationsstruktur ist klar, und lokale Überschreibungen werden nicht committet;
  • [ ] Validierung geschieht beim Start, und ein Fehler beendet sofort;
  • [ ] Geheimnisse kommen nur aus Umgebungsvariablen; Konfigurationsdateien enthalten nur Platzhalter;
  • [ ] Fixtures sind in valid/, invalid/, edge/ aufgeteilt, und die Namen drücken das Szenario aus;
  • [ ] Fixtures sind deterministisch (feste Zeit, IDs und Reihenfolge) und diffbar;
  • [ ] Echte Daten wurden vom Maskierungsskript verarbeitet;
  • [ ] Vertragsaktualisierungen fügen ein neues Versionsverzeichnis hinzu, statt in place zu bearbeiten.

FAQ

Wie sollten Konfigurationsschichten geordnet und zusammengeführt werden?

Verwenden Sie drei Schichten – Standardwerte, Umgebungskonfiguration und lokale Überschreibungen – und führen Sie Standardwerte → Umgebung → lokal zusammen, wobei jede spätere Schicht die vorherige überschreibt. Führen Sie nur so tief zusammen, wie es die Geschäftslogik braucht, und ersetzen Sie Arrays und Skalare als Ganzes, damit tiefes Zusammenführen Arrays nicht unerwartet verkettet. Nehmen Sie die lokale Überschreibungsdatei in die gitignore, statt persönliche Einstellungen zu committen.

Warum Konfiguration beim Start validieren?

Die Kosten eines Konfigurationsfehlers wachsen exponentiell damit, wie spät er entdeckt wird: eine Logzeile beim Start gegenüber einem Abgleichsvorfall am Abrechnungstag. Validieren Sie beim Start gegen ein JSON Schema und beenden Sie sofort bei fehlenden Pflichtelementen oder Typfehlern, statt einen „plausibel aussehenden“ Standardwert zu liefern.

Sollte ich Produktionsdaten direkt als Test-Fixtures verwenden?

Nein. Namen, Telefonnummern, Ausweisnummern, Adressen und Beträge brauchen alle Platzhalter oder offensichtlich fiktive Werte, und das Team sollte ein scripts/mask-fixture.mjs haben, das über einen Produktionsexport läuft und eine maskierte Version erzeugt, damit Handbearbeitungen nichts übersehen.