Spanner中PARSE_JSON浮点数往返序列化失败及Python客户端方案问询
解决Spanner PARSE_JSON的“无法通过字符串表示往返序列化”错误(Python客户端实现)
在Spanner中执行PARSE_JSON解析包含特定浮点数的JSON字符串时,会触发报错:“有效JSON解析错误,无法通过字符串表示往返序列化”。例如执行以下SQL时会报错:
SELECT PARSE_JSON('{"domInteractive_h": [{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {"n": 143, "mean": 688.43356, "max": 6000.0, "min": 76.0}, {}, {}, {}, {}, {}, {}, {}], "domInteractive": {"n": 143, "mean": 688.43356, "max": 6000.0, "min": 76.0}} ') AS json_data;
但将mean值改为1688.43356或2688.43356时,查询可正常执行。在SQL中添加wide_number_mode=>'round'参数可以解决该问题,以下是Python客户端的两种有效实现方案:
原因说明
Spanner的PARSE_JSON默认对浮点数的往返序列化一致性做严格校验:解析后的数字再转成字符串,必须和原始字符串完全一致。某些十进制浮点数(如688.43356)在转成二进制浮点数存储时会丢失精度,导致Spanner校验失败。wide_number_mode='round'参数会让Spanner放宽校验,允许通过四舍五入处理这种精度差异。
方案1:在查询中直接指定wide_number_mode参数
直接在Python客户端构造的SQL语句中,给PARSE_JSON添加wide_number_mode=>'round'参数,和SQL层面的解决逻辑一致。
from google.cloud import spanner # 初始化Spanner客户端 client = spanner.Client() instance = client.instance("你的实例ID") database = instance.database("你的数据库ID") # 定义SQL和JSON字符串 sql = """ SELECT PARSE_JSON(@json_input, wide_number_mode=>'round') AS json_data; """ json_input = '''{"domInteractive_h": [{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {"n": 143, "mean": 688.43356, "max": 6000.0, "min": 76.0}, {}, {}, {}, {}, {}, {}, {}], "domInteractive": {"n": 143, "mean": 688.43356, "max": 6000.0, "min": 76.0}}''' # 执行查询 with database.snapshot() as snapshot: results = snapshot.execute_sql(sql, params={"json_input": json_input}) for row in results: print("解析结果:", row[0])
方案2:在Python端精确序列化浮点数
如果不想依赖Spanner的参数,可以在Python序列化JSON时,确保浮点数的字符串表示精确,避免触发Spanner的严格校验。推荐用decimal.Decimal处理浮点数,生成精确的十进制字符串。
import json from decimal import Decimal from google.cloud import spanner # 自定义JSON序列化函数,将浮点数转为Decimal def serialize_float(obj): if isinstance(obj, float): # 将浮点数转为字符串再转Decimal,保留精确的十进制表示 return Decimal(str(obj)) raise TypeError(f"无法序列化类型: {type(obj)}") # 构造原始数据结构 raw_data = { "domInteractive_h": [{}]*16 + [{"n": 143, "mean": 688.43356, "max": 6000.0, "min": 76.0}] + [{}]*6, "domInteractive": {"n": 143, "mean": 688.43356, "max": 6000.0, "min": 76.0} } # 序列化时使用自定义函数,生成精确的JSON字符串 json_str = json.dumps(raw_data, default=serialize_float) # 初始化Spanner客户端并执行查询 client = spanner.Client() instance = client.instance("你的实例ID") database = instance.database("你的数据库ID") sql = "SELECT PARSE_JSON(@json_input) AS json_data;" with database.snapshot() as snapshot: results = snapshot.execute_sql(sql, params={"json_input": json_str}) for row in results: print("解析结果:", row[0])
为什么之前的自定义RoundedFloat无效?
修改全局json.encoder.float的方式存在两个核心问题:
- 全局修改会影响所有JSON序列化操作,可能引发其他逻辑的兼容性问题;
- 仅格式化浮点数到4位小数并没有解决往返序列化一致性的核心问题——Spanner校验的是原始字符串和解析后数字的字符串表示是否完全一致,格式化后的字符串如果和原始浮点数的实际存储值不匹配,依然会触发报错。
内容的提问来源于stack exchange,提问作者Gunnar Berthelsen
相关产品推荐
相关产品推荐

