A Rust implementation that sorts package.json files according to well-established npm conventions.
Note on Compatibility: This crate is not compatible with the original sort-package-json npm package. While both tools sort package.json files, this Rust implementation uses different sorting groupings that we believe are clearer and easier to navigate. The field order is inspired by both the original sort-package-json and Prettier's package.json sorting, but organized into more intuitive logical groups.
- Sorts top-level fields according to npm ecosystem conventions (138 predefined fields)
- Preserves all data - only reorders fields, never modifies values
- Fast and safe - pure Rust implementation with no unsafe code
- Idempotent - sorting multiple times produces the same result
- Handles edge cases - unknown fields sorted alphabetically, private fields (starting with
_) sorted last
Add to your Cargo.toml:
[dependencies]
sort-package-json = "0.0.5"use std::fs;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let contents = fs::read_to_string("package.json")?;
let sorted = sort_package_json::sort_package_json(&contents)?;
fs::write("package.json", sorted)?;
Ok(())
}With custom options:
use sort_package_json::{sort_package_json_with_options, SortOptions};
let options = SortOptions { pretty: false };
let sorted = sort_package_json_with_options(&contents, &options)?;To test on a repository, run the included example which recursively finds and sorts all package.json files:
cargo run --example simple [PATH]If no path is provided, it defaults to the current directory.
Given an unsorted package.json:
{
"version": "1.0.0",
"dependencies": { "foo": "1.0.0" },
"name": "my-package",
"scripts": { "test": "vitest" }
}After sorting:
{
"name": "my-package",
"version": "1.0.0",
"scripts": { "test": "vitest" },
"dependencies": { "foo": "1.0.0" }
}Fields are sorted into 12 logical groups, followed by unknown fields alphabetically, then private fields (starting with _) at the end. The complete field order is based on both the original sort-package-json and prettier's package.json sorting implementations.
We use serde_json instead of simd-json because:
- No preserve_order support - simd-json can't maintain custom field insertion order (required for our sorting)
- Platform issues - simd-json doesn't work on big-endian architectures (#437)
cargo build --releasecargo testTests use snapshot testing via insta. To review and accept snapshot changes:
cargo insta reviewOr to accept all changes:
cargo insta accept- Field ordering test - verifies correct sorting of all field types
- Idempotency test - ensures sorting is stable (sorting twice = sorting once)
MIT
{ // 1. Core Package Metadata "$schema": "https://json.schemastore.org/package.json", "name": "my-package", "displayName": "My Package", "version": "1.0.0", "private": true, "description": "A sample package", "categories": ["linters", "formatters"], "keywords": ["sample", "test"], "homepage": "https://example.com", "bugs": { "url": "https://github.com/user/repo/issues", "email": "support@example.com" }, // 2. License & People "license": "MIT", "author": { "name": "Author", "email": "author@example.com", "url": "https://example.com" }, "maintainers": [{ "name": "Maintainer", "email": "maintainer@example.com" }], "contributors": [{ "name": "Contributor", "email": "contributor@example.com" }], // 3. Repository & Funding "repository": { "type": "git", "url": "https://github.com/user/repo.git" }, "funding": { "type": "github", "url": "https://github.com/sponsors/user" }, // 4. Package Content & Distribution "bin": { "my-cli": "./bin/cli.js" }, "directories": { "lib": "lib", "bin": "bin", "man": "man", "doc": "doc" }, "workspaces": ["packages/*"], "files": ["dist", "lib", "src/index.js"], "os": ["darwin", "linux"], "cpu": ["x64", "arm64"], // 5. Package Entry Points "type": "module", "sideEffects": false, "main": "./dist/index.cjs", "module": "./dist/index.mjs", "browser": "./dist/browser.js", "types": "./dist/index.d.ts", "exports": { ".": { "types": "./dist/index.d.ts", "import": "./dist/index.mjs", "require": "./dist/index.cjs", "default": "./dist/index.mjs", }, }, "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" }, // 6. Scripts "scripts": { "build": "tsup", "test": "vitest", "lint": "eslint .", }, // 7. Dependencies "dependencies": { "lodash": "^4.17.21" }, "devDependencies": { "typescript": "^5.0.0", "vitest": "^1.0.0" }, "peerDependencies": { "react": ">=18.0.0" }, "peerDependenciesMeta": { "react": { "optional": true } }, "optionalDependencies": { "fsevents": "^2.3.0" }, "bundledDependencies": ["internal-lib"], "overrides": { "semver": "^7.5.4" }, // 8. Git Hooks & Commit Tools "simple-git-hooks": { "pre-commit": "npx lint-staged" }, "lint-staged": { "*.ts": ["eslint --fix", "prettier --write"] }, "commitlint": { "extends": ["@commitlint/config-conventional"] }, // 9. VSCode Extension Specific "contributes": { "commands": [] }, "activationEvents": ["onLanguage:javascript"], "icon": "icon.png", // 10. Build & Tool Configuration "browserslist": ["> 1%", "last 2 versions"], "prettier": { "semi": false, "singleQuote": true }, "eslintConfig": { "extends": ["eslint:recommended"] }, // 11. Testing "jest": { "testEnvironment": "node" }, "c8": { "include": ["src/**"] }, // 12. Runtime & Package Manager "engines": { "node": ">=18.0.0" }, "packageManager": "pnpm@8.0.0", "pnpm": { "overrides": {} }, // Unknown fields (sorted alphabetically) "customField": "value", "myConfig": {}, // Private fields (sorted alphabetically, always last) "_internal": "hidden", "_private": "data", }