JSON Toolkit

Správa JSON pro konfiguraci, fixtures a lokální data

Shrnutí: Konfigurace musí být vrstvená a validovaná; fixtures musí být deterministické, čitelné a diffovatelné. Pokud se tyto dva druhy souborů spravují špatně, náklady na diagnostiku zůstávají dlouho vysoké.

Začátečník~10 min čteníPublikováno 2026-10-30Aktualizováno 2026-10-30
Titulní obrázek článku Správa JSON pro konfiguraci, fixtures a lokální data

Stručně: konfiguraci postavte jako výchozí hodnoty, prostředí a lokální překryvy s validací při startu, dřív než začnete řešit formát souboru; fixtures udělejte deterministické v čase, ID, pořadí klíčů a odsazení, aby zůstaly diffovatelné, a teprve poté automatizujte maskování.

Tyto dva druhy JSON se neúčastní obchodní logiky, přesto určují „jak dlouho trvá novému kolegovi se rozjet“ a „jak dlouho trvá lokalizovat produkční problém“.

1. Konfigurace: struktura tří vrstev

Vrstva Zdroj Udržuje Commitováno
Výchozí hodnoty Konstanty v kódu / default.json Vývojáři Ano
Konfigurace prostředí config/production.json, proměnné prostředí Provoz Ano (bez tajemství)
Lokální překryvy .env.local, config/local.json Jednotlivci Ne (v gitignore)

Pořadí sloučení: výchozí → prostředí → lokální, přičemž každá pozdější vrstva překrývá předchozí. Slučujte jen tak hluboko, jak obchod potřebuje; nedělejte slepé hluboké sloučení – hluboké sloučení činí problémy jako „pole se nečekaně zřetězila“ extrémně těžko postřehnutelnými.

/**
 * Sloučí vrstvy konfigurace podle priority; pozdější vrstvy překrývají předchozí a pole se nahrazují celá, nikoli zřetězují.
 * @param layers Vrstvy konfigurace od nejnižší k nejvyšší prioritě
 * @returns Sloučený konfigurační objekt
 */
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;
      }

      // Pole a skaláry se vždy nahrazují celá, aby se zabránilo nechtěnému zřetězení prvků
      mergedConfig[configKey] = configValue;
    }
    return mergedConfig;
  }, {});
}

2. Validujte při startu, nečekejte na běh

Náklad chyby konfigurace roste exponenciálně s tím, jak pozdě je objevena:

Kdy je objevena Náklad
Při startu Jeden řádek logu, restart a jde se dál
Při prvním požadavku Některé funkce chybují, hodina ladění
Když se spustí plánovaná úloha Alerts uprostřed noci
V den zúčtování Incident při reconciliaci
import Ajv2020 from 'ajv/dist/2020';
import configSchema from './config.schema.json' with { type: 'json' };

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

/**
 * Validuje konfiguraci a při selhání okamžitě vyhodí chybu, čímž implementuje fail fast.
 * @param rawConfig Surová sloučená konfigurace
 * @returns Validovaná konfigurace
 */
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>;
}

Podpůrné zásady:

  • Okamžitě ukončete, když chybí povinná položka; neposkytujte „věrohodně vypadající“ výchozí hodnotu (například výchozí adresu databáze localhost);
  • Okamžitě ukončete při typových chybách; řetězec "false" není boolean false, takže proměnné prostředí se musí po načtení explicitně převést;
  • Tajemství nikdy nepatří do repozitáře; čtěte je jen z proměnných prostředí nebo služby správy tajemství.

3. Který formát pro konfigurační soubory

Soubor Formát Důvod
package.json Standardní JSON Správce balíčků přijímá jen tento
tsconfig.json JSONC Komentáře povoleny, oficiálně podporováno
.vscode/settings.json JSONC Stejné jako výše
Obchodní konfigurace YAML nebo JSONC Potřebuje komentáře vysvětlující, co každá položka znamená
Seznam proměnných prostředí .env Ploché dvojice klíč-hodnota, snadné vkládání

Praktický test: bude tento soubor číst ne‑lidský nástroj? Pokud ano → standardní JSON; pokud ho čte jen váš vlastní kód → dialekt je v pořádku.

4. Testovací fixtures: adresáře a pojmenování

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

Dobrá konvence pojmenování je objekt-scénář: order-missing-field je mnohem užitečnější než test1.json. Názvy souborů mimo ASCII občas v nástrojových řetězcích napříč platformami způsobují problémy s kódováním, takže názvy souborů držte v angličtině, zatímco obsah může zůstat v čínštině.

4.1 Tři druhy případů, tři druhy očekávání

Adresář Očekávání Účel
valid/ Validace projde Hlavní tok, generování typů
invalid/ Validace selže a chyba ukazuje na očekávané pole Zpracování chyb, text zpráv
edge/ Chování je explicitní (projít nebo selhat je jedno, ale musí být tvrzeno) Ochrana proti regresím

JSONTestSuite používá prefixy y_ / n_ / i_ pro stejné tři druhy (má přijmout / má odmítnout / definováno implementací) a tuto konvenci pojmenování můžete rovnou vypůjčit.

5. Determinismus: udělejte fixtures diffovatelné

Nestabilní fixtures činí revizi kódu bezcennou:

Zdroj šumu Jak s ním zacházet
Aktuální čas Zafixujte ho na konstantu, např. 2026-01-01T00:00:00Z
Náhodná ID / UUID Použijte rostoucí posloupnost nebo pevné hodnoty
Pořadí klíčů Při generování seřaďte abecedně
Formátování desetinných čísel Jednotně držte dvě desetinná místa, nebo převeďte na řetězce
Časové pásmo Všude používejte UTC
Odsazení Pevně 2 mezery, kontroluje Prettier v CI
/**
 * Rekurzivně seřadí klíče, aby vzniklo stabilní, diffovatelné JSON text.
 * @param value Hodnota k serializaci
 * @param indentWidth Šířka odsazení
 * @returns JSON řetězec se stabilním pořadím
 */
function toStableJson(value: unknown, indentWidth = 2): string {
  /**
   * Rekurzivně seřadí klíče objektů.
   * @param input Libovolná hodnota JSON
   * @returns Nová hodnota se seřazenými klíči
   */
  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. Maskování a compliance

Skutečná data nelze použít přímo jako fixtures:

  • Jména → zástupné hodnoty jako „John Doe“ a „Jane Roe“;
  • Telefonní čísla → zjevně fiktivní čísla jako 13800138000;
  • Rodná čísla → falešná čísla se správným formátem, ale neplatnou kontrolní číslicí;
  • Adresy → ponechte strukturu kraje/města, vyhoďte číslo domu;
  • Částky → ponechte řád velikosti, ale nikdy nedržte skutečné informace o účtu.

Nejlepší je, když tým má scripts/mask-fixture.mjs, který projde exportovaný produkční vzorek a vygeneruje maskovanou verzi, čímž se vyhne „ručně změnit tři místa a zapomenout zbytek“.

7. Migrace a verze

Fixtures také stárnou. Přidejte do fixture pole schemaVersion, nebo do adresáře umístěte soubor VERSION:

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

Když se smlouva API upgraduje:

  1. Přidejte nový adresář fixtures (v3/); nemodifikujte staré fixtures na místě;
  2. Držte staré fixtures pro regresi, dokud se stará verze API nestáhne;
  3. Spouštějte obě verze v CI, abyste potvrdili zpětnou kompatibilitu.

8. Kontrolní seznam

  • [ ] Třívrstvá struktura konfigurace je jasná a lokální překryvy se necommitují;
  • [ ] Validace probíhá při startu a selhání okamžitě ukončí proces;
  • [ ] Tajemství pocházejí jen z proměnných prostředí; konfigurační soubory obsahují jen zástupné hodnoty;
  • [ ] Fixtures jsou rozdělené do adresářů valid/invalid/edge a názvy vyjadřují scénář;
  • [ ] Fixtures jsou deterministické (pevný čas, ID a pořadí) a diffovatelné;
  • [ ] Skutečná data prošla maskovacím skriptem;
  • [ ] Upgrade kontraktu přidává nový adresář verzí, místo aby se upravovalo na místě.

Časté dotazy

Jak mají být vrstvy konfigurace seřazeny a sloučeny?

Použijte tři vrstvy – výchozí hodnoty, konfiguraci prostředí a lokální překryvy – slučujte výchozí → prostředí → lokální, přičemž každá pozdější vrstva překrývá předchozí. Slučujte jen tak hluboko, jak obchod potřebuje, a pole i skaláry nahrazujte celá, aby hluboké sloučení nemohlo nečekaně zřetězit pole. Soubor lokálních překryvů dejte do gitignore, místo abyste commitovali osobní nastavení.

Proč validovat konfiguraci při startu?

Náklad chyby konfigurace roste exponenciálně s tím, jak pozdě je objevena: jeden řádek logu při startu versus incident při reconciliaci v den zúčtování. Validujte proti JSON Schema při startu a okamžitě ukončete u chybějících povinných položek nebo typových chyb, místo abyste poskytli „věrohodně vypadající“ výchozí hodnotu.

Mám použít produkční data přímo jako testovací fixtures?

Ne. Jména, telefonní čísla, rodná čísla, adresy i částky potřebují zástupné nebo zjevně fiktivní hodnoty a tým by měl mít scripts/mask-fixture.mjs, který projde produkční export a vygeneruje maskovanou verzi, čímž se vyhne ručním úpravám, které něco vynechají.