相关文章推荐
买醉的石榴  ·  仗剑天涯 ...·  3 月前    · 
逼格高的山寨机  ·  Amazon.com·  1 年前    · 
c#反序列化json片段

c#反序列化json片段

小伙伴们大家好,在日常工作中,难免与json数据打交道,通常在处理大型json文档时,如果只对一小部分数据感兴趣,这时可能会比较头疼,往往为了解析一个片段信息而不得不定义整个json结构的c#类。

今天我为大家分享一下我日常工作中如解析取片段信息,为了使用和拓展,这里分享使用 Json.NET(newtonsoft) 以及微软原生(System.Text.Json)两种方法供大家参考。事不宜迟,现在开始吧!~ ‍

准备工作

为了方便起见,这里提供一段较为简短的json数据:

{
    "title": "zhihu666",
    "publisher": "zhihu",
    "shortDescription": "这是简短描述",
    "description": "这是详细描述",
    "contact": {
        "companyName": "知乎",
        "serviceProviderName": "张三",
        "serviceEmail": "123@zhihu.com",
        "servicePhone": "17717410000",
        "serviceQQ": "",
        "serviceWeChat": "",
        "weibo": ""
}

假设我们我们在这数据中只关心contact中的内容,下面我们定义一下contact的实体,下面我们需要将json中的contact的片段数据解析到Contact实体中。

public class Contact
    public string CompanyName { get; set; }
    public string ServiceProviderName { get; set; }
    public string ServiceEmail { get; set; }
    public string ServicePhone { get; set; }
    public string ServiceWorkTime { get; set; }
    public string ServiceQQ { get; set; }
    public string ServiceWeChat { get; set; }
    public string Weibo { get; set; }

使用 json.net 解析

使用 json.net 我们可以借助SelectToken获取片段或者节点的值,SelectToken支持三种方式获取:

  1. 属性名或数组索引获取(以点分隔)
  2. 使用JSONPath
  3. 使用LINQ

这里我们使用的是第一种的方式,剩下两种大家可以去 阅读文档 进行拓展。

通过SelectToken返回的是JToken类型,如果在指定的路径未搜寻到,则会 返回null。

基于上面的知识,我们想获取片段的实体,就可以分解为以下的步骤:

  1. 将json转换为JObject
  2. 使用SelectToken获取感兴趣的信息,得到jtoken
  3. 使用jtoken的ToObject方法转成对应的实体,或者直接将jtoken转换成对应的类型

比如我想获取Contact的实体信息:

var jo = JObject.Parse(json);
var jToken = jo.SelectToken("contact");
if (jToken != null)
    var contact = jToken.ToObject<Contact>();
}

如果您不想获取某个实体信息,只对 某个属性的值 感兴趣,比如只想获取companyName的信息,那么直接将jtoken转换成对应的值即可,示例如下

var jo = JObject.Parse(json);
var companyName = (string)jo.SelectToken("contact.companyName");

当然这只是最简单的情况,在日常工作中,您可能需要获取多个属性或者片段的值,甚至需要多属性多片段的值,如果继续采取上述方法,就不得不获取每一个您所关心的值,最后再拼接在一起,显然这么做并不是一个好主意。这时候我们就需要手动实现一个 JsonConverter。

先来一段json数据

{
  "node1": {
    "node1node1": "node1node1value",
    "node1node2": [ "value1", "value2" ],
    "node1node3": {
      "node1node3node1": "node1node3node1value"
  "node2": true,
  "node3": {
    "node3node1": "node3SubNode1Value",
    "node3node2": {
      "node3node2node1": {
        "node3node2node1node1": [ 1, 2, 3 ]
      "node3node2node2": "node3node2node1value"
}

对于上面的json数据,假如我比较关心node1node2,node2,以及node3node2node1node1节点的信息。下面我们来创建一个类用来反序列化这些信息。

[JsonConverter(typeof(JsonPathConverter))]
public class JsonModel
    [JsonProperty("node1.node1node2")]
    public IList<string> Node1Array { get; set; }
    [JsonProperty("node2")]
    public bool Node2 { get; set; }
    [JsonProperty("node3.node3node2.node3node2node1.node3node2node1node1")]
    public IList<int> Node3Array { get; set; }

这些JsonPropert中的参数就是这个属性对应的json中的路径,而这个JsonPathConverter是我们接下来要实现的内容,示例代码如下

public class JsonPathConverter : JsonConverter
	public override bool CanWrite => false;
	public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
		var jObject = JObject.Load(reader);
		var targetObj = Activator.CreateInstance(objectType);
		foreach (var prop in objectType.GetProperties().Where(p => p.CanRead && p.CanWrite))
			var jsonPropertyAttr = prop.GetCustomAttributes(true).OfType<JsonPropertyAttribute>().FirstOrDefault();
			if (jsonPropertyAttr == null)
				throw new JsonReaderException($"{nameof(JsonPropertyAttribute)} is mandatory when using {nameof(JsonPathConverter)}");
			var jsonPath = jsonPropertyAttr.PropertyName;
			var token = jObject.SelectToken(jsonPath);
			if (token != null && token.Type != JTokenType.Null)
				var jsonConverterAttr = prop.GetCustomAttributes(true).OfType<JsonConverterAttribute>().FirstOrDefault();
				object value;
				if (jsonConverterAttr == null)
					serializer.Converters.Clear();
					value = token.ToObject(prop.PropertyType, serializer);
					value = JsonConvert.DeserializeObject(token.ToString(), prop.PropertyType,
						(JsonConverter)Activator.CreateInstance(jsonConverterAttr.ConverterType));
				prop.SetValue(targetObj, value, null);
		return targetObj;
	public override bool CanConvert(Type objectType)
		return true;
	public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
		throw new NotImplementedException();
}

当这些都定义好后,反序列化就跟平时的操作一样了

var result = JsonConvert.DeserializeObject<JsonModel>(json);

怎么样是不是非常的简单。这两种方法都比较常用,如何取舍还应看获取内容多少,获取少量数据的话,就不需要搞得那么复杂。

使用System.Text.Json解析

好多小伙伴可能早就知道,在.NET Core 3.0中微软加入了对JSON的内置支持。如果不愿意引用第三方的json库,可以试试使用微软原生的解决方案。原生的json api拥有更好的性能,但是功能远不如 json.net 丰富。下面我们尝试使用原生的json api来解析contact片段信息。

大致的步骤如下:

  1. 将json转换为JsonDocument
  2. 通过RootElement获取到document的根元素
  3. 通过GetProperty方法,一级一级的找到您关心的数据的位置

示例代码如下

using var document = JsonDocument.Parse(json);
var contactEle = document.RootElement.GetProperty("contact");

到这里我们就获取到contact的JsonElement,值得主意的是,和 json.net 不同的是,如果找不到该属性,则会抛出KeyNotFoundException。当然微软也提供了Try***方法进行读取。比如

if(document.RootElement.TryGetProperty("contact", out var paraPosition))
  //do something

另外值得注意的是,相比于 json.net ,原生的api并没有提供ToObject扩展方法。但是在.NET 6官方提供了Deserialize方法,用法如下

var myClass = jDoc.RootElement.GetProperty("contact").Deserialize<Contact>();

如果是.NET6之前的版本,可以自己定义一个ToObject方法,也很方便

  public static T ToObject<T>(this JsonElement element, JsonSerializerOptions options = null)