You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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的方式存在两个核心问题:

  1. 全局修改会影响所有JSON序列化操作,可能引发其他逻辑的兼容性问题;
  2. 仅格式化浮点数到4位小数并没有解决往返序列化一致性的核心问题——Spanner校验的是原始字符串和解析后数字的字符串表示是否完全一致,格式化后的字符串如果和原始浮点数的实际存储值不匹配,依然会触发报错。

内容的提问来源于stack exchange,提问作者Gunnar Berthelsen

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.14 09:20:34