colored_json
Colorizes JSON output with ANSI terminal codes by implementing serde_json's Formatter trait directly.
Repository Health
Technical Analysis
colored_json is a small Rust crate that adds ANSI-colored output to JSON printed on the terminal, similar to how jq colors its output by default. Rather than post-processing a JSON string with regexes, it implements serde_json’s Formatter trait directly, intercepting each token as serde_json serializes it and wrapping it in ANSI escape codes via the yansi crate.
It exposes both a low-level ColoredFormatter type for full control over Style objects per JSON element (object brackets, keys, string/integer/float/bool/null values) and high-level convenience traits (ToColoredJson, to_colored_json_auto) that colorize any AsRef<str> JSON string or serde::Serialize value in one call. A ColorMode enum handles auto-detection of TTY output and respects the NO_COLOR environment variable, so piping output to a file or another process automatically disables coloring.
What You Get
- ColoredFormatter, a Formatter wrapper usable directly with serde_json::Serializer for full control over pretty or compact output.
- A Styler struct exposing individually configurable Style values for object brackets, colons, array brackets, keys, string/integer/float/bool/null values, and whether quotation marks inherit the string style.
- ToColoredJson trait adding to_colored_json_auto(), to_colored_json(mode), and to_colored_json_with_styler() methods to any AsRef<str> type.
- Free functions to_colored_json_auto, to_colored_json, write_colored_json, and write_colored_json_with_mode for serializing any serde::Serialize value directly.
- ColorMode::Auto detection that checks IsTerminal on stdout/stderr and honors the NO_COLOR environment variable automatically.
Common Use Cases
- CLI tool JSON output - a command-line tool prints an API response or config dump as colorized JSON when writing to a terminal, and plain JSON when piped into a file.
- Custom color themes - a developer building a jq-like tool defines its own Styler with specific Color/Style combinations per JSON token type to match a house color scheme.
- Compact colorized logs - a service uses ColoredFormatter with CompactFormatter instead of PrettyFormatter to emit single-line colored JSON for terminal log output.
- NO_COLOR-compliant tooling - a CLI respects the NO_COLOR convention automatically via ColorMode::Auto without extra flag-parsing logic.
Under The Hood
Architecture Single-file crate (src/lib.rs, ~815 lines). Core design: ColoredFormatter<F: Formatter> wraps a serde_json::ser::Formatter and delegates each callback (write_null, write_bool, write_i8..u64, write_f32/f64, begin_string/end_string, write_string_fragment, begin_array/end_array, begin_object/end_object, begin_object_key/end_object_key) to a colored() helper that buffers the wrapped formatter’s raw output into a Vec<u8>, then paints it using yansi::Paint before writing to the real writer. State is tracked with a single in_object_key boolean to disambiguate string_value style from key style, since serde_json’s Formatter trait calls the same string-writing methods for both keys and values. Two independent entry points exist: the trait-based ColoredFormatter used with serde_json::Serializer::with_formatter for full serde::Serialize values, and a string-based ToColoredJson trait that first deserializes into serde_json::Value then re-serializes through the same formatter path, meaning ToColoredJson pays a parse-plus-reserialize cost for the convenience. Flat, single-responsibility architecture with no internal modules or macros; the only branch point is ColorMode::use_color() gating whether the colored or bare wrapped Formatter is used at Serializer construction.
Tech Stack Pure Rust crate, edition 2021, MSRV 1.70.0. Runtime dependencies are minimal and precisely scoped: serde 1 (trait only, no derive needed at runtime), serde_json 1 for the Value type, Serializer, and Formatter trait it hooks into, and yansi 1 for the actual ANSI styling (Color, Style, Paint). Dev-dependencies add serde’s derive feature for one test. No async runtime, no allocator tricks, and no unsafe code beyond one unsafe block in a test helper. CI (.github/workflows/ci.yaml) runs cargo fmt —check, then cargo check plus cargo test across ubuntu, macos, and windows on both stable and the pinned MSRV toolchain, a real cross-platform, cross-toolchain matrix for a project this small. No build.rs, no proc-macros, and no feature flags in Cargo.toml.
Code Quality Tests live in tests/test.rs as integration tests (eleven #[test] functions) plus doctests embedded directly in lib.rs’s module-level documentation comments (four runnable examples). Most tests assert little beyond “runs and doesn’t panic”, printing colorized output for visual inspection rather than asserting exact ANSI byte sequences, so regression coverage on the actual escape-code output is limited. Error handling forwards serde_json::Result throughout with no custom error types introduced by the crate itself. Naming is consistent and idiomatic Rust, mirroring serde_json’s own Formatter method names. rustfmt.toml is present and enforced in CI, though there is no clippy step or coverage tooling wired into CI, despite one #[allow(clippy::wrong_self_convention)] suggesting clippy was run locally at some point.
API Design The crate’s core idea, implementing serde_json::ser::Formatter directly rather than post-processing a rendered JSON string with regex, is the right technical choice for this problem: it colorizes at zero extra parse cost when the caller already has a Serialize value, correctly handles the key-vs-value ambiguity via the in_object_key flag, and composes with any existing Formatter rather than reimplementing pretty-printing. Developer experience is strong for such a small surface area: three entry-point tiers (free functions for Serialize values, a ToColoredJson trait for existing JSON strings, and ColoredFormatter/Styler for full manual control) mean simple use is a single method call while advanced use is still just composing existing serde_json types. Automatic NO_COLOR and IsTerminal detection via ColorMode::Auto is a thoughtful default that comparable crates often skip. The pattern of colorizing JSON via a custom serializer formatter is not conceptually novel, similar approaches exist elsewhere, but it is executed cleanly and the crate does exactly one thing well.