# Migration: the `/json` preset moves to eslint-plugin-jsonc (v14.0.0)

As of `14.0.0` the `/json` entrypoint is built on `eslint-plugin-jsonc` and `eslint-plugin-package-json`
instead of `@eslint/json`. It now orders `package.json` as well, which replaces running `sort-package-json`
as a separate step.

## What changed

- The `@eslint/json` peer is gone; `eslint-plugin-jsonc` and `eslint-plugin-package-json` replace it.
- Reported rule names change from `json/*` to `jsonc/*` and `package-json/*`.
- The preset now sorts object keys and enforces a two-space indent in `.json`, `.json5`, and `.jsonc` files. It
  did not order anything before.
- Array values are sorted only in `tsconfig*.json` and `jsconfig.json`, and there only in `include`, `exclude`
  and `compilerOptions.lib`, which are sets. The same file's `extends` and `compilerOptions.paths` arrays are
  resolved in order and are left alone, as is every array elsewhere: an array in arbitrary JSON is as likely to
  be an ordered list - pipeline steps, menu entries, a glob list whose negation must follow its include - and
  reordering one of those breaks it silently.
- `package.json` field *shapes* are now validated - an `engines` field of the wrong type, or a malformed
  `packageManager` string, is reported - and a dependency declared in two collections is reported. Nothing is
  *required*: a field the
  manifest does not declare is not checked. `/package-json` is the entrypoint that requires them.
- `package.json` is ordered by `package-json/order-properties` and `package-json/sort-collections`, not by the
  generic key sorter, which is switched off for that file. `exports` is left out of the collection sorting: a
  conditional export map is matched in declared order, so alphabetising it changes which condition wins.
- Three validity checks are gone with no replacement: an empty object key (`json/no-empty-keys`); a key that
  is not Unicode-normalized (`json/no-unnormalized-keys`); and `json/no-unsafe-values`, which reported a
  number evaluating to `Infinity` or to zero, an integer outside the safe range, a subnormal number, and a
  lone surrogate in a string. Duplicate keys are still reported, as `jsonc/no-dupe-keys`. These stop being
  reported silently, so check whether your JSON relied on any of them.
- Comments remain allowed in `tsconfig*.json`, `jsconfig.json`, and `.vscode/*.json`. `.jsonc` and `.json5`
  files are linted as their own dialects, so comments there - and JSON5 grammar in `.json5` - are not reported.

## What you need to do

- **Swap the peers**: `pnpm remove @eslint/json` and
  `pnpm add -D eslint-plugin-jsonc eslint-plugin-package-json`.
- **Run `eslint --fix`** over your JSON. New ordering and indent findings are auto-fixable. A badly ordered
  file can need more than one pass, because each pass fixes one adjacent pair.
- **Drop `sort-package-json`** from your dependencies, scripts, and any pre-commit hook. The preset orders
  `package.json` through `eslint-plugin-package-json`, which reaches that tool rather than restating its order,
  so `eslint --fix` is what performs the step.

  Remove it rather than leaving it alongside. A `lint-staged` config of the common shape already routes JSON
  through `eslint --fix`, so the separate entry is a second fixer on the same file in the same run:

  ```js
  // before
  '*.{cjs,js,json,json5,jsonc,mjs,ts,tsx}': ['pnpm run eslint:fix'],
  'package.json': ['pnpm dlx sort-package-json'],   // <- delete this entry

  // after: the first entry already orders package.json
  '*.{cjs,js,json,json5,jsonc,mjs,ts,tsx}': ['pnpm run eslint:fix'],
  ```

  `pnpm dlx` resolves the newest `sort-package-json`, while the plugin pins the major it was built against.
  While those agree the duplicate is merely wasteful; once they disagree on a field's position the two fixers
  reverse each other on every run.

- **Stop using `lint-staged` to lint.** Its entries run the same scripts as the lint step, over a subset of the
  files, at a point where nothing enforces the result: a hook is skippable with `--no-verify` and is not what
  CI runs. Two checks that can disagree is worse than one that cannot.

  CI is the gate. A single aggregator script it invokes is the better shape, because local and CI then cannot
  drift, but a project whose workflow calls the lint steps directly is already enough for this - the point is
  that the enforcing run is CI's, not a hook's.

  Where `lint-staged` and `simple-git-hooks` exist for linting alone, remove both. Removing the packages does
  not uninstall the hook: delete `.git/hooks/pre-commit` by hand in any existing clone, or it keeps running
  against a config that is gone. A fresh clone is unaffected, because pnpm 10 does not run the install script
  that writes it.
- **Delete any local `jsonc/*` block** your config carries for ordering or indent; the preset now supplies it.
  `deploy_conf.json` is exempt from key sorting in the preset itself, and `composer.json` is ignored
  outright, so drop those overrides too. Leave `composer.json` to `ergebnis/composer-normalize`: nothing in
  this preset reports on it, including the indent.
- **Suppress a rule per file** where another JSON file must keep its own order, for example:

  ```js
  { files: ['seed-order.json'], rules: { 'jsonc/sort-keys': 'off' } }
  ```

- **Note the Node floor**: `eslint-plugin-package-json` needs at least `22.22.2` on the 22 line or `24.15.0`
  on the 24 line, which sets the floor `14.0.0` declares for the whole package. See
  [Migration: Node engines floor](./migration-node-floor.md).

## Why

`@eslint/json` ships validity rules only, with no indentation or array-ordering rule, so the preset could not
carry what consuming projects had configured locally. See ADR-0014.
