Showing Posts From

JSON

Python JSONDecodeError 排查:NaN、不完整结构、数字字符串的常见坑

JSONDecodeError 定位 json.loads() 报错时,JSONDecodeError 对象携带精确位置信息: import jsontry: data = json.loads(content) except json.JSONDecodeError as e: print(f"错误信息: {e.msg}") print(f"行号: {e.lineno}, 列号: {e.colno}") print(f"字符偏移: {e.pos}") print(f"出错内容: {content[max(0, e.pos-50):e.pos+50]}")原因一:裸 NaN / Infinity JSON 规范不允许 NaN 和 Infinity 作为数值字面量,只有加了引号的字符串 "NaN" 才合法: # 合法 json.loads('{"value": "NaN"}') # OK,返回字符串# 非法 json.loads('{"value": NaN}') # JSONDecodeError: Expecting value json.loads('{"value": Infinity}') # JSONDecodeError如果数据源返回了裸 NaN,需要预处理: import recleaned = re.sub(r'\bNaN\b', '"NaN"', content) data = json.loads(cleaned)原因二:内容不是完整 JSON 常见错误是将多个 JSON 对象直接拼接(不用数组包裹): # 非法:两个对象并排 content = '{"a": 1}, {"b": 2}' json.loads(content) # JSONDecodeError# 合法:用数组包裹 content = '[{"a": 1}, {"b": 2}]' json.loads(content) # OK另一种情况是只收到了响应的一部分(网络截断),pos 错误位置通常在字符串末尾。 原因三:数字存为字符串 JSON 中数字加了引号,解析后得到字符串而非数值: {"x": "1228.8650628969294", "z": "NaN"}data = json.loads(content) type(data["x"]) # str,不是 float# 需要手动转换 x = float(data["x"])# z 是 "NaN" 字符串,转 float 得到 nan z = float(data["z"]) # float('nan') import math math.isnan(z) # True原因四:编码问题 非 UTF-8 编码的文件用 json.loads 读取会报错: # 读取文件时指定编码 with open('data.json', 'r', encoding='utf-8') as f: data = json.load(f)# 如果来源是 bytes data = json.loads(raw_bytes.decode('utf-8'))快速调试模板 import json, tracebackdef safe_parse(content: str): try: return json.loads(content), None except json.JSONDecodeError as e: context_start = max(0, e.pos - 30) context_end = min(len(content), e.pos + 30) snippet = content[context_start:context_end] marker = ' ' * (e.pos - context_start) + '^' print(f"JSONDecodeError at line {e.lineno}, col {e.colno}:") print(snippet) print(marker) return None, e

Python JSONDecodeError 排查清单:从 NaN 到编码 BOM

json.loads 抛 JSONDecodeError 时,异常信息里有行号、列号、字节偏移量——先把这三个印出来,问题基本就定位了: import jsontry: data = json.loads(content) except json.JSONDecodeError as e: print(e) print("line:", e.lineno, "col:", e.colno, "pos:", e.pos) print(content[max(0, e.pos - 40): e.pos + 40])最后一行把出错位置前后 40 个字符打出来,肉眼就能看出问题。以下是几种常见踩坑。 1. 根本不是合法 JSON(最常见) 抓了一段"对象序列"贴进来: { "id": 1 }, { "id": 2 }这不是合法 JSON,缺个数组外壳。要么改成: [ { "id": 1 }, { "id": 2 } ]要么按字段包一层: { "objects": [...] }2. NaN / Infinity 没引号 标准 JSON 不支持 NaN、Infinity。这样合法: {"z": "NaN"} ← 字符串这样非法: {"z": NaN} ← Python json.loads 会崩服务端如果是 Python json.dumps(..., allow_nan=True) 输出的,可能就是裸 NaN。解决办法: import json data = json.loads(content, parse_constant=lambda x: None) # 或 data = content.replace("NaN", "null") data = json.loads(data)3. 结尾多逗号 也是非标准 JSON: { "a": 1, "b": 2, ← 这个逗号 Python 不接受 }用 json5 或者预处理正则去掉最后一个逗号: import re content = re.sub(r",\s*([}\]])", r"\1", content)4. UTF-8 BOM 从 Windows 记事本另存的 JSON 常带 BOM,第一个字符是 : json.loads(content) # JSONDecodeError: Expecting value: line 1 column 1 (char 0)读文件时用 utf-8-sig: with open("data.json", encoding="utf-8-sig") as f: data = json.load(f)5. 数字全被当字符串了 不会抛异常,但会让下游算错: {"x": "1228.86"}type(obj["x"]) 是 str,要计算得先转: x = float(obj["x"])批量转的话用一个小函数: def to_float_fields(d, fields): for f in fields: if f in d: d[f] = float(d[f]) return d6. 单引号不是 JSON 从 Python print(dict) 复制的字符串是单引号: {'id': 1, 'name': 'Tom'}这不是 JSON,是 Python repr。要么请对方 json.dumps 输出,要么本地 ast.literal_eval: import ast data = ast.literal_eval("{'id': 1, 'name': 'Tom'}")literal_eval 只解析字面量,比 eval 安全得多。 一句话总结 JSONDecodeError 第一步永远是把 e.pos 附近的原文打出来。九成情况能在 30 秒内定位到具体字符是啥。

JavaScript JSON 深层字符串替换:replaceAll 与递归遍历

常见写法 e = JSON.parse( JSON.stringify(e).replace( /https:\/\/old-domain\.obs\.cn-east-4\.example\.com:443/g, '/api' ) );先序列化为 JSON 字符串,全局替换目标子串,再反序列化回对象。适合简单的 URL 替换场景。 用 replaceAll 替代正则 对字面字符串使用正则容易漏掉 . 的转义(.com 写成 .com 会匹配任意字符),直接用 replaceAll 更安全: const target = 'https://old-domain.obs.cn-east-4.example.com:443';e = JSON.parse( JSON.stringify(e).replaceAll(target, '/api') );replaceAll 是纯字符串匹配,不会误伤相邻字符,可读性也更强。 JSON.stringify 会丢失特殊值 // 以下类型经 JSON.stringify 后会丢失或变形 new Date() // 变为 ISO 字符串,失去 Date 类型 new Map() // 变为 {} new Set() // 变为 {} undefined // 对象属性被整体删除 BigInt // 抛出 TypeError function // 被删除如果对象中包含这些类型,序列化 + 替换 + 反序列化会静默丢失数据。 推荐:递归遍历替换 function deepReplace(obj, from, to) { if (typeof obj === 'string') { return obj.replaceAll(from, to); } if (Array.isArray(obj)) { return obj.map(v => deepReplace(v, from, to)); } if (obj && typeof obj === 'object') { for (const k in obj) { obj[k] = deepReplace(obj[k], from, to); } } return obj; }deepReplace(e, 'https://old-domain.com', '/api');不走序列化路径,Date / Map / Set / 原型链全部保留,也适合在 hook 网络返回数据时做在线 URL 替换。 适用场景对比场景 推荐方式简单纯字符串对象 JSON.parse(JSON.stringify(e).replaceAll(...))含 Date / Map / Set 的对象 deepReplace() 递归遍历只替换字面字符串(无转义歧义) replaceAll 替代正则hook 网络数据在线修改 deepReplace()