Add serde support for Command and builder types
C-enhancementS-triage
### Please complete the following tasks
- [x] I have searched the [discussions](https://github.com/clap-rs/clap/discussions)
- [x] I have searched the [open](https://github.com/clap-rs/clap/issues) and [rejected](https://github.com/clap-rs/clap/issues?q=is%3Aissue+label%3AS-wont-fix+is%3Aclosed) issues
### Clap Version
4.5.60
### Describe your use case
A concrete use case is building a **proxy binary** that fronts multiple specialized binaries behind a single CLI entry point, where each specific binary becomes a subcommand of the proxy.
The proxy needs to provide shell completions, help text, and argument validation (acceptable if not complete) without shelling out to the underlying binaries on every invocation. However, directly linking the specific binaries (or even just their clap derives) into the proxy is not viable because:
- **Binary size**: some binaries pull in heavy dependencies (e.g. C++ bindings) that would bloat the proxy far beyond what a thin dispatcher needs
- **Traceability**: the actual work must still be performed by the specific binary for auditing and process-tracking purposes
- **Scalability**: as the number of proxied binaries grows, linking them all becomes impractical
With serde support, each binary can export its `Command` definition to a file (JSON, TOML, etc.) at build time. The proxy binary then deserializes these specs, mounts them as subcommands, and gets full CLI integration (help, completions, validation) for free — without any source-level dependency on the specific binaries.
## Related
- #5109 — "Dynamic subcommands": requested loading command definitions from files at runtime, noted `clap_serde` appears deprecated
### Describe the solution you'd like
Add an optional `serde` cargo feature that conditionally derives `serde::Serialize` and `serde::Deserialize` on `Command` and all related subtypes (`Arg`, `ArgGroup`, `ArgAction`, `ArgPredicate`, `ValueRange`, `ValueHint`, `PossibleValue`, `StyledStr`, `Str`, `OsStr`, `Id`, etc.).
Fields that are inherently non-serializable (trait objects like `ValueParser`, type-erased `Extensions`, function pointers) would be skipped with `#[serde(skip)]`.
## Example usage
```rust
// In each specific binary: export the CLI spec
let cmd = Command::new("my-tool")
.version("1.0")
.arg(Arg::new("input").long("input").action(ArgAction::Set));
let json = serde_json::to_string_pretty(&cmd)?;
std::fs::write("my-tool.cli.json", &json)?;
// In the proxy binary: reconstruct and mount as subcommands
let spec = std::fs::read_to_string("my-tool.cli.json")?;
let subcmd: Command = serde_json::from_str(&spec)?;
let proxy = Command::new("proxy")
.subcommand(subcmd)
.get_matches();
```
### Alternatives, if applicable
_No response_
### Additional Context
There is currently no built-in way to serialize/deserialize `Command`, `Arg`, `ArgGroup`, and related builder types.
This makes it difficult to build tooling that needs to inspect, transform, or reconstruct CLI definitions outside of the original binary.
Third-party crates like [`clap-serde`](https://docs.rs/clap-serde) and [`clap-serde-derive`](https://docs.rs/clap-serde-derive/) attempt to fill this gap but appear unmaintained and don't provide comprehensive coverage of clap's builder types.
4 条评论