JSON Toolkit

Mengurus JSON untuk Konfigurasi, Fixture, dan Data Setempat

Inti pati: Konfigurasi mesti berlapis dan disahkan; fixture mesti deterministik, boleh dibaca, dan boleh dibezakan. Jika dua jenis fail ini diurus dengan buruk, kos penyelesaian masalah kekal tinggi untuk masa yang lama.

Pemula~10 min bacaanDiterbitkan 2026-10-30Dikemas kini 2026-10-30
Kulit mengurus JSON untuk konfigurasi, fixture, dan data setempat

Kesimpulan utama: bina konfigurasi sebagai nilai lalai, persekitaran, dan override setempat dengan pengesahan semasa permulaan sebelum risau tentang format fail; jadikan fixture deterministik dalam masa, ID, susunan kunci, dan inden supaya ia kekal boleh dibezakan, dan hanya selepas itu automasikan penopengan.

Dua jenis JSON ini tidak mengambil bahagian dalam logik perniagaan, namun ia menentukan "berapa lama rakan sekerja baharu mengambil masa untuk mula berjalan" dan "berapa lama untuk menjejak isu pengeluaran".

1. Konfigurasi: struktur tiga lapisan

Lapisan Sumber Diselenggara oleh Dikomit
Nilai lalai Pemalar dalam kod / default.json Pembangun Ya
Konfigurasi persekitaran config/production.json, pemboleh ubah persekitaran Operasi Ya (tanpa rahsia)
Override setempat .env.local, config/local.json Individu Tidak (diabaikan git)

Urutan cantum: lalai → persekitaran → setempat, dengan lapisan kemudian menimpa yang sebelumnya. Cantum hanya sedalam keperluan perniagaan; jangan cantum mendalam secara buta — cantum mendalam menjadikan masalah seperti "tatasusunan tergabung secara tidak dijangka" sangat sukar dikesan.

/**
 * 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. Sahkan semasa permulaan, jangan tunggu sehingga masa jalan

Kos kesalahan konfigurasi membesar secara eksponen dengan betapa lambat ia ditemui:

Bila ia ditemui Kos
Semasa permulaan Satu baris log, mula semula dan teruskan
Pada permintaan pertama Sesetengah ciri gagal, sejam menyahpepijat
Apabila tugas berjadual dicetuskan Amaran di tengah malam
Pada hari penyelesaian Insiden penyesuaian
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>;
}

Prinsip sokongan:

  • Keluar serta-merta apabila item wajib tiada; jangan berikan nilai lalai yang "kelihatan munasabah" (seperti menetapkan alamat pangkalan data lalai kepada localhost);
  • Keluar serta-merta pada kesalahan jenis; string "false" bukan boolean false, jadi pemboleh ubah persekitaran mesti ditukar secara eksplisit selepas dibaca;
  • Rahsia tidak pernah masuk ke repositori; baca hanya daripada pemboleh ubah persekitaran atau perkhidmatan pengurusan rahsia.

3. Format mana yang digunakan untuk fail konfigurasi

Fail Format Sebab
package.json JSON standard Pengurus pakej hanya menerima ini
tsconfig.json JSONC Komen dibenarkan, disokong secara rasmi
.vscode/settings.json JSONC Sama seperti di atas
Konfigurasi perniagaan YAML atau JSONC Perlu komen menjelaskan maksud setiap item
Senarai pemboleh ubah persekitaran .env Pasangan kunci-nilai rata, mudah disuntik

Ujian praktikal: adakah fail ini akan dibaca oleh alat bukan manusia? Jika ya → JSON standard; jika hanya kod anda sendiri membacanya → dialek pun boleh.

4. Fixture ujian: direktori dan penamaan

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

Konvensyen penamaan yang baik ialah object-scenario: order-missing-field jauh lebih berguna daripada test1.json. Nama fail bukan ASCII kadangkala menyebabkan masalah pengekodan dalam rantaian alat merentas platform, jadi kekalkan nama fail dalam bahasa Inggeris manakala kandungan boleh kekal dalam bahasa Cina.

4.1 Tiga jenis kes, tiga jenis jangkaan

Direktori Jangkaan Tujuan
valid/ Pengesahan lulus Aliran utama, penjanaan jenis
invalid/ Pengesahan gagal, dan kesalahan menunjuk medan yang dijangkakan Pengendalian kesalahan, salinan mesej
edge/ Tingkah laku eksplisit (lulus atau gagal tidak mengapa, tetapi mesti dituntut) Perlindungan regresi

JSONTestSuite menggunakan awalan y_ / n_ / i_ untuk tiga jenis yang sama (harus diterima / harus ditolak / ditentukan pelaksanaan), dan anda boleh meminjam konvensyen penamaan itu secara langsung.

5. Determinisme: jadikan fixture boleh dibezakan

Fixture tidak stabil menjadikan semakan kod tidak berguna:

Sumber bunyi Cara mengendalikannya
Masa semasa Tetapkan kepada pemalar, contohnya 2026-01-01T00:00:00Z
ID rawak / UUID Gunakan jujukan bertambah atau nilai tetap
Susunan kunci Isih mengikut abjad semasa penjanaan
Pemformatan float Kekalkan dua perpuluhan secara seragam atau tukar kepada string
Zon masa Gunakan UTC di mana-mana
Inden Tetap 2 ruang, disemak oleh Prettier dalam CI
/**
 * 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. Penopengan dan pematuhan

Data sebenar tidak boleh digunakan terus sebagai fixture:

  • Nama → pemegang tempat seperti "John Doe" dan "Jane Roe";
  • Nombor telefon → nombor rekaan yang jelas seperti 13800138000;
  • Nombor kad pengenalan → nombor palsu dengan format betul tetapi digit semak tidak sah;
  • Alamat → kekalkan struktur negeri/bandar, gugurkan nombor rumah;
  • Jumlah → kekalkan urutan magnitud, tetapi jangan sekali-kali menyimpan maklumat akaun sebenar.

Sebaiknya pasukan mempunyai scripts/mask-fixture.mjs yang berjalan pada sampel pengeluaran yang dieksport untuk menjana versi bertopeng, mengelakkan "ubah tiga tempat dengan tangan, lupa yang lain".

7. Migrasi dan versi

Fixture juga menjadi basi. Tambah medan schemaVersion pada fixture, atau letakkan fail VERSION dalam direktori:

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

Apabila kontrak API dinaik taraf:

  1. Tambah direktori fixture baharu (v3/); jangan ubah fixture lama di tempatnya;
  2. Kekalkan fixture lama untuk regresi sehingga versi API lama dihentikan;
  3. Jalankan kedua-dua versi dalam CI untuk mengesahkan keserasian ke belakang.

8. Senarai semak

  • [ ] Struktur konfigurasi tiga lapisan jelas, dan override setempat tidak dikomit;
  • [ ] Pengesahan berlaku semasa permulaan, dan kegagalan keluar serta-merta;
  • [ ] Rahsia hanya datang daripada pemboleh ubah persekitaran; fail konfigurasi mengandungi pemegang tempat sahaja;
  • [ ] Fixture dipecahkan kepada direktori valid/invalid/edge, dan nama menyatakan senario;
  • [ ] Fixture deterministik (masa, ID, dan susunan tetap) dan boleh dibezakan;
  • [ ] Data sebenar telah diproses oleh skrip penopengan;
  • [ ] Naik taraf kontrak menambah direktori versi baharu dan bukannya menyunting di tempatnya.

FAQ

Bagaimana lapisan konfigurasi harus disusun dan dicantum?

Gunakan tiga lapisan — nilai lalai, konfigurasi persekitaran, dan override setempat — mencantum lalai → persekitaran → setempat, dengan lapisan kemudian menimpa yang sebelumnya. Cantum hanya sedalam keperluan perniagaan dan ganti tatasusunan serta skalar sepenuhnya, supaya cantum mendalam tidak menggabungkan tatasusunan secara tidak dijangka. Gitignore fail override setempat dan bukannya mengomit tetapan peribadi.

Mengapa mengesahkan konfigurasi semasa permulaan?

Kos kesalahan konfigurasi membesar secara eksponen dengan betapa lambat ia ditemui: satu baris log semasa permulaan berbanding insiden penyesuaian pada hari penyelesaian. Sahkan terhadap JSON Schema semasa permulaan dan keluar serta-merta pada item wajib hilang atau kesalahan jenis, dan bukannya memberikan nilai lalai yang "kelihatan munasabah".

Patutkah saya menggunakan data pengeluaran terus sebagai fixture ujian?

Tidak. Nama, nombor telefon, nombor kad pengenalan, alamat, dan jumlah semuanya memerlukan pemegang tempat atau nilai rekaan yang jelas, dan pasukan harus mempunyai scripts/mask-fixture.mjs yang berjalan pada eksport pengeluaran untuk menjana versi bertopeng, mengelakkan suntingan tangan yang terlepas sesuatu.