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é.

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í booleanfalse, 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:
- Přidejte nový adresář fixtures (
v3/); nemodifikujte staré fixtures na místě; - Držte staré fixtures pro regresi, dokud se stará verze API nestáhne;
- 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/edgea 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í.