Files
lserp_cs_6.0/docs/superpowers/specs/2026-07-30-browser-json-datatable-complex-values-design.md
T

125 lines
6.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 浏览器 JSON 复杂字段兼容设计
## 背景
网页通过 `OpenOnRightClick` 将选中行 JSON 传入旧 WinForms 客户端。当前代码直接使用 Newtonsoft.Json 将 JSON 数组反序列化为 `DataTable`。当行数据包含对象或数组字段时,例如:
```json
{
"$styles": {
"rowstyle": "{color:'#FFFF00','background-color':'#FF0000'}"
}
}
```
`DataTableConverter` 无法把 `StartObject``StartArray` 直接写入普通 `DataColumn`,因此抛出 `Unexpected JSON token when reading DataTable`。字段名 `$styles` 本身合法,真正的问题是字段值为嵌套对象。
## 目标
- 保留对象和数组字段,不删除 `$styles` 或其他未来字段。
- 将复杂值转换成紧凑 JSON 字符串后再生成 `DataTable`
- 保持纯标量字段现有的列名、值和类型推断行为。
- 只影响网页与旧客户端交互的数据边界,不改变全局 Newtonsoft.Json 行为。
- 同时兼容右键调用和网页返回行两个浏览器入口。
## 非目标
- 不修改 WPF 启动、窗口或宿主逻辑。
- 不修改数据库中的 JSON。
- 不扁平化嵌套对象,不删除未知字段。
- 不替换项目中所有 `DeserializeObject<DataTable>` 调用。
- 不改变旧模块的菜单查找、参数替换和 DLL 加载流程。
## 方案比较
### 方案一:在浏览器边界归一化复杂列(采用)
解析网页 JSON,扫描整批记录,找出任一行中出现对象或数组值的列。将这些列的所有非空值统一转换为字符串,其中对象和数组使用紧凑 JSON;其他纯标量列保持不变。归一化后继续使用现有 Newtonsoft.Json `DataTable` 转换。
优点:影响范围小;保留现有标量类型;能处理未来任意字段名;可复用在两个浏览器入口。
### 方案二:手工构造全部为 `object` 的 DataTable
可以容纳复杂值,但会改变所有列的类型,可能影响比较、筛选、参数替换和旧模块中的类型判断,兼容风险较大。
### 方案三:注册全局 Newtonsoft.Json 转换器
可以统一处理,但会影响整个旧系统中大量 JSON 反序列化入口,难以证明不会改变其他模块行为,范围过宽。
## 详细设计
### 公共转换入口
`Lskj.Util.JsonUtil` 中增加一个含义明确的新方法,专门把可能包含复杂字段的网页行 JSON 转为 `DataTable`。不修改现有 `ToDataTable``ToDataRow` 的语义,避免影响未知调用方。
该方法接受:
- 顶层 JSON 数组,用于网页右键选中行。
- 顶层单个 JSON 对象,用于网页返回一行数据;内部统一包装成数组处理。
空输入、非对象行或其他不符合约定的顶层结构视为无效输入,抛出可诊断异常,继续交给当前宿主的既有异常保护策略处理。
### 复杂列识别
必须先扫描全部记录,再执行转换。只要某列在任意一行中的值为 `JObject``JArray`,该列就被标记为复杂列。
不能只转换当前遇到的复杂值。例如第一行是数字、第二行是对象时,如果第一行已让 `DataTable` 将列推断为数值类型,第二行即使变成字符串仍会发生类型冲突。
### 值归一化规则
对于复杂列:
- `JObject` 转换为不带缩进和额外空白的 JSON 字符串。
- `JArray` 转换为不带缩进和额外空白的 JSON 字符串。
- 同列中的字符串保持原字符串内容。
- 同列中的数字、布尔值等标量转换为稳定的字符串表示,确保整列类型一致。
- `null` 保留为空值,不转换为空字符串。
对于从未出现对象或数组的列,不进行任何预处理,由现有 `DataTableConverter` 继续推断原有类型。
### 调用点
仅替换以下两个浏览器桥接入口的直接反序列化:
1. `Lskj.Control.BrowserSetting.BsOpenModuleHandler.OpenRightModule`
2. `Lskj.Control.BrowserSetting.BsOpenModuleHandler.AddReturnRow`
`OpenRightModule` 后续仍把原有 `DataRow[]` 交给 `BaseRightMenu.ExecRightMenu`。菜单 ID 查询、占位符替换、`PUR_4004` 模块解析和 DLL 加载逻辑不变。
`AddReturnRow` 后续仍执行现有产品前缀处理并加入 `StaticControl.ReturnRowLisy`
### 下游兼容性
经过归一化的 `DataRow` 再序列化传给动态模块时,复杂字段已经是普通字符串。下游 `DynamicModuleModel``DynamicModuleDetailModel` 等现有 `DataTable` 反序列化可继续工作,无需全局改造。
## 异常处理
- 合法对象/数组字段不再产生 `StartObject``StartArray` 异常。
- 语法错误的 JSON、非对象行等仍作为真实错误报告,不静默删除数据。
- 转换方法不吞异常;沿用当前宿主已经建立的异常保护策略,避免用空表或残缺数据掩盖真实输入错误。
- 错误信息应保留原始异常,便于定位具体字段和输入。
## 验证方案
### 转换级测试
1. 当前实际 `$styles` 对象:成功生成一行,`$styles` 为紧凑 JSON 字符串。
2. 任意名称的嵌套对象字段:成功并保留字段名和内容。
3. 数组字段:成功并保留为紧凑 JSON 字符串。
4. 同一复杂列在不同行中分别为对象、字符串、数字和 `null`:整列稳定转换,不发生类型冲突。
5. 只有标量的原有 JSON:列和值与修改前一致。
6. 单对象输入:用于 `AddReturnRow` 时成功生成一行。
7. 无效 JSON 和非对象数组元素:明确失败,不产生不完整数据。
### 集成验证
1. 使用当前待办任务 JSON 点击网页“待办任务”,不再出现 `$styles``DataTable` 反序列化错误。
2. 右键配置 ID 能继续解析,模块编号 `PUR_4004` 正常传入原 DLL 加载流程。
3. 不含复杂字段的网页右键功能行为不变。
4. 网页返回行功能仍可回填,包含复杂字段时不崩溃。
5. 分别通过旧 WinForms 启动和 WPF 宿主启动验证相同行为。
## 构建与交付
改动位于旧代码项目。完成后重新编译并更新运行目录使用的 `Lskj.Util.dll``Lskj.Control.dll`WPF 项目无需为本修复修改代码。