🇨🇳 中文版 🇬🇧 English
jsonfmt(JSON Formatter)是一款简单而强大的 JSON 处理工具。
众所周知,Python 自身已经内置了一个用于格式化 JSON 数据的工具:python -m json.tool
。但是它的功能过于简单,因此 jsonfmt 在此基础上做了很多实用的扩展:
🎨 它不仅可以用来漂亮的打印 JSON 数据,
🔄 也可以将 JSON、TOML、XML、YAML 数据进行互相转化,
🔎 还可以通过 JMESPATH 或 JSONPATH 来提取 JSON 中的内容。
👀 您甚至可以通过 jsonfmt 来比较两个 JSON 或其他格式的数据之间的差异。
$ pip install jsonfmt
- 处理文件中的数据。
$ jf [可选参数] [数据文件 ...]
- 处理“标准输入”中的数据。
$ echo '{"hello": "world"}' | jf [可选参数]
位置参数
files
: 要处理的数据文件,支持 JSON / TOML / XML / YAML 格式。
可选参数
-h
: 显示帮助文档。-C
: 复制模式,此模式会将处理结果复制到剪贴板。-d
: 对比模式. 此模式可以对传入的两个数据进行差异对比。-D DIFFTOOL
: 与“对比模式”类似。你可以指定一个工具来进行差异对比。-o
: 概览模式,可以显示数据结构的概览,可以用来快速了解较大的数据。-O
: 覆盖模式,会将处理后的内容覆盖到原文件。-c
: 删除 JSON 中的所有空白字符(对其他数据格式无效)-e
: 将所有字符转义成 ASCII 码-f
: 输出格式(默认值:与传入的数据格式相同,可选项:json
/toml
/xml
/yaml
)-i
: 缩进的空格数(默认值:2,范围:0~8,设置 t 时会以 Tab 作为缩进符)-l
: 提取数据时的查询语言(默认:自动识别,可选项:jmespath / jsonpath)-p QUERYPATH
: JMESPath 或 JSONPath 查询路径-s
: 按键的字母顺序对数据中的字典进行排序--set 'foo.k1=v1;k2[i]=v2'
: 要添加或修改的键值对(多个值用“;”分隔)--pop 'k1;foo.k2;k3[i]'
: 通过键指定要删除的键值对(多个值用“;”分隔)-v
: 显示版本号
为了演示 jsonfmt 的功能,我们需要先创建一份测试数据,并将其保存到文件 example.json 中。文件内容如下所示:
{
"name": "Bob",
"age": 23,
"gender": "纯爷们",
"money": 3.1415926,
"actions": [
{
"name": "eating",
"calorie": 1294.9,
"date": "2021-03-02"
},
{
"name": "sporting",
"calorie": -2375,
"date": "2023-04-27"
},
{
"name": "sleeping",
"calorie": -420.5,
"date": "2023-05-15"
}
]
}
然后,再将这份数据转换为 TOML、XML 和 YAML 格式,分别保存到文件 example.toml、example.xml 和 example.yaml 中。
这些数据文件可以在源码的 test 文件夹中找到:
test/
|- example.json
|- example.toml
|- example.xml
|- example.yaml
jsonfmt 默认的工作模式就是对数据进行格式化处理,并以带有语法高亮的形式进行打印。
选项 -i
可以指定缩进的空格数量。默认情况下,缩进为 2 个空格,允许的空格数介于 0 到 8 之间。如果想要使用制表符 tab 作为缩进,可将其设置为 t
。
选项 -s
用于按字典键名顺序对输出结果进行排序。
如果 JSON 数据中有一些非 ASCII 字符,您可以使用 -e
对它们进行转义。
$ jf -s -i 4 test/example.json
输出:
{
"actions": [
{
"calorie": 1294.9,
"date": "2021-03-02",
"name": "eating"
},
{
"calorie": -2375,
"date": "2023-04-27",
"name": "sporting"
},
{
"calorie": -420.5,
"date": "2023-05-15",
"name": "sleeping"
}
],
"age": 23,
"gender": "纯爷们",
"money": 3.1415926,
"name": "Bob"
}
如果要处理的数据来自于其他命令的输出,这时只需使用管道 |
连接两个命令,然后从“标准输入”中取即可。
$ curl -s https://jsonplaceholder.typicode.com/posts/1 | jf -i 4
输出:
{
"userId": 1,
"id": 1,
"title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
"body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nrep..."
}
选项 -c
可用于删除所有的空白和换行符,将 JSON 数据压缩成单行。
$ echo '{
"name": "alex",
"age": 21,
"items": [
"pen",
"phone"
]
}' | jf -c
输出:
{"name":"alex","age":21,"items":["pen","phone"]}
jsonfmt 同时使用 JMESPath 和 JSONPath 作为其查询语言。
JMESPath(JSON Meta Language for Expression Path)是由 AWS 推出的一种查询语言,用于处理JSON数据。在众多 JSON 查询语言中,JMESPath 似乎是使用最广、增长最快、评价最好的解决方案。它比 jq 的语法更简洁、更通用,比 JSONPath 的功能更丰富、更强大,因此我更倾向于使用它作为主要的 JSON 查询语言。
JMESPath 可以优雅地使用简单的语法从 JSON 数据中提取一部分内容,也可以将过滤后的数据组成一个新的对象或数组。JMESPath 的官方教程在这里。
-
提取 example.json 中
actions
的第一项:$ jf -p 'actions[0]' test/example.json
输出:
{ "name": "eating", "calorie": 1294.9, "date": "2021-03-02" }
-
过滤
actions
中所有calorie < 0
的项。# 此处的 `0` 表示 0 是一个数字 $ jf -p 'actions[?calorie<`0`]' test/example.json
输出:
[ { "name": "sporting", "calorie": -2375, "date": "2023-04-27" }, { "name": "sleeping", "calorie": -420.5, "date": "2023-05-15" } ]
-
显示所有键和
actions
的长度。$ jf -p '{all_keys:keys(@), actions_len:length(actions)}' test/example.json
输出:
{ "all_keys": [ "name", "age", "gender", "money", "actions" ], "actions_len": 3 }
-
按
actions
中每一项的calorie
值进行排序,并将结果定义成一个新字典。$ jf -p 'sort_by(actions, &calorie)[].{foo: name, bar:calorie}' test/example.json
输出:
[ { "foo": "sporting", "bar": -2375 }, { "foo": "sleeping", "bar": -420.5 }, { "foo": "eating", "bar": 1294.9 } ]
JSONPath 的设计灵感来源于 XPath。因此它可以像 XPath 那样通过类似于路径表达式的方式精准地定位到 JSON 文档中的任意元素,从而实现对复杂嵌套数据的高效检索、筛选与操作。
不同于 XML 的标签层级结构,JSONPath 针对 JSON 的键值对和数组进行了特殊处理,使得用户可以方便地访问多级对象属性、遍历对象和数组,以及根据条件过滤数据。
有些使用 JMESPath 难以处理的查询,JSONPath 却可以很轻松的实现。
-
通过相对路径过滤所有
name
字段:# 使用 -l 指定的查询语言为 JSONPath $ jf -l jsonpath -p '$..name' test/example.json
输出:
[ "Bob", "eating", "sporting", "sleeping" ]
在执行查询时,您可以不指定 -l
选项。jsonfmt 会先尝试使用 JMESPath 语法去解析 -p QUERYPATH
jsonfmt 的众多强大功能之一就是,您可以使用与 JSON 完全同样的方式来处理 TOML、XML 和 YAML,并任意转换结果的格式。甚至可以在单个命令中同时处理这四种格式。
-
从 toml 文件读取数据,并以 YAML 格式输出
$ jf -p '{all_keys:keys(@), actions_len:length(actions)}' test/example.toml -f yaml
输出:
all_keys: - name - age - gender - money - actions actions_len: 3
-
同时处理四种格式
$ jf -p 'actions[0]' test/example.json test/example.toml test/example.xml test/example.yaml
输出:
1. test/example.json { "name": "eating", "calorie": 1294.9, "date": "2021-03-02" } 2. test/example.toml name = "eating" calorie = 1294.9 date = "2021-03-02" 3. test/example.xml <?xml version="1.0" ?> <root> <name>eating</name> <calorie>1294.9</calorie> <date>2021-03-02</date> </root> 4. test/example.yaml name: eating calorie: 1294.9 date: '2021-03-02'
jsonfmt 支持 JSON、TOML、XML 和 YAML 格式的处理。每种格式都可以通过指定 "-f" 选项转换为其他格式。
- TOML 中不存在
null
值。因此从其他格式转换为 TOML 时,所有的 null 值都将被删除。 - XML 不支持多维数组。所以在向 XML 格式转换时,如果原数据中存在多维数组,则会产生错误的数据。
$ jf test/example.json -f yaml
输出:
name: Bob
age: 23
gender: 纯爷们
money: 3.1415926
actions:
- name: eating
calorie: 1294.9
date: '2021-03-02'
- name: sporting
calorie: -2375
date: '2023-04-27'
- name: sleeping
calorie: -420.5
date: '2023-05-15'
$ jf test/example.toml -f xml
输出:
<?xml version="1.0" ?>
<root>
<name>Bob</name>
<age>23</age>
<gender>纯爷们</gender>
<money>3.1415926</money>
<actions>
<name>eating</name>
<calorie>1294.9</calorie>
<date>2021-03-02</date>
</actions>
<actions>
<name>sporting</name>
<calorie>-2375</calorie>
<date>2023-04-27</date>
</actions>
<actions>
<name>sleeping</name>
<calorie>-420.5</calorie>
<date>2023-05-15</date>
</actions>
</root>
在开发中,我们经常需要对一些数据或者配置进行差异对比。比如对比某个 API 在传入不同参数时的返回结果,或者运维人员对比某个系统不同格式的配置文件之间的差异。
jsonfmt 默认支持多种差异对比工具,如:diff
、vimdiff
、git
、code
、kdiff3
、meld
,也支持 Windows 上的 WinMerge
和 fc
,还可以通过 -D
选项来支持其他工具。
默认情况下,jsonfmt 会首先检查电脑上是否安装了 git。如果 git 可用,jsonfmt 会调用 git config --global diff.tool
读取其配置的对比工具。如果未设置该项则使用 git 默认的差异对比工具进行处理。如果电脑上没有安装 git,则会按照 code
、kdiff3
、meld
、vimdiff
、diff
、WinMerge
、fc
的顺序进行查找。如果没有找到可用的差异对比工具,jsonfmt 会报错退出。
在差异对比模式下,jsonfmt 会先将需要对比的数据进行格式化处理(此时 -s
选项会被自动激活),并将结果保存到临时文件中,然后再调用指定的工具进行差异对比。
$ jf -d test/example.json test/another.json
输出:
--- /tmp/.../jf-jjn86s7r_example.json 2024-03-23 18:22:00
+++ /tmp/.../jf-vik3bqsu_another.json 2024-03-23 18:22:00
@@ -3,21 +3,16 @@
{
"calorie": 1294.9,
"date": "2021-03-02",
- "name": "eating"
+ "name": "thinking"
},
{
- "calorie": -2375,
- "date": "2023-04-27",
- "name": "sporting"
- },
- {
"calorie": -420.5,
"date": "2023-05-15",
"name": "sleeping"
}
],
"age": 23,
- "gender": "纯爷们",
+ "gender": "male",
"money": 3.1415926,
- "name": "Bob"
+ "name": "Tom"
}
-D DIFFTOOL
选项可以指定一款差异对比工具。只要其命令格式满足 command [options] file1 file2
即可,无论它是否在 jsonfmt 默认支持的工具列表中。
$ jf -D sdiff test/example.json test/another.json
输出:
{ {
"actions": [ "actions": [
{ {
"calorie": 1294.9, "calorie": 1294.9,
"date": "2021-03-02", "date": "2021-03-02",
"name": "eating" | "name": "thinking"
}, },
{ {
"calorie": -2375, <
"date": "2023-04-27", <
"name": "sporting" <
}, <
{ <
"calorie": -420.5, "calorie": -420.5,
"date": "2023-05-15", "date": "2023-05-15",
"name": "sleeping" "name": "sleeping"
} }
], ],
"age": 23, "age": 23,
"gender": "纯爷们", | "gender": "male",
"money": 3.1415926, "money": 3.1415926,
"name": "Bob" | "name": "Tom"
} }
如果需要向差异对比工具传递参数,可以使用 -D 'DIFFTOOL OPTIONS'
来操作。
$ jf -D 'diff --ignore-case --color=always' test/example.json test/another.json
输出:
6c6
< "name": "eating"
---
> "name": "thinking"
9,13d8
< "calorie": -2375,
< "date": "2023-04-27",
< "name": "sporting"
< },
< {
20c15
< "gender": "纯爷们",
---
> "gender": "male",
22c17
< "name": "Bob"
---
> "name": "Tom"
对于不同来源的数据,其格式、缩进,以及键的顺序可能都不一样,这时可以使用 -i
、-f
配合来进行差异对比。
$ jf -d -i 4 -f toml test/example.toml test/another.json
输出:
--- /var/.../jf-qw9vm33n_example.toml 2024-03-23 18:29:17
+++ /var/.../jf-dqb_fl4x_another.json 2024-03-23 18:29:17
@@ -1,18 +1,13 @@
age = 23
-gender = "纯爷们"
+gender = "male"
money = 3.1415926
-name = "Bob"
+name = "Tom"
[[actions]]
calorie = 1294.9
date = "2021-03-02"
-name = "eating"
+name = "thinking"
[[actions]]
-calorie = -2375
-date = "2023-04-27"
-name = "sporting"
-
-[[actions]]
calorie = -420.5
date = "2023-05-15"
name = "sleeping"
很多时候来自于程序接口的 JSON 数据非常大,这会给我们阅读、调试、处理带来很多困难。jsonfmt 提供了四种方式来处理大型 JSON 数据:
- 使用 JMESPath 或 JSONPath 读取一部分内容(前文已经做过介绍)
- 使用分页模式查看较大的 JSON 数据
- 显示大型 JSON 数据的概览
- 将处理结果复制到剪贴板
分页模式类似于 more
命令。当 JSON 数据过大而无法在窗口显示区域内完全显示时,jsonfmt 将自动以分页模式呈现结果。
分页模式的操作与 more
命令相同:
键 | 描述 |
---|---|
j | 前进一行 |
k | 后退一行 |
f 或 ctrl+f | 前进一页 |
b 或 ctrl+b | 后退一页 |
g | 跳到页面顶部 |
G | 跳到页面底部 |
/ | 搜索模式 |
q | 退出分页模式 |
下面这个 API 的返回值是一个大型 JSON 数据,您可以将此命令粘贴到终端以尝试分页模式:
$ curl -s https://jsonplaceholder.typicode.com/users | jf
有时我们只想看到 JSON 数据的概览而不关心具体细节,这时可以使用 -o
选项。
它将清除 JSON 中的子列表,并将字符串修改为 "..."
以显示概览。
如果 JSON 数据的根节点是一个列表,概览中仅保留它的第一个子元素。
$ jf -o test/example.json
输出:
{
"name": "...",
"age": 23,
"gender": "...",
"money": 3.1415926,
"actions": []
}
如果您想将处理后的结果粘贴到文件中,但终端中打印的内容超过了一页时,复制起来会比较困难。此时,您可以通过 -C
选项,将结果自动复制到剪贴板。
$ jf -C test/example.json
完成上述操作后,您可以使用 ctrl+v 或 cmd+v 将结果粘贴到其他文档中。
当同时处理多个目标时,比如:jf -C file1 file2 file3 ...
,jsonfmt 会将所有文件的处理结果都复制到剪贴板,多个结果之间使用两个换行符 '\n\n' 进行分隔。
当您需要更改输入文档中的某些内容时,请使用 --set
和 --pop
选项。
格式为 --set 'key=value'
。如果需要修改多个值,可以使用 ;
分隔:--set 'k1=v1;k2=v2'
。如果键值对不存在,则会被添加。
对于列表中的项目,请使用 key[i]
或 key.i
指定。如果索引大于或等于元素个数,则值将被追加。
# 添加 country = China,并为 actions 追加一项
$ jf --set 'country=China; actions[3]={"name": "drinking"}' test/example.json
输出:
{
"name": "Bob",
"age": 23,
"gender": "纯爷们",
"money": 3.1415926,
"actions": [
{
"name": "eating",
"calorie": 1294.9,
"date": "2021-03-02"
},
{
"name": "sporting",
"calorie": -2375,
"date": "2023-04-27"
},
{
"name": "sleeping",
"calorie": -420.5,
"date": "2023-05-15"
},
{
"name": "drinking"
}
],
"country": "China"
}
# 修改 money 和 actions[1]["name"]
$ jf --set 'money=1000; actions[1].name=swim' test/example.json
输出:
{
"name": "Bob",
"age": 23,
"gender": "纯爷们",
"money": 1000,
"actions": [
{
"name": "eating",
"calorie": 1294.9,
"date": "2021-03-02"
},
{
"name": "swim",
"calorie": -2375,
"date": "2023-04-27"
},
{
"name": "sleeping",
"calorie": -420.5,
"date": "2023-05-15"
}
]
}
# 删除 gender 和 actions[1]
$ jf --pop 'gender; actions[1]' test/example.json
输出:
{
"name": "Bob",
"age": 23,
"money": 3.1415926,
"actions": [
{
"name": "eating",
"calorie": 1294.9,
"date": "2021-03-02"
},
{
"name": "sleeping",
"calorie": -420.5,
"date": "2023-05-15"
}
]
}
当然,您也可以同时使用 --set
和 --pop
:
jf --set 'skills=["Django","Flask"];money=1000' --pop 'gender;actions[1]' test/example.json
jsonfmt 并没有专门提供将处理结果写入到文件的选项。因为使用终端的重定向符号 >
可以很方便的处理这个事情,而且不管是 Linux 还是 Windows 都支持这一操作。
$ jf -si 4 test/example.json > formatted.json
如果需要将处理结果覆盖到原文件,可以使用 -O
选项:
# 按照对象的 key 进行排序,缩进设置为 4 个空格,将 name 的值设置为 Alex,并将最终结果写入到原文件中
$ jf -s -i 4 --set 'name=Alex' -O test/example.json
- 增加 URL 支持,可以直接对比来自两个 API 的数据
- 增加 INI 格式支持
- 增加 merge 模式,将多个 JSON 或其他格式的数据按 key 进行合并