使用System.Text.Json入門 - 2

by vivid 19. 二月 2020 01:18

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

這個範例執行結果請參考下圖:

clip_image002

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

 

這個範例執行結果請參考下圖:

clip_image004

圖 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 '"'

這個範例執行結果請參考下圖:

clip_image006

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

 

這個範例執行結果請參考下圖:

clip_image008

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

}

這個範例程式的執行結果參考如下:

clip_image010

圖 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」屬性中的項目一一讀取出來。這個範例執行結果請參考下圖:

clip_image012

圖 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開頭的方法,可以將屬性值讀取之後,自動轉型成適當型別。這個範例的執行結果,請參考下圖所示:

clip_image014

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

 

這個範例執行結果請參考下圖:

clip_image016

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

這個範例執行結果請參考下圖:

clip_image018

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

目前評分 4.5 , 共有 2 人參與

  • Currently 4.5/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags:

.NET Core | .NET Magazine國際中文電子雜誌 | C# | 許薰尹Vivid Hsu

NET Magazine國際中文電子雜誌

NET Magazine國際中文電子版雜誌,由恆逸資訊創立於2000,自發刊日起迄今已發行超過500篇.NET相關技術文章,擁有超過40000名註冊讀者群。NET Magazine國際中文電子版雜誌希望藉於電子雜誌與NET Developer達到共同學習與技術新知分享,歡迎每一位對.NET 技術有興趣的朋友們多多支持本雜誌,讓作者群們可以有持續性的動力繼續爬文。<請加入免費訂閱>

月分類Month List