通过引用序列化/反序列化对象,在服务器和客户端之间传输 JSON





5.00/5 (13投票s)
本文将找出一种在服务器和客户端按引用序列化/反序列化对象的解决方案,还将序列化/反序列化具有循环引用的对象。
引言
在服务器和客户端之间以 JSON 格式传输数据时,存在一些常见的挑战。我在这里尝试解决其中几个问题:
- 按引用序列化/反序列化对象或保留对象之间的关联
- 序列化/反序列化循环引用
像 DataContractJsonSerializer 和 JavaScriptSerializer 这样的序列化器是按值进行对象序列化的。结果是丢失对象引用或对象之间的关联。这意味着,如果一个对象被其他对象引用,该对象将在所有这些对象中本地复制。因此,这是数据重复。另一个挑战是这些序列化器不支持循环引用并抛出异常。
理想情况下,如果一个对象的实例在对象图中的多个地方被引用,那么在反序列化时应该只创建一个实例。并且该创建的实例应该从其他所有地方引用。重要的是,解决方案应该同时适用于服务器和客户端。这样,从服务器到客户端以及从客户端到服务器的数据传输在反序列化后应该保留对象之间的关联。
用例
让我们从以下示例类图开始。这里的 Branch 和 Customer 类与多个类相关联。Account 类中也有一个循环引用。
根据上述图生成类。尝试将 branch 对象序列化为 JSON 格式
IEnumerable branches = Branch.GetAllBranches();
var js = new JavaScriptSerializer();
string json = js.Serialize(branches);
这将抛出以下异常
即使没有循环引用,它也会按值序列化对象。结果是丢失对象引用和数据重复。在这种情况下,将在 Account 和 Loan 中生成重复的 Customer 和 Branch 数据。
服务器端解决方案(C#,Web API)
因此,让我们开始使用 Json.NET 来修复这些问题。Json.NET 提供了许多 JavaScriptSerializer 和 DataContractSerializer 中没有的功能。例如,按引用序列化对象、循环引用、LINQ to JSON、ISO8601 日期、非默认构造函数、基于接口的反序列化等等。
首先,您需要安装 Json.NET,使用以下 NuGet 命令安装 Json.NET:
Install-Package Newtonsoft.Json
我得到了 Newtonsoft.Json 9.0.1,但您可能有其他版本。接下来,在代码中添加 Json.NET 命名空间:
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
所有设置都已完成,是时候实施解决方案了。修改我们之前的解决方案,用这个新的替换 JavaScriptSerializer。
// GET api/GetAllBranches
public HttpResponseMessage GetAllBranches()
{
var branches = Branch.GetAllBranches();
string json = JsonConvert.SerializeObject(branches, Formatting.Indented, new JsonSerializerSettings { PreserveReferencesHandling = PreserveReferencesHandling.Objects });
return Request.CreateResponse(HttpStatusCode.OK, json);
}
这里 Json.NET 按引用而不是按值序列化对象。它为每个对象分配一个“$id”值,例如 {"$id":"1"},然后稍后仅使用该 ID 引用该对象,例如 {"$ref":"1"}。据我所知,当是 Json.NET(使用 C#/.NET)反序列化这些引用时,它会将对象的适当实例放置在适当的位置。它解决了这两个问题;保留对象之间的关联和循环引用。使用下面的代码块反序列化从客户端代码传输的 JSON 数据:
// POST api/PostBranches
public HttpResponseMessage PostBranches(JObject jsonData)
{
dynamic m = jsonData;
string jBranches = m.branches.Value as string;
List deserializedBranches = JsonConvert.DeserializeObject>(jBranches, new JsonSerializerSettings { PreserveReferencesHandling = PreserveReferencesHandling.Objects });
return Request.CreateResponse(HttpStatusCode.OK);
}
客户端解决方案(javascript)
到目前为止一切都很好——但是,在 JavaScript 中反序列化这些对象的最佳方法是什么,以便我实际在适当的位置获得适当的对象实例,而不仅仅是奇怪的 $ref 字段?同时,在 JSON 中为每个 JavaScript 对象中的引用加入 $ref,这样我就可以在服务器端保留对象引用。
我在 GitHub 上找到了一个 JavaScript 库 JsonNetDecycle.js。对其进行了一些修改。思路是在反序列化时用引用对象替换“$ref”。在序列化过程中,为每个唯一对象添加“$id”,并用“$ref”和相应的对象 ID 替换对象引用。
反序列化 JSON 数据相关的代码是:
var jsObject = retrocycle(JSON.parse(jsonData));
function retrocycle(obj) {
var catalog = [];
findReferences(obj, catalog);
return resolveReferences(obj, catalog);
}
function findReferences(obj, catalog) {
// The catalogObject function walks recursively through an object graph
// looking for $id properties. When it finds an object with that property, then
// it adds it to the catalog under that key.
var i;
if (obj && typeof obj === "object") {
var id = obj.$id;
if (typeof id === "string") {
catalog[id] = obj;
}
if (Object.prototype.toString.apply(obj) === "[object Array]") {
for (i = 0; i < obj.length; i += 1) {
findReferences(obj[i], catalog);
}
}
else {
for (name in obj) {
if (obj.hasOwnProperty(name)) {
if (typeof obj[name] === "object") {
findReferences(obj[name], catalog);
}
}
}
}
}
}
function resolveReferences(obj, catalog) {
var i, item, name, id;
if (obj && typeof obj === "object") {
if (Object.prototype.toString.apply(obj) === "[object Array]") {
for (i = 0; i < obj.length; i += 1) {
item = obj[i];
if (item && typeof item === "object") {
id = item.$ref;
if (typeof id === "string") {
obj[i] = catalog[id];
}
else {
obj[i] = resolveReferences(item, catalog);
}
}
}
}
else if (obj.$values && Object.prototype.toString.apply(obj.$values) === "[object Array]") {
var arr = new Array();
for (i = 0; i < obj.$values.length; i += 1) {
item = obj.$values[i];
if (item && typeof item === "object") {
id = item.$ref;
if (typeof id === "string") {
arr[i] = catalog[id];
}
else {
arr[i] = resolveReferences(item, catalog);
}
}
else {
arr[i] = item;
}
}
obj = arr;
}
else {
for (name in obj) {
if (obj.hasOwnProperty(name)) {
if (typeof obj[name] === "object") {
item = obj[name];
if (item) {
id = item.$ref;
if (typeof id === "string") {
obj[name] = catalog[id];
}
else {
obj[name] = resolveReferences(item, catalog);
}
}
}
}
}
}
}
//removeAutoGenProperty(catalog);
return obj;
}
function removeAutoGenProperty(catalog) {
for (i = 0; i < catalog.length; i += 1) {
var obj = catalog[i];
if (obj && typeof obj === "object") {
var id = obj['$id'];
if (typeof id != "undefined") {
delete obj['$id'];
}
}
}
}
调用以下函数将 JavaScript 对象序列化为 JSON,Web API 将进一步使用它进行反序列化。decycle 函数以对象作为输入参数并返回 JSON。
function decycle(obj) {
var catalog = []; // Keep a reference to each unique object or array
var newObj = getDecycledCopy(obj, catalog);
return newObj;
}
function getDecycledCopy(obj, catalog) {
// The createReferences function recurses through the object, producing the deep copy.
var i; // The loop counter
var name; // Property name
var nu; // The new object or array
switch (typeof obj) {
case "object":
// typeof null === 'object', so get out if this value is not really an object.
// Also get out if it is a weird builtin object.
if (obj === null || obj instanceof Boolean || obj instanceof Date || obj instanceof Number || obj instanceof RegExp || obj instanceof String) {
return obj;
}
for (i = 0; i < catalog.length; i += 1) {
if (catalog[i] === obj) {
return { $ref: i.toString() };
}
}
// Otherwise, accumulate the unique value and its id.
obj.$id = catalog.length.toString();
catalog.push(obj);
// If it is an array, replicate the array.
if (Object.prototype.toString.apply(obj) === "[object Array]") {
nu = [];
for (i = 0; i < obj.length; i += 1) {
nu[i] = getDecycledCopy(obj[i], catalog);
}
}
else {
// If it is an object, replicate the object.
nu = {};
for (name in obj) {
if (Object.prototype.hasOwnProperty.call(obj, name)) {
nu[name] = getDecycledCopy(obj[name], catalog);
}
}
}
return nu;
case "number":
case "string":
case "boolean":
default:
return obj;
}
}
序列化后的输出 JSON 数据将如下所示:
{
"$id": "1",
"AccountNumber": "IN-1700774952",
"Balance": 0.0,
"OpeningDate": "2016-08-23T14:39:55.8165458+05:30",
"Branch": {
"$id": "2",
"Code": "IN-Delhi-1700774952",
"HeadOffice": {
"$ref": "2"
},
"Bank": {
"$id": "3",
"Name": "State Bank Of India",
"Code": "SBI"
},
"City": "Delhi"
},
"Customer": null
}, …
最终想法
所以,您有了,可以使用 Json.NET 按引用序列化和反序列化对象,并使用客户端的 JavaScript 库 JsonNetDecycle.js 使其与客户端兼容。它保持引用完整。还将防止数据冗余和循环引用问题。
我非常乐意收到您的反馈。