数据格式速查
本课程涉及的核心数据格式对比。快速参考——详细讨论见各课程。
一、JSON
| 属性 | 值 |
| 全称 | JavaScript Object Notation |
| RFC | RFC 8259 |
| MIME 类型 | application/json |
| 文件后缀 | .json |
| 结构 | 一个完整的 JSON 值:对象 {}、数组 []、字符串、数字、布尔、null |
| 解析 | 整个文件 → JSON.parse() |
| 修改 | 读 → 改 → 写(三次 I/O) |
| 适合场景 | 配置、API 响应、数据交换、需要随机访问任意字段 |
解析代码
const data = JSON.parse(readFileSync('file.json', 'utf-8'));
console.log(data.key);
二、JSONL(JSON Lines)
| 属性 | 值 |
| 全称 | JSON Lines |
| 别名 | NDJSON、LDJSON |
| 规范 | jsonlines.org |
| 文件后缀 | .jsonl |
| 结构 | 每行一个独立完整的 JSON 值,无最外层包裹 |
| 行分隔符 | \n(LF,Unix 风格) |
| 解析 | 按行拆分 → 逐行 JSON.parse() |
| 修改 | 追加一行即可(一次 I/O) |
| 适合场景 | 日志、消息流、会话记录、事件溯源——持续追加、永不修改历史 |
核心规则
- 每行一个完整 JSON 值
- 行内换行符必须转义(
\n → \\n)
- 文件编码为 UTF-8
- 建议行末不加逗号(它不是 JSON 数组)
解析代码
const lines = readFileSync('file.jsonl', 'utf-8').trim().split('\n');
const records = lines.filter(l => l.trim()).map(JSON.parse);
console.log(records[0].type);
追加代码
const entry = { type: 'user', content: 'hello' };
appendFileSync('file.jsonl', JSON.stringify(entry) + '\n');
常见陷阱
| 陷阱 | 错误 | 正确做法 |
| 空行 | JSON.parse('') → SyntaxError | .filter(l => l.trim()) |
| 整体解析 | JSON.parse(整个文件) → SyntaxError | 必须逐行 parse |
| 行内换行 | content 字段含未转义的 \n | 始终用 JSON.stringify() 序列化 |
三、JSONL 在本项目中的使用
Claude Code 将每个对话会话存储为 ~/.claude/projects/<encoded-path>/<session-id>.jsonl。
消息行示例
用户消息行:
{"type":"user","message":{"content":"帮我写一个函数","timestamp":"..."}}
AI 响应行(含 token 用量):
{"type":"assistant","message":{"content":"好的……","model":"claude-sonnet-4-6","usage":{"input_tokens":42,...}}}
系统命令行:
{"type":"user","message":{"content":"<command-name>/clear</command-name>","timestamp":"..."}}
claude-context-bar 的读取策略
- 反向扫描:从最后一行往前找最近的
/clear 命令
- 正向扫描:从
/clear 位置往后收集 sessionCreated、firstMessage、model、totalTokens
- 关键点:利用 JSONL 的行结构实现"部分读取"——不需要解析整个文件
四、何时用哪种格式
决策流程:
数据是否需要持续追加?
├─ 是 → JSONL
└─ 否 → 是否需要随机访问任意字段?
├─ 是 → JSON
└─ 否 → 数据量是否非常大(>10MB)?
├─ 是 → JSONL(支持流式读取)
└─ 否 → JSON(更简单的工具链)