1. 从"1.JOSN"到JSON:一个格式的自我修养
先说个有意思的事,我收到的项目标题是"1.JOSN",字母顺序错了。但无论是JSON还是JOSN,任何一个写过几年代码的人都能第一时间反应出来,说的是JSON。这个小插曲本身就很能说明问题——JSON已经变成了那种"闭着眼睛都能认出"的通用语言,它在后端接口里,在配置文件里,在日志系统里,在消息队列里,甚至连手机App的本地缓存、自动化脚本的中间结果,都是JSON。
这篇文章就是一份JSON实战笔记。我打算把这几年在项目里反复用到、踩过坑、最后沉淀下来的东西都写出来,包括:JSON为什么能取代XML成为默认格式、多语言下读写JSON的正确姿势、怎么把JSON安全地塞进RabbitMQ、JSON Schema校验、以及一些常见到让人崩溃的解析问题。无论你是刚接触JSON的新手,还是已经写了几年接口的老手,应该都能从里面翻到点有用的。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. JSON在真实项目中的四大典型用法
2.1 配置文件:比INI更能表达嵌套结构
很多人习惯用INI或者properties做配置文件,比如:
server.port=8080
这种写法在扁平结构下没问题,但一旦配置变成两层、三层,properties就开始难看,INI更是直接没法表达数组。举个例子,你要配置一个多数据源:
properties复制datasource.primary.url=jdbc:mysql://localhost:3306/db1
datasource.primary.username=root
datasource.primary.password=123456
datasource.secondary.url=jdbc:mysql://localhost:3306/db2
datasource.secondary.username=root
datasource.secondary.password=123456
字段一多,靠前缀加点的约定来模拟层级,维护起来非常痛苦。换成JSON之后清晰得多:
json复制{
"datasource": {
"primary": { "url": "jdbc:mysql://localhost:3306/db1", "username": "root", "password": "123456" },
"secondary": { "url": "jdbc:mysql://localhost:3306/db2", "username": "root", "password": "123456" }
}
}
结构嵌套是用花括号天然表达的,不需要靠命名约定。这一点在配置项越来越复杂的微服务场景下尤其重要。我见过不少团队直接用JSON当配置文件,配合JSON Schema做配置校验,效果比传统的properties+自定义校验好得多。
2.2 接口数据交换:请求与响应的规范化
JSON能成为事实标准,最大的功臣是接口数据交换。前后端分离之后,后端返回一段JSON,前端直接解析渲染,不用再操心XML的命名空间、DTD、XSD那一堆东西。一个典型的响应结构是这样的:
json复制{
"code": 0,
"message": "success",
"data": {
"list": [ { "id": 1, "name": "张三" } ],
"total": 1
}
}
为什么大家都愿意这么干?核心就三点。第一,JSON是文本格式,任何语言都能解析,跨语言零成本;第二,JSON结构一眼能看懂,出问题直接复制到JSON在线工具里格式化,马上就能定位;第三,JSON的序列化和反序列化性能远高于XML,虽然比不上Protobuf这种二进制协议,但胜在通用和调试方便。
2.3 日志与数据采集:结构化日志
传统日志是纯文本,2026-01-01 10:00:00 ERROR something happened,这种日志只能靠正则去匹配,想统计某个字段的值非常痛苦。结构化日志是把日志写成JSON,每条日志就是一个JSON对象,字段含义明确,工县直接按字段索引查询。
比如用Python的logging库输出结构化日志,可以自己写一个JSON Formatter:
python复制import json
import logging
class JsonFormatter(logging.Formatter):
def format(self, record):
payload = {
"time": self.formatTime(record, self.datefmt),
"level": record.levelname,
"logger": record.name,
"message": record.getMessage(),
}
return json.dumps(payload, ensure_ascii=False)
logger = logging.getLogger("demo")
handler = logging.StreamHandler()
handler.setFormatter(JsonFormatter())
logger.addHandler(handler)
logger.setLevel(logging.INFO)
logger.info("用户登录成功", extra={"user_id": 1001})
这样日志直接进ELK或者其他日志平台,按user_id字段做聚合分析,效率比正则解析高出一个量级。
2.4 订阅源与资源集合:JSON数组的典型组织套路
这阵子"书源合集JSON""音乐源JSON分享"这类词很火,很多社区都在分享整理好的订阅源。抛开版权和合规的顾虑不谈,单从技术角度看,这类"合集"本质上就是一个JSON数组,数组里每个元素是一个对象,描述一个订阅源的名称、类型、地址和规则。一个典型的资源集合长这样:
json复制[
{
"name": "示例源A",
"type": "book",
"url": "https://example.com",
"update_time": "2026-03-01",
"rules": {
"search_url": "/search?q={keyword}",
"book_list": "div.list"
}
},
{
"name": "示例源B",
"type": "music",
"url": "https://example.org",
"update_time": "2026-03-05"
}
]
这种结构的好处是清晰、可扩展,以后要加字段直接往对象里塞,不影响老字段。对使用者来说,只要一个JSON数组,前端就能渲染出列表,后端也能直接入库。
不过我要提醒一句:分享和传播盗版书源、盗版音乐源的JSON合集有版权风险,这方面的技术模式可以学,但实际使用时务必注意内容合规,别踩红线。
3. 多语言解析JSON的实操笔记
3.1 Python:json库的四个关键参数
Python里处理JSON基本就是标准库json,但很多人在细节上栽跟头。先看一个最常用的场景:读取JSON文件并解析。
python复制import json
with open("config.json", "r", encoding="utf-8") as f:
data = json.load(f)
注意encoding="utf-8",少了这个参数在Windows平台上很容易因为默认编码不是UTF-8而报错。反过来,把Python字典写入JSON文件:
python复制with open("output.json", "w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False, indent=2)
这里有四个参数值得专门说一下:
ensure_ascii=False:默认情况下,json模块会把中文转成\uXXXX的ASCII转义序列,文件里看起来全是\u4e2d\u6587,极其难受。设成False才能保留原始中文字符。indent=2:格式化缩进,输出可读。如果不设,全部挤在一行,生产环境调试不方便。sort_keys=True:按key排序输出。这个在生成配置文件、做对比时有用,能保证多次生成结果一致。separators=(",", ":"):紧凑输出,去掉多余空格,适合接口返回,能省一点流量。
再看loads和dumps,它们处理的是字符串,load和dump处理的是文件对象,这个要分清。我见过有人json.loads(json_str)写成了json.load(json_str),然后报类型错误的。
3.2 Java:Jackson与IDEA生成JSON实体类插件
Java里读取JSON文件,主流方案是Jackson或者Gson。以Jackson为例:
java复制import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.File;
import java.util.List;
import java.util.Map;
public class JsonReader {
public static void main(String[] args) throws Exception {
ObjectMapper mapper = new ObjectMapper();
// 解析成Map
Map<String, Object> data = mapper.readValue(new File("config.json"), Map.class);
// 解析成List
List<Map<String, Object>> list = mapper.readValue(new File("sources.json"),
new TypeReference<List<Map<String, Object>>>() {});
System.out.println(data.get("datasource"));
}
}
用Map和List是偷懒的做法,正经项目里还是建议定义对应的实体类,这样类型安全,代码可读性也强。手写实体类麻烦?IDEA里有个很实用的插件叫"Json to Java",你直接把一段JSON粘进去,选择生成目录,它就能根据JSON结构自动生成对应的Java类,字段名、类型、嵌套关系全帮你搞定。还有个插件叫"GsonFormatPlus",在老版本的IDEA上比较流行,功能类似。
热词里提到的"idea类生成json的插件",我猜指的就是这类工具。使用的时候有个坑:如果JSON里字段命名是user_name这种下划线风格,生成Java类时默认会变成userName,但反序列化时Jackson默认要求字段完全匹配,需要在类上加上@JsonProperty("user_name")或者在全局配置里开启SNAKE_CASE策略,否则反序列化出来全是null。
3.3 LabVIEW:读写JSON文件的要点
LabVIEW写JSON不算常见,但确实有人用,特别是做仪器控制、测试台架数据采集的时候。LabVIEW本身没有内置JSON解析函数,通常用第三方工具包,比如JSONtext Toolkit或者MGI JSON库。基本套路是这样的:
- 读取:用
Read From Text File读出整个字符串,然后交给JSON解析函数转成带标签的数据结构(Cluster或Map)。 - 写入:把Cluster/Map通过
LabVIEW JSON Serialize函数转成字符串,再写入文件。
实测下来要注意两点。第一,字符串编码,建议统一用UTF-8,不然中文注释或者采集结果里的中文会乱码。第二,LabVIEW的Cluster是有固定结构的,如果JSON里的字段数量和类型跟Cluster对不上,解析会直接报错。所以我一般建议在LabVIEW里先定义一个固定的JSON模板,所有数据都按模板生成,不要做动态字段,否则排查问题会非常痛苦。
3.4 JSON转换与格式化工具:从在线工具到jq
日常处理JSON,我依赖三类工具:
- 在线格式化工具:快速看结构、检查语法。这类工具很多,随便搜"JSON格式化"就有。但要注意:别把敏感数据贴到不可信的网站上。公司内部数据我从来不用在线工具,本地能解决的尽量本地解决。
- jq命令行工具:这是处理JSON的神器,Linux下必备。比如提取某个字段:
bash复制cat sources.json | jq '.[].name'
看整个JSON的结构:
bash复制cat config.json | jq '.datasource | keys'
甚至可以直接修改JSON:
bash复制cat config.json | jq '.server.port = 9090' > new_config.json
- JSONPath:用路径表达式定位JSON里的数据,类似XPath之于XML。比如
$.store.book[*].author这种写法。写接口测试断言、做数据校验的时候,JSONPath比一层层取key方便得多。Python里可以用jsonpath-ng库,Java里可以用Jayway JsonPath。
4. 把JSON放进RabbitMQ:消息队列场景下的JSON实践
4.1 为什么消息体常用JSON
热词里有"把json放入rabbitmq",这确实是后端开发里的高频操作。RabbitMQ本身不关心你发的是什么内容,它就是个搬运工,扔进去的是字节流。但实际项目中大家几乎都约定用JSON作为消息体格式,原因很朴素:
- 生产者和消费者可能是不同语言写的,JSON跨语言无障碍。
- 消息在RabbitMQ管理后台里可以直接查看,如果是JSON,出问题一眼能看出来;要是用了Java序列化或者Protobuf,管理后台里全是乱码。
- 消息体里带个
type字段,消费者可以根据类型做路由,实现同一个队列处理多种消息。
4.2 生产者:设置content_type很重要
用Python的pika库发一个JSON消息:
python复制import json
import pika
connection = pika.BlockingConnection(
pika.ConnectionParameters(host="localhost")
)
channel = connection.channel()
channel.queue_declare(queue="task_queue", durable=True)
msg = {
"task": "send_email",
"to": "user@example.com",
"payload": {"order_id": 12345}
}
channel.basic_publish(
exchange="",
routing_key="task_queue",
body=json.dumps(msg, ensure_ascii=False).encode("utf-8"),
properties=pika.BasicProperties(
delivery_mode=2,
content_type="application/json"
)
)
connection.close()
这里有两个细节值得注意。
第一个是content_type="application/json"。虽然RabbitMQ不强制校验content_type,但设置清楚了,消费者端可以判断消息类型,消息中间件或者日志收集系统也能据此做解析。不设置的话,别人接收到消息还得猜这是什么格式。
第二个是delivery_mode=2。这表示消息持久化,RabbitMQ重启后消息还在。如果消息丢了无所谓,可以不设;但要处理订单、任务这种关键业务,不设就等着挨骂。
4.3 消费者:反序列化与异常处理
消费者端反序列化JSON要用try-except包住。为什么?因为队列里的消息来源不可控,可能某次发布版本改了消息结构,老消息还在队列里没消费完,一旦反序列化失败,消息会被重新放回队列,形成一个无限重试的循环,把消费者CPU跑满。
python复制import json
import pika
def callback(ch, method, properties, body):
try:
data = json.loads(body.decode("utf-8"))
print(f"处理任务: {data.get('task')}")
except json.JSONDecodeError:
print("消息格式异常,直接确认丢弃")
ch.basic_ack(delivery_tag=method.delivery_tag)
return
try:
# 实际业务处理
process_task(data)
ch.basic_ack(delivery_tag=method.delivery_tag)
except Exception:
ch.basic_nack(delivery_tag=method.delivery_tag, requeue=False)
connection = pika.BlockingConnection(
pika.ConnectionParameters(host="localhost")
)
channel = connection.channel()
channel.basic_qos(prefetch_count=1)
channel.basic_consume(queue="task_queue", on_message_callback=callback)
channel.start_consuming()
注意basic_nack(requeue=False),这是我踩过好多次坑才改过来的。默认的requeue=True会把无法处理的消息重新放回队列,如果消息本身有业务问题(比如字段缺失、数据不合法),重试多少次都是失败,还会阻塞后续消息。正确做法是:格式错误直接确认丢弃,业务处理失败记录到死信队列或者日志表,之后人工排查。
4.4 消息结构的版本演进
在实际项目里,消息体不是一成不变的,加了字段、改了类型都很常见。怎么保证消费者不被更新搞挂?我的经验是:
- 消息体里必须带一个版本号字段,比如
"version": 1,消费者按版本号做兼容处理。 - 消费者解析时只取自己关心的字段,别把整个对象强依赖在反序列化工具上,这样即使消息体新增了字段,老消费者也能正常工作。
- 删除字段要谨慎,先停掉所有消费者的依赖,再改生产者,再发新版本,别上来就把字段删了。
5. 2026年日历JSON数据、JSON Schema与JDM
5.1 2026年日历JSON数据的获取与结构
热词里出现"2026年日历数据json",这类需求一般来自日历组件、预约系统、或者需要展示节假日信息的应用。最正规的获取方式是用一些公开的日历API接口,返回的就是JSON。不管是谁提供的接口,返回结构大同小异,通常是按月份组织:
json复制{
"year": 2026,
"months": [
{
"month": 1,
"days": [
{ "date": "2026-01-01", "is_holiday": true, "holiday_name": "元旦" },
{ "date": "2026-01-02", "is_holiday": true, "holiday_name": "元旦" },
{ "date": "2026-01-03", "is_holiday": false }
]
}
]
}
拿到这种JSON,前端渲染日历只需要遍历数组,后端判断某天是否工作也只需要查这个结构。值得关注的一点是,日历数据往往会涉及到调休政策等动态调整,所以发布后要标一个version字段,客户端要定期拉取更新,不能缓存死数据。
5.2 JSON Schema:让数据格式可控
JSON灵活是优点,但有时候太灵活也是灾难。接口返回的字段突然从字符串变成数字,前端就可能报错;订阅源合集的字段名大小写不一致,解析就得写一堆兼容逻辑。JSON Schema就是解决这个问题的:它描述一个JSON对象应该长什么样、哪些字段必须、字段类型是什么、取值范围是什么。
比如我们要校验一个订阅源对象:
json复制{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"required": ["name", "url", "type"],
"properties": {
"name": { "type": "string", "minLength": 1 },
"url": { "type": "string", "format": "uri" },
"type": { "type": "string", "enum": ["book", "music", "video"] },
"update_time": { "type": "string", "format": "date" }
}
}
Python里用jsonschema库做校验:
python复制import json
import jsonschema
from jsonschema import validate
with open("schema.json", "r", encoding="utf-8") as f:
schema = json.load(f)
with open("sources.json", "r", encoding="utf-8") as f:
data = json.load(f)
try:
validate(instance=data, schema=schema)
print("校验通过")
except jsonschema.exceptions.ValidationError as e:
print(f"校验失败: {e.message}")
用Schema有两个明显好处:第一,接口联调的时候,给前端一个Schema,前端能根据Schema自动生成Mock数据和类型定义,后端能确保返回的数据格式不出错;第二,解析外部来源的JSON(比如爬虫数据、用户上传的JSON),先过一遍Schema再入库,能挡掉绝大多数脏数据。我在实际项目的经验是,凡是需要对接外部JSON的地方,先写Schema再写代码,能省至少一半的排查时间。
5.3 JDM(JSON Decision Model)简介
热词里有"jdm json decision model",这是个比较冷门但有意思的方向。JDM是JSON Decision Model的缩写,意思是用JSON来描述业务决策逻辑。比如保险公司的理赔规则、风控系统的审批规则,传统做法是写在代码里的if-else里,改一条规则就得发一次版。用JDM这类方案,把规则变成JSON配置,规则引擎加载JSON执行决策,业务人员改配置就能调整规则。
一个简化的决策JSON长这样:
json复制{
"decision-id": "loan-approval",
"rules": [
{
"rule-id": "r1",
"when": { "creditScore": { "gte": 650 } },
"then": { "outcome": "approved" }
},
{
"rule-id": "r2",
"when": { "creditScore": { "lt": 650 } },
"then": { "outcome": "manual_review" }
}
]
}
这种做法的好处在于规则和代码解耦,规则变化不需要重新部署应用。但也不是银弹,JDM方案往往需要配套规则编辑器、版本管理、测试工具,如果业务规则简单到只有七八条if-else,引入规则引擎反而是过度设计。
6. 常见问题与排查技巧实录
6.1 中文变成\uXXXX怎么办
前面说过,Python的json.dumps默认ensure_ascii=True,会把中文转成\uXXXX。很多人第一次遇到都以为是数据错了,其实是默认行为。
解决方式就一个,ensure_ascii=False。另外注意写入文件时也要指定encoding="utf-8",两处都对了,中文才能正常显示。
6.2 JSON解析失败的五个典型原因
我总结过JSON解析报错,绝大多数是这五种情况:
- BOM头问题:Windows下用记事本保存的UTF-8文件会带BOM头(
\ufeff),某些解析器会把它当成非法字符。Python里可以用json.load流式处理时指定编码utf-8-sig来兼容。
python复制with open("config.json", "r", encoding="utf-8-sig") as f:
data = json.load(f)
- 多余逗号:
[1, 2, 3,]这种末尾逗号,JavaScript里能过,但严格JSON不允许。报错内容是Expecting value都算隐晦的,直接搜"trailing comma"吧。 - 单引号:
{'name': '张三'}是Python字典写法,不是JSON。JSON标准只允许双引号。遇到这种情况,要么用ast.literal_eval转成Python对象再处理,要么让上游改成标准JSON。 - 注释残留:JSON标准不允许注释。但很多人写配置文件时习惯加
//注释,导致解析失败。如果配置文件由人维护,建议用JSON5或者YAML替代,没必要硬撑着用严格JSON。 - 数字被截断:JSON数字类型对应到Java的double或者long,精度有限。如果JSON里有很大很大的整数或者很多位的小数,直接转double会丢精度。解决办法是把这类字段定义成字符串,或者用Java的
BigDecimal,Python里可以用parse_float=Decimal。
6.3 超大JSON怎么处理
几GB的JSON日志文件,直接json.load会把内存吃爆。这时候要流式解析。Python可以用ijson库:
python复制import ijson
with open("huge.json", "r", encoding="utf-8") as f:
items = ijson.items(f, "item")
for item in items:
# 一条一条处理
print(item)
Java里直接用Jackson的流式APIJsonParser,逐token读取,而不是一次性readValue。
6.4 一个排查JSON问题的通用套路
如果你拿到一个JSON解析出错,别急着怀疑代码。先把原始内容原样复制到本地,用jq .命令跑一下,或者粘贴到本地vscode里,右下角选择JSON语言模式,看vscode能不能正常高亮。如果vscode也报错,那就是JSON本身有问题,跟代码无关。如果vscode正常但代码解析报错,那就要检查是不是文件编码、BOM、或者解析库版本的问题。我这些年处理的所有JSON解析bug,用这个思路都能在三分钟内定位到根因。
7. 补一个实用的JSON小技巧:用JSON写配置时的变量替换
最后再分享一个我常用的技巧。很多时候JSON配置文件里有敏感信息,比如密码、API Key,不可能明文提交到git仓库。我的做法是:配置文件是JSON模板,里面放占位符,部署的时候用环境变量替换。
json复制{
"database": {
"url": "jdbc:mysql://${DB_HOST}:3306/db",
"username": "${DB_USER}",
"password": "${DB_PASSWORD}"
}
}
替换逻辑用Python写非常简单:
python复制import json
import os
import re
def replace_env_vars(data):
if isinstance(data, dict):
return {k: replace_env_vars(v) for k, v in data.items()}
elif isinstance(data, list):
return [replace_env_vars(item) for item in data]
elif isinstance(data, str):
pattern = re.compile(r"\$\{(\w+)\}")
def repl(match):
return os.environ.get(match.group(1), match.group(0))
return pattern.sub(repl, data)
return data
with open("config.template.json", "r", encoding="utf-8") as f:
config = json.load(f)
config = replace_env_vars(config)
with open("config.json", "w", encoding="utf-8") as f:
json.dump(config, f, ensure_ascii=False, indent=2)
这样做的好处是:配置模板入库是安全的,真正的敏感值只存在于部署环境的变量里。实际项目中我连续用了很多年,没出过安全事故。
8. 我对JSON的三个核心体会
写了这么多年代码,接触的格式越来越多,但JSON始终是那个"最不容易出错"的选择。它不像XML那样沉重,不像YAML那样对缩进敏感,不像Protobuf那样需要编译流程。JSON就是那种"几乎没有学习成本"的东西,任何一个新同事接手项目,看到JSON都不会觉得陌生。
当然,JSON也有它的短板:没有注释能力,不适合写复杂的配置文件;没有日期类型,日期全靠字符串约定;数字精度有限。但我们不能苛求一个格式满足所有场景,关键是知道它在哪个场景下最好用。对我来说,JSON适合做接口传输、结构化日志、消息队列的数据载体、以及轻量级配置文件;不适合做大数据量的存储格式,更不适合当复杂的规则语言。
在我看来,使用JSON的核心原则就一句话:保持JSON尽可能地简单。不在JSON里塞逻辑,不在JSON里写注释,不在JSON里做复杂的嵌套。遇到复杂场景,先考虑拆分而不是越套越深。结构化日志里打一行JSON,往往比写一大段"人类友好"的日志文本有用得多——因为这行JSON可以被系统解析、可以被工具查询、可以跨部门传递,这些能力不是"好看",而是实打实的生产力。
