從本篇開始寫 .net5 core webapi 進階系列,先從JSON這種數據格式開始,原因如下:
1 . 夠簡潔;
2 . 易於理解;
3 . 其格式和面向對象的語言天然匹配;
4 . 多語言(Javascript 、C# 、Java 等)支持;
毫無疑問,JSON是不同語言,不同系統之間進行數據交換的最好選擇,沒有之一,
如果XML代表着上一個10年,那么JSON將統治下一個10年,也許更久。。。
一 、System.Text.Json 的內部結構。
System.Text.Json.dll 程序集中有兩個名稱空間,分別是 System.Text.Json 和 System.Text.Json.Serialization,
用反編譯工具打開System.Text.Json並只顯示公有類型和成員,得到如下的類/結構:

System.Text.Json用於讀寫JSON,
System.Text.Json.Serialization用於JSON的序列化和反序列化。
二 、新建一個.net5 core webapi 項目演示System.Text.Json的用法。
項目名稱為webapidemo2,按照慣例,
刪除根目錄下的 WeatherForecast.cs 文件和Controllers目錄下的 WeatherForecastController.cs 文件,
新建一個API控制器(不是MVC控制器)文件 JsonDemoController.cs,如下:


在此文件中引用如下幾個名稱空間:
using System.Buffers;
using System.IO;
using System.Text.Json;
using System.Text.Json.Serialization;
using static System.Text.Json.JsonElement;
在項目根目錄下新建一個Models的文件夾,
然后添加一個 Company.cs 和 User.cs 的類文件作為序列化和反序列化使用 ,如下:

Company.cs 和 User.cs 的代碼先不寫,在演示序列化和反序列化的時候再補充完成。
我們在 JsonDemoController.cs 這個控制器中編寫7個終結點,分別完成如下7個功能:
1 . 終結點: JsonDemo1();作用:return 一個最簡單的 JSON 字符串;
{"userid":123, "username":"jacky"}
2 . 終結點: JsonDemo2();作用:return 一個帶字符串數組的 JSON 字符串;
{"userid":123, "username":"jacky", "mobile":["13244445555","18966667777"]}
3 . 終結點: JsonDemo3();作用:return 一個帶對象數組的 JSON 字符串;
{"userid":123, "username":"jacky", "employees":[{"userid":834,"username":"simon"},{"userid":835,"username":"roger"}]}
4 . 終結點: JsonDemo4();作用:用 JsonDocument 類解析一個JSON文件,讀取指定屬性的值;
文件位於根目錄下,名稱叫 appcom.json,格式如下:
{ "CompanyName": "top", "Website": "www.xxxx.com", "Address": "開曼群島", "Boss": {"userid": 1001, "username": "王五", "mobile": "13300047775"}, "Employees": [ { "userid": 2113, "username": "張三", "mobile": "13378465709" }, { "userid": 3266, "username": "李四", "mobile": "13378465709" } ], "PhoneNumber": ["010-93847485","15890276458"] }
5 . 終結點:JsonDemo5();作用:用 Utf8JsonReader 遍歷 JSON 文件(文件同 4.)
6 . 終結點:JsonDemo6();作用:將 Company 和 User 實例序列化成 JSON 對象;
7 . 終結點:JsonDemo7();作用:將 JSON 對象反序列化成 Company 和 User 實例;
三 、編碼,7個終結點的代碼如下。
1 . 終結點: JsonDemo1();
[Route("json1")] [HttpGet] public string Demo1() { //{"userid":123, "username":"jacky"} //表示基於堆、使用字節數組存儲數據的輸出接收器,可向其中寫入數據。 ArrayBufferWriter<byte> buffer = new ArrayBufferWriter<byte>(); //utf8編碼的JSON寫入器,可以將int,string,array等數據寫入到JSON中 Utf8JsonWriter writer = new Utf8JsonWriter(buffer); writer.WriteStartObject(); //表示一個開頭花括號 "{" //寫入屬性及其值,這兩行語句可以換成 writer.WriteNumber("userid", 123); writer.WritePropertyName("userid"); writer.WriteNumberValue(123); writer.WriteString("username", "jacky"); writer.WriteEndObject(); //表示一個結尾花括號 "}" writer.Flush();//寫入到buffer //ReadOnlySpan表示的存儲區有更好的性能和內存開銷 ReadOnlySpan<byte> span = buffer.WrittenSpan; string strJson = Encoding.UTF8.GetString(span); return strJson; }
打開POSTMAN ,用GET方式訪問網址:http://localhost:61946/api/jsondemo/json1 得到如下結果:

2 . 終結點: JsonDemo2();
[Route("json2")] [HttpGet] public string Demo2() { //{"userid":123, "username":"jacky", "mobile":["13244445555","18966667777"]} ArrayBufferWriter<byte> buffer = new ArrayBufferWriter<byte>(); Utf8JsonWriter writer = new Utf8JsonWriter(buffer); writer.WriteStartObject(); writer.WriteNumber("userid", 123); writer.WriteString("username", "jacky"); //寫入json數組開始,這兩行語句可以換成 writer.WriteStartArray("mobile"); writer.WritePropertyName("mobile"); writer.WriteStartArray();//寫入json數組開始,表示一個開頭中括號[ writer.WriteStringValue("13244445555");//簡單的值對象輸出用對應的Value結尾的方法 writer.WriteStringValue("18966667777"); writer.WriteEndArray();//寫入json數組結束,表示一個結束中括號] writer.WriteEndObject(); writer.Flush(); ReadOnlySpan<byte> span = buffer.WrittenSpan; string strJson = Encoding.UTF8.GetString(span); return strJson; }
打開POSTMAN ,用GET方式訪問網址:http://localhost:61946/api/jsondemo/json2 得到如下結果:

3 . 終結點: JsonDemo3();
[Route("json3")] [HttpGet] public string Demo3() { //{"userid":123, "username":"jacky", "employees":[{"userid":834,"username":"simon"},{"userid":835,"username":"roger"}]} //mock一個泛型List<User>供下面使用 List<User> userList = new List<User>(); userList.Add(new User { UserId = 834, UserName = "simon" }); userList.Add(new User { UserId = 835, UserName = "roger" }); ArrayBufferWriter<byte> buffer = new ArrayBufferWriter<byte>(); Utf8JsonWriter writer = new Utf8JsonWriter(buffer); writer.WriteStartObject(); writer.WriteNumber("userid", 123); writer.WriteString("username", "jacky"); writer.WriteStartArray("employees"); foreach (User user in userList) //遍歷寫入User對象 { writer.WriteStartObject(); writer.WriteNumber("userid", user.UserId); writer.WriteString("username", user.UserName); writer.WriteEndObject(); } writer.WriteEndArray(); writer.WriteEndObject(); writer.Flush(); ReadOnlySpan<byte> span = buffer.WrittenSpan; string strJson = Encoding.UTF8.GetString(span); return strJson; }
打開POSTMAN ,用GET方式訪問網址:http://localhost:61946/api/jsondemo/json3 得到如下結果:

4 . 終結點: JsonDemo4();
[Route("json4")] [HttpGet] public string Demo4() { StringBuilder sb = new StringBuilder(); string filePath = Path.GetFullPath("./appcom.json"); using (FileStream stream = System.IO.File.OpenRead(filePath)) { JsonDocument doc = JsonDocument.Parse(stream); JsonElement root = doc.RootElement; //得到單個屬性的值 string addr = root.GetProperty("Address").GetString(); sb.Append("公司地址 : " + addr + Environment.NewLine); //屬性的值是數組時取值 ArrayEnumerator arr = root.GetProperty("PhoneNumber").EnumerateArray(); sb.Append("聯系電話 : "); foreach (JsonElement ele in arr) { sb.Append(ele.GetString() + ","); } sb.Append(Environment.NewLine); //屬性的值是對象時取值 sb.Append("老板名字 : "); ObjectEnumerator arr2 = root.GetProperty("Boss").EnumerateObject(); while (arr2.MoveNext()) { if (arr2.Current.Name == "username") { sb.Append(arr2.Current.Value.GetString() + ","); } } sb.Append(Environment.NewLine); //屬性的值是對象數組時取值 sb.Append("員工名單 : "); ArrayEnumerator arr3 = root.GetProperty("Employees").EnumerateArray(); foreach (JsonElement ele in arr3) { sb.Append(ele.GetProperty("username").GetString() + ","); } } return sb.ToString(); }
打開POSTMAN ,用GET方式訪問網址:http://localhost:61946/api/jsondemo/json4 得到如下結果:

5 . 終結點: JsonDemo5();
[Route("json5")] [HttpGet] public string Demo5() { string filePath = Path.GetFullPath("./appcom.json"); ClearFileBom(filePath);//去除文件中的BOM頭,避免解析錯誤 byte[] contents = System.IO.File.ReadAllBytes(filePath); JsonReaderOptions options = new JsonReaderOptions { AllowTrailingCommas = true, //忽略屬性末尾多余的逗號 CommentHandling = JsonCommentHandling.Skip //跳過注釋 }; //變量span也可用構造方法new ReadOnlySpan<byte>(contents);初始化 ReadOnlySpan<byte> span = contents.AsSpan<byte>(); Utf8JsonReader reader = new Utf8JsonReader(span, options); StringBuilder sb = new StringBuilder(); while (reader.Read()) //遍歷所有屬性 { sb.Append("depth:" + reader.CurrentDepth + "---"); //當前屬性深度 sb.Append("JsonTokenType:" + reader.TokenType.ToString()); //屬性token類型 sb.Append("------【" + Encoding.UTF8.GetString(reader.ValueSpan) + "】"); //屬性名稱 switch (reader.TokenType) { case JsonTokenType.String: sb.Append(" --- value is=" + reader.GetString()); //不同的屬性值類型用不同的GetXxx()取值 break; case JsonTokenType.Number: sb.Append(" --- value is=" + reader.GetInt32()); break; } sb.Append(Environment.NewLine); } return sb.ToString(); } private void ClearFileBom(string filePath) { //以UTF-8編碼讀取帶BOM頭文件的內容 Encoding end = new UTF8Encoding(true); string str = string.Empty; using (StreamReader reader = new StreamReader(filePath, end)) { str = reader.ReadToEnd(); } //以UTF-8編碼重新寫入不帶BOM頭文件的內容 end = new UTF8Encoding(false); using (StreamWriter writer = new StreamWriter(filePath, false, end)) { writer.Write(str); } }
打開POSTMAN ,用GET方式訪問網址:http://localhost:61946/api/jsondemo/json5 得到如下結果:

可以看到按順序輸出了整個JSON信息。
