
本文讲解如何避免因共享对象属性名导致的 json 序列化异常,重点介绍通过深拷贝、属性隔离和不可变数据模式解决跨函数修改同一对象引发的后端解析失败问题。
本文讲解如何避免因共享对象属性名导致的 json 序列化异常,重点介绍通过深拷贝、属性隔离和不可变数据模式解决跨函数修改同一对象引发的后端解析失败问题。
在 JavaScript 前端开发中,当多个函数共用同一原始数据对象(如 Kendo UI Grid 的 dataItem)并直接修改其属性时,极易引发隐式状态污染——尤其当不同业务逻辑对同一属性名赋予不兼容的数据类型(如 Models: "string" vs Models: ["array"]),会导致后端 Newtonsoft.Json 解析失败,抛出 JsonReaderException: Unexpected character encountered while parsing value。
根本原因在于:currentGrid.dataItem(row) 返回的是对底层数据模型的引用,而非副本。函数 X 和 Y 均操作同一 rawData 对象,若 X 先设 rawData.Models = "some text",随后 Y 覆盖为 rawData.Models = ["some text"],则该对象的 Models 属性类型已变更;若此时再次调用 X(期望字符串但实际是数组),或后端强类型反序列化时字段类型不匹配,即触发 JSON 解析异常。
✅ 正确实践:避免共享对象污染
1. 使用结构化克隆(推荐现代方案)
利用 structuredClone() 创建纯净副本,确保各函数操作独立数据:
function X() {
"use strict";
const tab = globalFunctionToGetActiveTab();
const view = globalFunctionToGetView(tab.id);
const tabPane = tab.getPane();
const currentGrid = tabPane.find(".k-grid:visible").data("kendoGrid");
const row = $(e).closest("tr");
const rawData = currentGrid.dataItem(row);
// 深拷贝,彻底隔离
const payload = structuredClone(rawData);
payload.Models = "some text";
OpenPageA(payload);
}
function Y() {
"use strict";
const tab = globalFunctionToGetActiveTab();
const view = globalFunctionToGetView(tab.id);
const tabPane = tab.getPane();
const currentGrid = tabPane.find(".k-grid:visible").data("kendoGrid");
const row = $(e).closest("tr");
const rawData = currentGrid.dataItem(row);
const payload = structuredClone(rawData);
payload.ModelList = ["some text"]; // 语义化命名更佳
OpenPageB(payload);
}
✅ 优势:无需重命名属性,天然支持嵌套对象/数组;✅ 兼容性:Chrome 98+、Firefox 94+、Edge 98+(旧环境可用
JSON.parse(JSON.stringify(obj))作为降级,但注意 Date、Function、undefined 等丢失)。
2. 显式解构 + 新对象字面量(轻量兼容方案)
适用于简单扁平结构,规避引用风险:
function X() {
const rawData = currentGrid.dataItem(row);
// 只提取必要字段,显式构造新对象
const payload = {
...rawData,
Models: "some text"
};
OpenPageA(payload);
}
function Y() {
const rawData = currentGrid.dataItem(row);
const payload = {
...rawData,
ModelList: ["some text"]
};
OpenPageB(payload);
}
3. 后端契约先行:定义明确 DTO 接口
在前后端协作中,应约定清晰的数据传输对象(DTO)结构。前端始终按 DTO 形状构建 payload,而非复用 UI 绑定对象:
// 统一数据契约
const PageARequest = (data) => ({
id: data.id,
name: data.name,
Models: typeof data.Models === 'string' ? data.Models : ''
});
const PageBRequest = (data) => ({
id: data.id,
items: Array.isArray(data.ModelList) ? data.ModelList : []
});
⚠️ 注意事项
-
切勿依赖全局变量缓存业务对象:
rawData是视图层数据,非状态管理器,不应被多处函数“接力修改”。 -
警惕
JSON.stringify()降级陷阱:JSON.stringify会忽略undefined、function、Symbol及循环引用,生产环境务必验证数据完整性。 -
Kendo UI 特别提示:
dataItem()返回的对象可能包含内部元数据(如_events),建议仅提取业务字段或使用toJSON()(若模型支持)。
总结
解决此类问题的核心原则是:数据所有权明确化。每个函数应对传入数据执行“消费前复制”,而非“就地修改”。通过 structuredClone、解构赋值或契约式 DTO 构建,可彻底消除因属性名复用导致的类型冲突,无需妥协于语义模糊的重命名(如 ModelList),同时提升代码可维护性与前后端协作健壮性。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











