
本文探讨了在 python 中对包含已编码 json 字符串的字典进行序列化时遇到的双重编码问题。当内部 json 字符串作为外部字典的一个字段值时,`json.dumps` 会对其进行转义。文章阐明了这种行为是符合预期的,并提供了生产者和消费者双方的正确处理策略,强调消费者需要进行多阶段解码以恢复原始数据。
当在 Python 中处理 JSON 数据时,一个常见的场景是将一个已编码为 JSON 字符串的数据作为另一个 JSON 结构中的字段值。例如,将一个复杂的 JSON 消息封装到一个更简单的信封(envelope)结构中,并通过消息队列发布。然而,直接将已编码的 JSON 字符串作为字段值再次进行 json.dumps 操作时,往往会导致内部引号被双重转义,使得消费者难以直接解析。本文将深入探讨这一现象,并提供一套清晰的生产者与消费者处理策略。
假设我们有一个复杂的 Python 字典 message,需要先根据 Avro Schema 将其序列化为 JSON 字符串 message_str。
import json
from datetime import datetime
from io import StringIO
import fastavro
# 原始数据字典
message = {
"name": "any",
"ingestion_ts": datetime.utcnow(),
"values": {
"amount": 5,
"countries": ["se", "nl"],
"source": {
"name": "web",
"url": "whatever"
}
}
}
# 示例 Avro Schema,用于序列化原始消息
avro_schema = {
"type": "record",
"name": "MyRecord",
"fields": [
{"name": "name", "type": "string"},
{"name": "ingestion_ts", "type": {"type": "long", "logicalType": "timestamp-micros"}},
{"name": "values", "type": {
"type": "record",
"name": "Values",
"fields": [
{"name": "amount", "type": "int"},
{"name": "countries", "type": {"type": "array", "items": "string"}},
{"name": "source", "type": {
"type": "record",
"name": "Source",
"fields": [
{"name": "name", "type": "string"},
{"name": "url", "type": "string"}
]
}}
]
}}
]
}
fo = StringIO()
fastavro.json_writer(fo, avro_schema, [message])
message_str = fo.getvalue()
print(f"原始 JSON 字符串:
{message_str}
")
# 示例输出: '{"name": "any", "ingestion_ts": 1703192665965373, "values": {"amount": 5, "countries": ["se", "nl"], "source": {"name": "web", "url": "whatever"}}}'现在,我们需要将这个 message_str 封装到一个新的字典 wrap 中,作为 payload 字段的值,并按照特定的 Schema 结构(其中 payload 被定义为 string 类型)发布。
# 外部包装字典
wrap = {
"sys": "my_system",
"op": "c",
"payload": message_str
}
# 再次进行 JSON 编码
wrap_str = json.dumps(wrap)
print(f"双重编码后的 JSON 字符串:
{wrap_str}
")
# 示例输出: '{"sys": "my_system", "op": "c", "payload": "{\"name\": \"any\", \"ingestion_ts\": 1703192665965373, \"values\": {\"amount\": 5, \"countries\": [\"se\", \"nl\"], \"source\": {\"name\": \"web\", \"url\": \"whatever\"}}}"}'从输出中可以看出,payload 字段的值 message_str 中的双引号被 进行了转义。这使得 wrap_str 看起来不直观,并可能导致消费者在尝试直接解析时遇到问题。
立即学习“Python免费学习笔记(深入)”;
JSON 规范规定,当一个字符串作为 JSON 对象的值时,其内部的特殊字符(如双引号 "、反斜杠 等)必须被转义。在上述例子中,message_str 本身是一个合法的 JSON 字符串。当它被赋值给 wrap 字典的 payload 键时,payload 的值在 Python 层面是一个普通的字符串。随后,json.dumps(wrap) 会将这个字符串作为 JSON 的一个字段值进行处理。根据 JSON 规范,为了确保最终的 JSON 字符串是有效的,它会将其中的所有双引号进行转义,从而产生了 " 的形式。这并非错误,而是 json.dumps 遵循 JSON 规范的正确行为。
问题的关键不在于生产者如何避免双重转义(因为在这种场景下,根据 payload 字段被定义为 string 的 Schema,生产者当前的编码方式是符合规范的),而在于消费者如何正确地处理这种嵌套的 JSON 字符串。消费者需要执行多阶段解码:
以下是消费者端解码的示例代码:
# 假设消费者接收到 wrap_str
received_wrap_str = wrap_str # 模拟接收到的数据
# 1. 解码外部 JSON 结构
wrapped_object = json.loads(received_wrap_str)
print(f"解码外部 JSON 后:
{wrapped_object}
")
# 示例输出: {'sys': 'my_system', 'op': 'c', 'payload': '{"name": "any", "ingestion_ts": 1703192665965373, "values": {"amount": 5, "countries": ["se", "nl"], "source": {"name": "web", "url": "whatever"}}}'}
# 2. 从 payload 字段中提取内部 JSON 字符串
payload_json_str = wrapped_object["payload"]
print(f"提取的 payload 字符串:
{payload_json_str}
")
# 3. 对内部 JSON 字符串进行解码(此处使用 fastavro.json_reader)
# 注意:需要提供原始的 Avro Schema
# 重新创建 StringIO 对象以供 fastavro.json_reader 使用
payload_stream = StringIO(payload_json_str)
decoded_payload_records = []
for record in fastavro.json_reader(payload_stream, avro_schema):
decoded_payload_records.append(record)
print(f"最终解码的原始消息:
{record}
")
# 示例输出: {'name': 'any', 'ingestion_ts': 1703192665965373, 'values': {'amount': 5, 'countries': ['se', 'nl'], 'source': {'name': 'web', 'url': 'whatever'}}}通过上述两步解码过程,消费者能够完全恢复原始的 message 数据,避免了因双重转义而导致的解析错误。
当一个字典字段的值本身是一个已编码的 JSON 字符串时,对其进行再次 JSON 编码会导致内部引号被转义。这并非编码错误,而是 JSON 规范的正常行为。解决此问题的关键在于消费者端采用多阶段解码策略:首先解码外部 JSON 结构,然后将 payload 字段提取出的字符串再次解码。理解数据 Schema 的定义和生产者与消费者之间的数据契约是成功处理此类问题的核心。
以上就是Python 中处理嵌套 JSON 字符串字段的双重编码与解码策略的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号