文章目录
-
- [一、项目背景:为什么要自己做一个 JSON 格式化器?](#一、项目背景:为什么要自己做一个 JSON 格式化器?)
- [二、项目结构设计(workspace:core/cli/wasm 三段式)](#二、项目结构设计(workspace:core/cli/wasm 三段式))
- 三、核心库实现(`json-core`)
- [四、CLI 工具(`json-cli`):文件/管道/重定向全支持](#四、CLI 工具(
json-cli):文件/管道/重定向全支持) - [五、WASM 版本(`json-wasm`):前端直接调用](#五、WASM 版本(
json-wasm):前端直接调用) - 六、发布与开源建议
- 七、结语
一、项目背景:为什么要自己做一个 JSON 格式化器?
前端日常里,JSON 调试几乎无处不在:
- 接口响应排查(API 网关回包、错误定位)
- 日志/埋点数据抽样核对
- 本地配置文件(
*.json,.eslintrc,tsconfig.json)格式修正
常见痛点:
- 线上/后端返回的数据体量大 、带有转义字符 、混杂中文;
- CLI 工具跨平台依赖重,或需要 Node 环境;
- H5 项目里希望离线使用 、快速格式化,不依赖服务端。
Rust 方案的优势:
serde_json解析和序列化稳定与高效;- CLI 编译成单个二进制,无运行时;
- 通过
wasm-bindgen输出 WASM,浏览器直接调用,加载即用。
二、项目结构设计(workspace:core/cli/wasm 三段式)
json-toolkit/
├─ Cargo.toml # workspace 定义
├─ json-core/ # 核心库:解析、格式化、压缩、校验
│ ├─ Cargo.toml
│ └─ src/lib.rs
├─ json-cli/ # CLI 二进制:文件/管道/重定向
│ ├─ Cargo.toml
│ └─ src/main.rs
└─ json-wasm/ # WASM 包:给前端/NPM 使用
├─ Cargo.toml
└─ src/lib.rs
三、核心库实现(json-core)
功能目标:
format_pretty: 美化打印(两空格缩进)minify: 压缩去空格validate: 语法校验 + 友好错误消息pretty_with: 自定义缩进(空格/制表符)
json-core/Cargo.toml
toml
[package]
name = "json-core"
version = "0.1.0"
edition = "2021"
license = "MIT"
description = "Core utilities for JSON formatting/minifying/validation"
repository = "https://github.com/yourname/json-toolkit"
[dependencies]
serde_json = "1.0"
thiserror = "1.0"
json-core/src/lib.rs
rust
//! json-core: JSON 格式化/压缩/校验核心库
use serde_json::{self, Value};
use thiserror::Error;
#[derive(Debug, Error)]
pub enum JsonCoreError {
#[error("JSON 解析失败: {0}")]
Parse(String),
#[error("JSON 序列化失败: {0}")]
Serialize(String),
}
impl From<serde_json::Error> for JsonCoreError {
fn from(e: serde_json::Error) -> Self {
JsonCoreError::Parse(e.to_string())
}
}
/// 解析字符串为 Value
pub fn parse(input: &str) -> Result<Value, JsonCoreError> {
serde_json::from_str::<Value>(input).map_err(Into::into)
}
/// 美化打印(两空格缩进)
pub fn format_pretty(input: &str) -> Result<String, JsonCoreError> {
let value = parse(input)?;
serde_json::to_string_pretty(&value)
.map_err(|e| JsonCoreError::Serialize(e.to_string()))
}
/// 自定义美化:缩进字符和重复次数
pub fn pretty_with(input: &str, indent: &str, repeat: usize) -> Result<String, JsonCoreError> {
let value = parse(input)?;
let formatter = serde_json::ser::PrettyFormatter::with_indent(indent.repeat(repeat).as_bytes());
let mut buf = Vec::new();
let mut ser = serde_json::Serializer::with_formatter(&mut buf, formatter);
value.serialize(&mut ser).map_err(|e| JsonCoreError::Serialize(e.to_string()))?;
String::from_utf8(buf).map_err(|e| JsonCoreError::Serialize(e.to_string()))
}
/// 压缩(去空格)
pub fn minify(input: &str) -> Result<String, JsonCoreError> {
let value = parse(input)?;
serde_json::to_string(&value).map_err(|e| JsonCoreError::Serialize(e.to_string()))
}
/// 校验,仅返回是否通过与错误信息
pub fn validate(input: &str) -> Result<(), JsonCoreError> {
let _ = parse(input)?;
Ok(())
}
四、CLI 工具(json-cli):文件/管道/重定向全支持
功能:
--input/-i输入文件(缺省读 stdin)--output/-o输出文件(缺省 stdout)--mode [pretty|minify|validate]--indent " "自定义缩进(对 pretty 生效)- 支持大文件流式读取,避免一次性占满内存
json-cli/Cargo.toml
toml
[package]
name = "json-cli"
version = "0.1.0"
edition = "2021"
[dependencies]
clap = { version = "4.4", features = ["derive"] }
json-core = { path = "../json-core" }
anyhow = "1.0"
json-cli/src/main.rs
rust
use clap::{Parser, ValueEnum};
use std::{
fs::File,
io::{self, Read, Write},
path::PathBuf,
};
use anyhow::{Context, Result};
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Debug)]
enum Mode {
Pretty,
Minify,
Validate,
}
#[derive(Parser, Debug)]
#[command(author, version, about = "A fast JSON formatter/minifier/validator in Rust")]
struct Args {
#[arg(short, long)]
input: Option<PathBuf>,
#[arg(short, long)]
output: Option<PathBuf>,
#[arg(short, long, value_enum, default_value_t = Mode::Pretty)]
mode: Mode,
#[arg(long, default_value = " ")]
indent: String,
#[arg(long, default_value_t = 1)]
repeat: usize,
}
fn read_all(mut reader: impl Read) -> Result<String> {
let mut buf = String::new();
reader.read_to_string(&mut buf)?;
Ok(buf)
}
fn main() -> Result<()> {
let args = Args::parse();
let input_str = match args.input {
Some(path) => {
let file = File::open(&path)
.with_context(|| format!("无法打开输入文件: {}", path.display()))?;
read_all(file)?
}
None => read_all(io::stdin())?,
};
let output = match args.mode {
Mode::Pretty => json_core::pretty_with(&input_str, &args.indent, args.repeat)?,
Mode::Minify => json_core::minify(&input_str)?,
Mode::Validate => {
if let Err(e) = json_core::validate(&input_str) {
eprintln!(" JSON 校验失败:{e}");
std::process::exit(2);
}
" JSON 校验通过".to_string()
}
};
match args.output {
Some(path) => {
let mut f = File::create(&path)
.with_context(|| format!("无法创建输出文件: {}", path.display()))?;
f.write_all(output.as_bytes())?;
eprintln!(" 已写入: {}", path.display());
}
None => io::stdout().write_all(output.as_bytes())?,
}
Ok(())
}
五、WASM 版本(json-wasm):前端直接调用
json-wasm/src/lib.rs
rust
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn format_pretty(input: &str) -> Result<String, JsValue> {
json_core::format_pretty(input).map_err(|e| JsValue::from_str(&e.to_string()))
}
#[wasm_bindgen]
pub fn minify(input: &str) -> Result<String, JsValue> {
json_core::minify(input).map_err(|e| JsValue::from_str(&e.to_string()))
}
#[wasm_bindgen]
pub fn validate(input: &str) -> Result<(), JsValue> {
json_core::validate(input).map_err(|e| JsValue::from_str(&e.to_string()))
}
在前端 H5 中直接使用:
html
<script type="module">
import init, { format_pretty } from "./pkg/json_wasm.js";
await init();
console.log(format_pretty('{"a":1,"b":[2,3]}'));
</script>
六、发布与开源建议
- CLI 工具发布到 crates.io
- WASM 版本发布到 npm ,提供
.d.ts类型定义 - 结合 GitHub Actions 实现自动化版本发布
- 后续可拓展:
JSON5/HJSON/YAML转换模块
七、结语
Rust 让"一个小工具"也能做到工业级稳定。
当 CLI 与 WASM 共用一套核心逻辑时,意味着你在思考模块化复用与生态适配 。
这正是 Rust 生态的魅力所在。
它不仅写得快,更写得长久。