C# 技巧 :JSON处理

7317e228775927f4a33481624d2639b9.jpeg

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  
     }  
 }  
}

这是我们得到的字典

7337764cab9855a27bb95f47801bb348.jpeg

现在,在我们的服务中,无论在什么地方,只要我们需要获得“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我们需要转换我们的扩展方法。

  1. System.Text.Json 的 DeepInsert

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 变得越来越复杂,我们需要扩展和改进我们用来处理它的工具。

如果你喜欢我的文章,请给我一个赞!谢谢

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值