.NET Magazine國際中文電子雜誌
作 者:許薰尹
審 稿:張智凱
文章編號: N200221602
出刊日期: 2020/2/19
本篇文章延續《使用System.Text.Json入門 - 1》一文的內容,介紹如何在.NET Core 3以上的專案之中,使用「System.Text.Json」套件中提供的類別進行序列化與還原序列化的動作。
「JsonSerializerOptions」物件提供許多屬性可以搭配「JsonSerializer」類別來控制序列化的細節,以下繼續介紹一些此類別常用的屬性。
尾端逗號
預設JSON格式的資料不允許在尾端出現逗號,例如底下格式的資料是有問題的:
{
"EmployeeId": 1,
"EmployeeName": "Mary",
"BirthDate": "2000-01-02T00:00:00",
"IsMarried": false,
"Interests": [
"Swimming",
"Hiking",
"Running"
],
"WorkExperience": {
"CompanyName": "UUU",
"Years": 3
},
}
試圖去還原序列化這個JSON文件,便會遭遇到例外錯誤,錯誤訊息如下:
Unhandled exception. System.Text.Json.JsonException: The JSON object contains a trailing comma at the end which is not supported in this mode. Change the reader options.
這個範例執行結果請參考下圖:

圖 1:序列化與還原序列化測試結果。
若要允許尾端逗號,可以將「JsonSerializerOptions」物件的「AllowTrailingCommas」屬性設定為「true」,參考以下範例程式碼:
using System;
using System.Collections.Generic;
using System.Text.Json;
namespace JSONApp1 {
class Program {
static void Main( string[] args ) {
var options = new JsonSerializerOptions {
AllowTrailingCommas = true
};
var str = System.IO.File.ReadAllText( "json.txt" );
Console.WriteLine(str);
Console.WriteLine( "===============================" );
Console.WriteLine( "Deserialize : " );
var obj = JsonSerializer.Deserialize<Employee>( str ,options );
Console.WriteLine( obj.EmployeeId );
Console.WriteLine( obj.EmployeeName );
Console.WriteLine( obj.BirthDate );
Console.WriteLine( obj.IsMarried );
Console.WriteLine( string.Join( ',' , obj.Interests ) );
Console.WriteLine( obj.WorkExperience.CompanyName );
Console.WriteLine( obj.WorkExperience.Years );
}
}
public class Employee {
public int EmployeeId { get; set; }
public string EmployeeName { get; set; }
public DateTime BirthDate { get; set; }
public bool IsMarried { get; set; }
public List<string> Interests { get; set; }
public WorkExperience WorkExperience { get; set; }
}
public class WorkExperience {
public string CompanyName { get; set; }
public int Years { get; set; }
}
}
這個範例執行結果請參考下圖:

圖 2:序列化與還原序列化測試結果。
使用ReadCommentHandling處理註解
JSON資料預設不可包含註解,若JSON資料包含以下註解:
{
"EmployeeId": 1, //serial no
"EmployeeName": "Mary", //user name
"BirthDate": "2000-01-02T00:00:00",
"IsMarried": false,
"Interests": [
"Swimming",
"Hiking",
"Running"
],
"WorkExperience": {
"CompanyName": "UUU",
"Years": 3
}
}
則試圖去還原序列化這個JSON文件,便會遭遇到例外錯誤,錯誤訊息如下:
Unhandled exception. System.Text.Json.JsonException: '/' is an invalid start of a property name. Expected a '"'
這個範例執行結果請參考下圖:

圖 3:序列化與還原序列化測試結果。
只要指定「JsonSerializerOptions」物件的「ReadCommentHandling」屬性值為「JsonCommentHandling.Skip」就可以解決這個問題,參考以下範例程式碼:
using System;
using System.Collections.Generic;
using System.Text.Json;
namespace JSONApp1 {
class Program {
static void Main( string[] args ) {
var options = new JsonSerializerOptions {
ReadCommentHandling = JsonCommentHandling.Skip ,
WriteIndented = true
};
var str = System.IO.File.ReadAllText( "json.txt" );
Console.WriteLine( str );
Console.WriteLine( "===============================" );
Console.WriteLine( "Deserialize : " );
var obj = JsonSerializer.Deserialize<Employee>( str , options );
Console.WriteLine( obj.EmployeeId );
Console.WriteLine( obj.EmployeeName );
Console.WriteLine( obj.BirthDate );
Console.WriteLine( obj.IsMarried );
Console.WriteLine( string.Join( ',' , obj.Interests ) );
Console.WriteLine( obj.WorkExperience.CompanyName );
Console.WriteLine( obj.WorkExperience.Years );
}
}
public class Employee {
public int EmployeeId { get; set; }
public string EmployeeName { get; set; }
public DateTime BirthDate { get; set; }
public bool IsMarried { get; set; }
public List<string> Interests { get; set; }
public WorkExperience WorkExperience { get; set; }
}
public class WorkExperience {
public string CompanyName { get; set; }
public int Years { get; set; }
}
}
這個範例執行結果請參考下圖:

圖 4:序列化與還原序列化測試結果。
使用Attribute控制序列化
「System.Text.Json.Serialization」命名空間下包含多個Attribute類別可以控制序列化個別屬性的細節。若有些屬性包含機密的資料,你可以使用內建的[JsonIgnore] attribute來避免序列化。有時為了和不同系統做交換,序列化完的格式和C# 類別的屬性名稱通常不相符,我們可以利用內建的「JsonPropertyName」attribute類別來控制之。
例如以下範例程式碼,利用「System.Text.Json.Serialization」命名空間下的「JsonPropertyName」類別,指定「EmployeeId」屬性名稱序列化完的結果要改為「_id」,「EmployeeName」屬性名稱序列化完的結果要改為「_name」;且利用「JsonIgnore」Attribute設定「WorkExperience」屬性將忽略,不序列化:
using System;
using System.Collections.Generic;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace JSONApp1 {
class Program {
static void Main( string[] args ) {
Employee emp = new Employee() {
EmployeeId = 1 ,
EmployeeName = "Mary" ,
BirthDate = new DateTime( 2000 , 1 , 2 ) ,
IsMarried = false ,
Interests = new List<string> { "Swimming" , "Hiking" , "Running" } ,
WorkExperience = new WorkExperience() {
CompanyName = "UUU" ,
Years = 3
}
};
var options = new JsonSerializerOptions {
WriteIndented = true
};
Console.WriteLine( "Serize : " );
var jsonString = JsonSerializer.Serialize( emp , options );
Console.WriteLine( jsonString );
Console.WriteLine( "===============================" );
Console.WriteLine( "Deserialize : " );
var obj = JsonSerializer.Deserialize<Employee>( jsonString , options );
Console.WriteLine( obj.EmployeeId );
Console.WriteLine( obj.EmployeeName );
Console.WriteLine( obj.BirthDate );
Console.WriteLine( obj.IsMarried );
Console.WriteLine( string.Join( ',' , obj.Interests ) );
Console.WriteLine( obj.WorkExperience?.CompanyName );
Console.WriteLine( obj.WorkExperience?.Years );
}
}
public class Employee {
[JsonPropertyName( "_id" )]
public int EmployeeId { get; set; }
[JsonPropertyName( "_name" )]
public string EmployeeName { get; set; }
public DateTime BirthDate { get; set; }
public bool IsMarried { get; set; }
public List<string> Interests { get; set; }
[JsonIgnore]
public WorkExperience WorkExperience { get; set; }
}
public class WorkExperience {
public string CompanyName { get; set; }
public int Years { get; set; }
}
}
這個範例程式的執行結果參考如下:

圖 5:序列化與還原序列化測試結果。
JsonExtensionData attribute
若遇到JSON資料的屬性多於欲還原的物件屬性,可以在類別中定義一個標註「JsonExtensionData」attribute的「Dictionary<string , object>」型別屬性,還原序列化的過程中,會將不相符於物件屬性的JSON資料放在此型別之中,以一個Key配一個Value的形式存在於「Dictionary<string , object>」型別物件中。例如JSON資料如下,包含「BirthDate」與「IsMarried」屬性:
{
"EmployeeId": 1,
"EmployeeName": "Mary",
"BirthDate": "2000-01-02T00:00:00",
"IsMarried": false,
"Interests": [
"Swimming",
"Hiking",
"Running"
],
"WorkExperience": {
"CompanyName": "UUU",
"Years": 3
}
}
而以下範例的「Employee」類別不包含「BirthDate」與「IsMarried」屬性,多了一個標註「JsonExtensionData」attribute的「ExtensionData」屬性,
using System;
using System.Collections.Generic;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace JSONApp1 {
class Program {
static void Main( string[] args ) {
var options = new JsonSerializerOptions {
WriteIndented = true
};
var str = System.IO.File.ReadAllText( "json.txt" );
Console.WriteLine(str);
Console.WriteLine( "===============================" );
Console.WriteLine( "Deserialize : " );
var obj = JsonSerializer.Deserialize<Employee>( str ,options );
Console.WriteLine( obj.EmployeeId );
Console.WriteLine( obj.EmployeeName );
Console.WriteLine( string.Join( ',' , obj.Interests ) );
Console.WriteLine( obj.WorkExperience.CompanyName );
Console.WriteLine( obj.WorkExperience.Years );
Console.WriteLine("ExtensionData : ");
foreach ( var (key, value) in obj.ExtensionData ) {
Console.WriteLine( $"{key} : {value}" );
}
}
}
public class Employee {
public int EmployeeId { get; set; }
public string EmployeeName { get; set; }
public List<string> Interests { get; set; }
public WorkExperience WorkExperience { get; set; }
[JsonExtensionData]
public Dictionary<string , object> ExtensionData { get; set; }
}
public class WorkExperience {
public string CompanyName { get; set; }
public int Years { get; set; }
}
}
還原序列化之後,可以利用一個「foreach」迴圈,將「ExtensionData」屬性中的項目一一讀取出來。這個範例執行結果請參考下圖:

圖 6:序列化與還原序列化測試結果。
使用JsonDocument進行隨機存取
「JsonDocument」類別可以將JSON字串讀入,直接在記憶體中轉換成一個document object model (DOM)物件,以便隨機存取它的屬性值。
例如先前使用「JsonSerializer」類別將物件序列化成json.txt檔案,檔案內容如下:
{
"EmployeeId": 1,
"EmployeeName": "Mary",
"BirthDate": "2000-01-02T00:00:00",
"IsMarried": false,
"Interests": [
"Swimming",
"Hiking",
"Running"
],
"WorkExperience": {
"CompanyName": "UUU",
"Years": 3
}
}
我們改用「JsonDocument」類別來讀取它的內容,使用程式如下:
using System;
using System.Collections.Generic;
using System.Text.Json;
namespace JSONApp1 {
class Program {
static void Main( string[] args ) {
var json = System.IO.File.ReadAllText( "json.txt" );
Console.WriteLine(json);
Console.WriteLine( "Deserialize : " );
var doc = JsonDocument.Parse( json );
Console.WriteLine( doc.RootElement );
Console.WriteLine( "===============================" );
var employeeId = doc.RootElement.GetProperty( "EmployeeId" ).GetInt32();
Console.WriteLine( " EmployeeId : " + employeeId );
var employeeName = doc.RootElement.GetProperty( "EmployeeName" ).GetString();
Console.WriteLine( " EmployeeName : " + employeeName );
var birthDate = doc.RootElement.GetProperty( "BirthDate" ).GetDateTime();
Console.WriteLine( " BirthDate : " + birthDate );
var isMarried = doc.RootElement.GetProperty( "IsMarried" ).GetBoolean();
Console.WriteLine( " IsMarried : " + isMarried );
var interests = doc.RootElement.GetProperty( "Interests" );
Console.WriteLine( " Interests : " + interests );
for ( int i = 0 ; i < interests.GetArrayLength() ; i++ ) {
Console.WriteLine( interests[i].GetString() );
}
var companyName = doc.RootElement.GetProperty( "WorkExperience" ).GetProperty( "CompanyName" );
Console.WriteLine( " CompanyName : " + companyName );
var years = doc.RootElement.GetProperty( "WorkExperience" ).GetProperty( "Years" );
Console.WriteLine( " Years : " + years );
Console.WriteLine();
}
}
}
JsonDocument」類別的「Parse」方法可以剖析JSON字串,將它們存放在「RootElement」之中,接著透過「JsonElement」類別的「GetProperty」方法讀取屬性值,「JsonElement」類別提供多個Get開頭的方法,可以將屬性值讀取之後,自動轉型成適當型別。這個範例的執行結果,請參考下圖所示:

圖 7:序列化與還原序列化測試結果。
JsonDocument整合JsonSerializer還原序列化
「JsonDocument」物件的內容若要轉換成物件,可以叫用「JsonElement」類別的「GetRawText」方法取得JSON字串,再搭配「JsonSerializer」類別的「Deserialize」處理之,參考以下範例程式碼:
using System;
using System.Collections.Generic;
using System.Text.Json;
namespace JSONApp1 {
class Program {
static void Main( string[] args ) {
var json = System.IO.File.ReadAllText( "json.txt" );
Console.WriteLine( json );
Console.WriteLine( "Deserialize : " );
var doc = JsonDocument.Parse( json );
Console.WriteLine( doc.RootElement );
var obj = JsonSerializer.Deserialize<Employee>( doc.RootElement.GetRawText() );
Console.WriteLine( obj.EmployeeId );
Console.WriteLine( obj.EmployeeName );
Console.WriteLine( obj.BirthDate );
Console.WriteLine( obj.IsMarried );
Console.WriteLine( string.Join( ',' , obj.Interests ) );
Console.WriteLine( obj.WorkExperience.CompanyName );
Console.WriteLine( obj.WorkExperience.Years );
}
}
public class Employee {
public int EmployeeId { get; set; }
public string EmployeeName { get; set; }
public DateTime BirthDate { get; set; }
public bool IsMarried { get; set; }
public List<string> Interests { get; set; }
public WorkExperience WorkExperience { get; set; }
}
public class WorkExperience {
public string CompanyName { get; set; }
public int Years { get; set; }
}
}
我們也可以還原序列化個別的屬性,例如上述範例中的「Interests」字串陣列,或「WorkExperience」物件,參考以下範例程式碼:
using System;
using System.Collections.Generic;
using System.Text.Json;
namespace JSONApp1 {
class Program {
static void Main( string[] args ) {
var json = System.IO.File.ReadAllText( "json.txt" );
Console.WriteLine( json );
Console.WriteLine( "===============================" );
Console.WriteLine( "Deserialize : " );
var doc = JsonDocument.Parse( json );
Console.WriteLine( doc.RootElement );
var interests = JsonSerializer.Deserialize<string[]>( doc.RootElement.GetProperty( "Interests" ).GetRawText() );
Console.WriteLine( string.Join( ',' , interests ) );
var workExperience = JsonSerializer.Deserialize<WorkExperience>( doc.RootElement.GetProperty( "WorkExperience" ).GetRawText() );
Console.WriteLine( workExperience.CompanyName );
Console.WriteLine( workExperience.Years );
}
}
public class Employee {
public int EmployeeId { get; set; }
public string EmployeeName { get; set; }
public DateTime BirthDate { get; set; }
public bool IsMarried { get; set; }
public List<string> Interests { get; set; }
public WorkExperience WorkExperience { get; set; }
}
public class WorkExperience {
public string CompanyName { get; set; }
public int Years { get; set; }
}
}
這個範例執行結果請參考下圖:

圖 8:序列化與還原序列化測試結果。
序列化UTF-8格式
「JsonSerializer」類別提供了「SerializeToUtf8Bytes」方法可將資料序列化成UTF-8位元組陣列 (byte[]),根據官網文件的說明,其效能優於序列化成UTF-16格式的字串約5-10%,參考範例程式碼如下:
using System;
using System.Collections.Generic;
using System.Text.Json;
namespace JSONApp1 {
class Program {
static void Main( string[] args ) {
Employee emp = new Employee() {
EmployeeId = 1 ,
EmployeeName = "瑪麗" ,
BirthDate = new DateTime( 2000 , 1 , 2 ) ,
IsMarried = false ,
Interests = new List<string> { "游泳" , "爬山" , "跑步" } ,
WorkExperience = new WorkExperience() {
CompanyName = "UUU" ,
Years = 3
}
};
var options = new JsonSerializerOptions {
WriteIndented = true
};
Console.WriteLine( "Serize : " );
var bytes = JsonSerializer.SerializeToUtf8Bytes( emp , options );
Console.WriteLine( bytes );
Console.WriteLine( "===============================" );
Console.WriteLine( "Deserialize : " );
var obj = JsonSerializer.Deserialize<Employee>( bytes );
Console.WriteLine( obj.EmployeeId );
Console.WriteLine( obj.EmployeeName );
Console.WriteLine( obj.BirthDate );
Console.WriteLine( obj.IsMarried );
Console.WriteLine( string.Join( ',' , obj.Interests ) );
Console.WriteLine( obj.WorkExperience.CompanyName );
Console.WriteLine( obj.WorkExperience.Years );
}
}
public class Employee {
public int EmployeeId { get; set; }
public string EmployeeName { get; set; }
public DateTime BirthDate { get; set; }
public bool IsMarried { get; set; }
public List<string> Interests { get; set; }
public WorkExperience WorkExperience { get; set; }
}
public class WorkExperience {
public string CompanyName { get; set; }
public int Years { get; set; }
}
}
這個範例執行結果請參考下圖:

圖 9:序列化與還原序列化測試結果。