現在的工作大都是使用微軟內建的 Json 序列化套件 System.Text.Json,為什麼要用可以參考 黑大這一篇,在尋求 Json Compare/Diff 解決方案時大都是看到 Newtonsoft(Json.NET) 的 JsonDiffPatch 做法,經同事分享 System.Text.Json 已經有人實作出來了,知道後立馬套用
開發環境
- Windows 11
- JetBrains Rider 2022.1.1
- .NET 6
比對的功能主要有兩個方法, Diff 和 DeepEquals
- Diff:列出差異
- DeepEquals:回傳布林
這可能不適合用在測試,因為他描述的問題不夠詳細,無法得知哪裡比對失敗
接下來我們就來看看 System.Text.Json 和 Json.NET 的用法吧
Newtonsoft(Json.NET)
原生的 Json.NET 已經有提供 DeepEquals 方法,JsonDiffPatch.Net 則是基於 Json.NET 發展的套件,擴充了 Diff 方法,開始之前請先安裝套件
Install-Package JsonDiffPatch.Net -Version 2.3.0
dotnet add package JsonDiffPatch.Net --version 2.3.0
它會依賴 Newtonsoft.Json
DeepEquals
下面則是使用 JToken.DeepEquals 的例子
[TestMethod]
public void 比對兩個一樣的JObject()
{
JObject source = new JObject
{
{ "Integer", 12345 },
{ "String", "A string" },
{ "Items", new JArray(1, 2) }
};
JObject dest = new JObject
{
{ "Integer", 12345 },
{ "String", "A string" },
{ "Items", new JArray(1, 2) }
};
var isEquals = JToken.DeepEquals(source, dest);
Assert.IsTrue(isEquals);
}
Diff
JsonDiffPatch 的使用方式如下:
[TestMethod]
public void 比對兩個不一樣的JObject()
{
var source = new JObject
{
{ "Integer", 12345 },
{ "String", "A string" },
{ "Items", new JArray(1, 2) }
};
var dest = new JObject
{
{ "integer", 12345 },
{ "String", "A string" },
{ "Items", new JArray(1, 2, new JArray { "a", "b" }) }
};
var diffPath = new JsonDiffPatch();
var diff = diffPath.Diff(source, dest);
if (diff != null)
{
Console.WriteLine(diff.ToString());
}
Assert.IsNotNull(diff);
}
System.Text.Json.Json
.NET 6 替 System.Text.Json 新增 DOM 的控制物件:JsonNode、JsonArray、JsonObject、JsonValue 這可以更有效率的處理 Json 結構,如需詳細資訊,請參閱 JSON DOM 選項。
System.Text.Json.JsonDiffPath 基於System.Text.Json.Json 套件的擴充,可以用它取代 jsondiffpatch.net 的功能。
JsonDocument、JsonElement、JsonNode 支援 DeepEquals 方法,也有 JsonAssert,讓我們在測試專案驗證。
更詳細的功能可以參考以下system-text-json-jsondiffpatch
開始之前請先安裝套件
Install-Package SystemTextJson.JsonDiffPatch.MSTest -Version 1.3.0
dotnet add package SystemTextJson.JsonDiffPatch.MSTest --version 1.3.0
它會依賴 SystemTextJson.JsonDiffPatch、System.Text.Json
DeepEquals
[TestMethod]
public void 比對兩個不一樣的JsonDocument()
{
var source = new JsonObject
{
{ "Integer", 12345 },
{ "String", JsonValue.Create("A string") },
{ "Items", new JsonArray(1, 2) }
};
var dest = new JsonObject
{
{ "integer", 12345 },
{ "String", "A string" },
{ "Items", new JsonArray(1, 2, new JsonArray { "a", "b" }) }
};
var left = JsonDocument.Parse(source.ToJsonString());
var right = JsonDocument.Parse(dest.ToJsonString());
var isEquals = left.DeepEquals(right);
Assert.IsFalse(isEquals);
}
Diff
JsonDiffPatcher
比對 Json 字串,通過 Diff 方法可以知道這兩個物件差異的結構在甚麼地方
[TestMethod]
public void 比對兩個一樣的Json字串_via_JsonDiffPatcher()
{
var source = new JsonObject
{
{ "Integer", 12345 },
{ "String", "A string" },
{ "Items", new JsonArray(1, 2) }
};
var dest = new JsonObject
{
{ "Integer", 12345 },
{ "String", "A string" },
{ "Items", new JsonArray(1, 2) }
};
var left = source.ToJsonString();
var right = dest.ToJsonString();
var diff = JsonDiffPatcher.Diff(left, right);
if (diff != null)
{
Console.WriteLine(JsonSerializer.Serialize(diff));
}
Assert.IsNull(diff);
}
除了Json 字串,還有很多型別,參考下圖
JsonObject
[TestMethod]
public void 比對兩個不一樣的JsonObject()
{
var source = new JsonObject
{
{ "Integer", 12345 },
{ "String", JsonValue.Create("A string") },
{ "Items", new JsonArray(1, 2) }
};
var dest = new JsonObject
{
{ "integer", 12345 },
{ "String", "A string" },
{ "Items", new JsonArray(1, 2, new JsonArray { "a", "b" }) }
};
var diff = source.Diff(dest);
if (diff != null)
{
Console.WriteLine(JsonSerializer.Serialize(diff));
}
Assert.IsNotNull(diff);
}
JsonNode
[TestMethod]
public void 比對兩個不一樣的JsonNode()
{
var source = new JsonObject
{
{ "Integer", 12345 },
{ "String", JsonValue.Create("A string") },
{ "Items", new JsonArray(1, 2) }
};
var dest = new JsonObject
{
{ "integer", 12345 },
{ "String", "A string" },
{ "Items", new JsonArray(1, 2, new JsonArray { "a", "b" }) }
};
var left = JsonNode.Parse(source.ToJsonString());
var right = JsonNode.Parse(dest.ToJsonString());
var diff = left.Diff(right);
if (diff != null)
{
Console.WriteLine(JsonSerializer.Serialize(diff));
}
Assert.IsNotNull(diff);
}
JsonAssert
上面的例子是先使用 Diff 方法知道哪裡不一樣,再把它們印出來,當我們在測試程式碼就可以直接使用組合技Assert.That.JsonAreEqual(o1, o2, true);,那個 true 的參數可以把不一樣的地方輸出到 Console
[TestMethod]
public void 比對兩個不一樣的JsonObject_via_JsonAssert()
{
var source = new JsonObject
{
{ "Integer", 12345 },
{ "String", JsonValue.Create("A string") },
{ "Items", new JsonArray(1, 2) }
};
var dest = new JsonObject
{
{ "integer", 12345 },
{ "String", "A string" },
{ "Items", new JsonArray(1, 2, new JsonArray { "a", "b" }) }
};
Assert.That.JsonAreEqual(source, dest, true);
}
結論
System.Text.Json.JsonDiffPath 提供 DeepEquals 跟 Diff 兩個方法,DeepEquals 的效能肯定是優於 Diff 的,你得依據自己的需求來決定要使用哪一個,下圖出自:system-text-json-jsondiffpatch/Benchmark.md at main · weichch/system-text-json-jsondiffpatch (github.com)
System.Text.Json.JsonDiffPath 的擴充方法很明顯地優於 jsondiffpatch.net,開發體驗到目前為止感覺還不錯
範例位置
sample.dotblog/Json/Lab.JsonCompare at master · yaochangyu/sample.dotblog (github.com)
若有謬誤,煩請告知,新手發帖請多包涵
Microsoft MVP Award 2010~2017 C# 第四季
Microsoft MVP Award 2018~2022 .NET