JSON,我们了解并喜欢它。今天,如果不使用 JSON,我们就无法做任何与 Web 相关的事情,而在 C# 开发中,这通常意味着使用Newtonsoft.json nuget。
Newtonsoft.json 是一个了不起的 nuget,这就是为什么它是在 C# 中使用 JSON 这么长时间的事实标准。但是,它不支持一些小事情,我们需要自己编写它们。
我将在这里展示其中的 2 个:
1. Deep insert
在 JObject 中插入值很简单:
var jobject = new JObject { };
jobject["a"]=1;
jobject["b"]="text";
当我们需要在顶层添加一个简单的值时,这很好,但是我们如何向更深层次添加一些东西呢?
让我们看一下以下 JSON
{
"user": {
"address": {
"streetName": "aa"
}
}
}
我们如何将 HouseNumber 属性添加到_地址_中?我们需要获取_地址_对象,确保它不是 NULL,如果它为 NULL,则创建一个新对象,如果它不是 NULL,则更新现有对象。现在认为我们需要向下添加 2 级(或更多)的东西......
这有点痛苦。所以也许我们可以找到一种新的方法来做到这一点:
public static void DeepInsert(this JObject obj, string path, JToken value)
{
var current = obj;
var pathParts = path.Split('.');
for (int i = 0; i < pathParts.Length; i++)
{
var part = pathParts[i];
if (i != (pathParts.Length - 1))
{
var innerObject = current[part];
if (innerObject == null)
{
innerObject = new JObject();
current[part] = innerObject;
current = (JObject)innerObject;
}
else
{
current = (JObject)innerObject;
}
}
else
{
current[part] = value;
}
}
}
所以这是 JObject 的简单扩展方法,它获取一个路径(“user.address.houseNumber”)和一个要插入的值。
我们将路径拆分为其组成部分的列表并迭代它们,每次我们“更深入”地进入 JSON 对象,直到我们到达最后一个路径并将其添加到当前级别。
在此过程中,如果JSON中不存在我们需要的级别,我们将创建它,因此,如果“用户”没有“地址”对象,我们将创建它,如果存在,我们将更新它。
下面是一个示例:
var jobject = new JObject { };
jobject.DeepInsert(“a”, 1);
jobject.DeepInsert(“b.c”, 2);
jobject.DeepInsert(“b.d”, 3);
jobject.DeepInsert(“b.e.f”, 5);
jobject.DeepInsert(“b.e.g”, 6);
结果将是:
{
“a”: 1,
“b”: {
“c”: 2,
“d”: 3,
“e”: {
“f”: 5,
“g”: 6
}
}
}
2. Flatten a JSON
一旦我们有一个JSON对象,我们可能想要从中读取,而JObject确实有很好的方法来从JSON中读取数据:
var jobject = new JObject { };
var a=jobject["a"];
var b= jobject.SelectToken("inner.b");//SelectToken can read nested properties
只有一个问题:遍历 JObject 可能会对性能造成很大影响。
如果我们有一个 JObject,我们想在应用程序运行时大量读取,我们可能需要一种更有效的方法来执行此操作。
例如,如果我们将服务配置保存在 JObject 中,我们可以在服务加载时将其转换为更高效的对象,并在服务运行时始终从该更高效的对象中读取。
还有什么比字典更有效的呢?
public static Dictionary<string, JToken> Flatten(this JObject obj, string previousPath = null)
{
var result = new Dictionary<string, JToken>();
foreach (var prop in obj.Properties())
{
var currentPropName=previousPath != null ? $"{previousPath}.{prop.Name}" : prop.Name;
result.Add(currentPropName, prop.Value);
if (prop.Value.Type == JTokenType.Object)
{
var innerDictionary = ((JObject)prop.Value).Flatten(currentPropName);
foreach (var inner in innerDictionary)
{
result.Add(inner.Key, inner.Value);
}
}
}
return result;
}
因此,我们有一个扩展方法,可以将 JObject 转换为 Dictionary。
如果我们使用上一个示例中的 JSON,并且
{
“a”: 1,
“b”: {
“c”: 2,
“d”: 3,
“e”: {
“f”: 5,
“g”: 6
}
}
}
这是我们得到的字典
现在,在我们的服务中,无论在什么地方,只要我们需要获得“b.e.g”的值,我们都可以轻松做到:
var g= flat["b.e.g"]
(“b.e.g”可能听起来不像我们需要得到的东西,但在真正的应用程序中,它可能是“connections.sql.usersDB”)
现在,我们不必遍历图形来获取值,而是可以执行效率更高的操作(从字典中读取应该是 o(1) )。
System.Text.Json
虽然 Newtonsoft.json 很棒,但它在性能方面显示出了它的年龄。因此,Microsoft创建了一个名为 System.Text.Json 的新 JSON 处理库,它现在已内置于较新版本的 ASP.NET 中。
如果我们想开始使用 System.Text.Json 而不是 Newtonsoft.json我们需要转换我们的扩展方法。
public static void DeepInsert(this JsonObject obj, string path, JsonNode value)
{
var current = obj;
var pathParts = path.Split('.');
for (int i = 0; i < pathParts.Length; i++)
{
var part = pathParts[i];
if (i != (pathParts.Length - 1))
{
var innerObject = current[part];
if (innerObject == null)
{
innerObject = new JsonObject();
current[part] = innerObject;
current = (JsonObject)innerObject;
}
else
{
current = (JsonObject)innerObject;
}
}
else
{
current[part] = value;
}
}
}
如您所愿,方法签名之外没有更改,我们只是将 JObject 更改为 JsonObject,将 JToken 更改为 JsonNode。 毫不奇怪,Microsoft不想对Newtonsoft.json的工作方式做出太大改变(Newtonsoft.json的开发者詹姆斯·牛顿-金现在在Microsoft工作)。
2. System.Text.Json 的展平
public static Dictionary<string, JsonElement> Flatten(this JsonElement obj, string previousPath = null)
{
var result = new Dictionary<string, JsonElement>();
foreach (var prop in obj.EnumerateObject())
{
var currentPropName = previousPath != null ? $"{previousPath}.{prop.Name}" : prop.Name;
result.Add(currentPropName, prop.Value);
if (prop.Value.ValueKind == JsonValueKind.Object)
{
var innerDictionary = prop.Value.Flatten(currentPropName);
foreach (var inner in innerDictionary)
{
result.Add(inner.Key, inner.Value);
}
}
}
return result;
}
这也差不多,在签名中,我们将 JObject 改为 JsonElement。 我们改变了测试值类型的方式
if (prop.Value.Type == JTokenType.Object)
自
if (prop.Value.ValueKind == JsonValueKind.Object)
这也是相同的,只是名称不同。
您可能会问:为什么我们将 JsonObject 用于 DeepInsert 方法,而将 JsonElement 用于 Flatten 方法?
这是因为 JsonObject 和 JsonNode 没有获取 JsonValueKind 的方法(希望 Microsoft 稍后会添加这个)。
因此,我们需要创建一个 JsonDocument 对象,该对象是一个表示 JSON 值的只读对象,从此对象中我们需要使用 RootElement 属性。
//convert a JsonObject to a JsonDocument
var jsonDocument = JsonDocument.Parse(jsonObject.ToString());
//flatten the JsonDocument
var flat = jsonDocument.RootElement.Flatten();
摘要
JSON 很棒,但随着我们使用的 JSON 变得越来越复杂,我们需要扩展和改进我们用来处理它的工具。