語言

如何處理溢位 JSON 或使用 JsonElement 或 JsonNode

本文說明如何使用 System.Text.Json 命名空間來處理溢位 JSON。 它也會示範如何還原序列化成 JsonElement 或 JsonNode,作為其他情境下的替代做法;在這些情境中,目標型別可能無法與要還原序列化的 JSON 內容完全相符。

處理溢出 JSON

反序列化時,您可能會在 JSON 中收到未由目標型別屬性表示的資料。 例如,假設您的目標類型如下:

public class WeatherForecast
{
    public DateTimeOffset Date { get; set; }
    public int TemperatureCelsius { get; set; }
    public string? Summary { get; set; }
}
Public Class WeatherForecast
    Public Property [Date] As DateTimeOffset
    Public Property TemperatureCelsius As Integer
    Public Property Summary As String
End Class

而要還原序列化的 JSON 如下:

{
  "Date": "2019-08-01T00:00:00-07:00",
  "temperatureCelsius": 25,
  "Summary": "Hot",
  "DatesAvailable": [
    "2019-08-01T00:00:00-07:00",
    "2019-08-02T00:00:00-07:00"
  ],
  "SummaryWords": [
    "Cool",
    "Windy",
    "Humid"
  ]
}

如果您將顯示的 JSON 還原序列化為所顯示類型,則 DatesAvailable 和 SummaryWords 屬性會無處可去,且會遺失。 若要擷取像這些屬性這樣的額外資料,請將 [JsonExtensionData] 屬性套用到屬性或欄位。 請使用下列其中一種受支援的宣告:

對於可變的字典擴充資料,請使用任何可指派給其中一個受支援的 IDictionary 介面的類型,例如 Dictionary<string, JsonElement>。 唯讀宣告必須使用 IReadOnlyDictionary 上述精確的介面類型之一。

public class WeatherForecastWithExtensionData
{
    public DateTimeOffset Date { get; set; }
    public int TemperatureCelsius { get; set; }
    public string? Summary { get; set; }
    [JsonExtensionData]
    public Dictionary<string, JsonElement>? ExtensionData { get; set; }
}
Public Class WeatherForecastWithExtensionData
    Public Property [Date] As DateTimeOffset
    Public Property TemperatureCelsius As Integer
    Public Property Summary As String

    <JsonExtensionData>
    Public Property ExtensionData As Dictionary(Of String, Object)

End Class

對於 IReadOnlyDictionary 擴充資料成員,.NET 11 會將其具現化為 Dictionary<string, object> 或 Dictionary<string, JsonElement>。 該成員必須是可寫的,因為序列化器會以現有值初始化新的字典,然後再將其指派回該成員。 如果輸入的 JSON 屬性重複了現有的鍵,則該輸入值會勝出。

下表顯示將稍早所顯示 JSON 還原序列化為此範例類型的結果。 額外資料會成為 ExtensionData 屬性的鍵值配對:

屬性 值 備註
Date "8/1/2019 12:00:00 AM -07:00"
TemperatureCelsius 0 因為大小寫不一致(JSON 中的 temperatureCelsius),所以不會設定該屬性。
Summary "Hot"
ExtensionData "temperatureCelsius": 25,
"DatesAvailable": ["2019-08-01T00:00:00-07:00","2019-08-02T00:00:00-07:00"],
"SummaryWords": ["Cool","Windy","Humid"]
由於大小寫不一致,temperatureCelsius 會被視為多餘項,並成為字典中的鍵值組。
JSON 中的每個額外陣列都會變成機碼值組,並將陣列當作值物件。

將目標物件序列化時,擴充資料索引鍵/值組會成為 JSON 屬性,如同它們在傳入的 JSON 中一樣:

{
  "Date": "2019-08-01T00:00:00-07:00",
  "TemperatureCelsius": 0,
  "Summary": "Hot",
  "temperatureCelsius": 25,
  "DatesAvailable": [
    "2019-08-01T00:00:00-07:00",
    "2019-08-02T00:00:00-07:00"
  ],
  "SummaryWords": [
    "Cool",
    "Windy",
    "Humid"
  ]
}

請注意,ExtensionData 屬性名稱不會出現在 JSON 中。 此行為可讓 JSON 在往返傳遞過程中,不會遺失任何原本無法反序列化的額外資料。

從 .NET 11 開始,序列化會將 JsonObject 擴充資料攤平至其所屬的 JSON 物件中。 JsonObject 屬性會直接出現在其所在的物件中,而不是出現在 extension-data 成員名稱之下。

下列範例顯示從 JSON 到還原序列化物件,並返回 JSON 的來回行程:

using System.Text.Json;
using System.Text.Json.Serialization;

namespace RoundtripExtensionData
{
    public class WeatherForecast
    {
        public DateTimeOffset Date { get; set; }
        public int TemperatureCelsius { get; set; }
        public string? Summary { get; set; }
        [JsonExtensionData]
        public Dictionary<string, JsonElement>? ExtensionData { get; set; }
    }

    public class Program
    {
        public static void Main()
        {
            string jsonString =
@"{
  ""Date"": ""2019-08-01T00:00:00-07:00"",
  ""temperatureCelsius"": 25,
  ""Summary"": ""Hot"",
  ""SummaryField"": ""Hot"",
  ""DatesAvailable"": [
    ""2019-08-01T00:00:00-07:00"",
    ""2019-08-02T00:00:00-07:00""
  ],
  ""SummaryWords"": [
    ""Cool"",
    ""Windy"",
    ""Humid""
  ]
}";
            WeatherForecast weatherForecast = 
                JsonSerializer.Deserialize<WeatherForecast>(jsonString)!;

            var serializeOptions = new JsonSerializerOptions { WriteIndented = true };
            jsonString = JsonSerializer.Serialize(weatherForecast, serializeOptions);
            Console.WriteLine($"JSON output:\n{jsonString}\n");
        }
    }
}
// output:
//JSON output:
//{
//  "Date": "2019-08-01T00:00:00-07:00",
//  "TemperatureCelsius": 0,
//  "Summary": "Hot",
//  "temperatureCelsius": 25,
//  "SummaryField": "Hot",
//  "DatesAvailable": [
//    "2019-08-01T00:00:00-07:00",
//    "2019-08-02T00:00:00-07:00"
//  ],
//  "SummaryWords": [
//    "Cool",
//    "Windy",
//    "Humid"
//  ]
//}

反序列化為 JsonElement 或 JsonNode

如果您只想要靈活地決定特定屬性可接受的 JSON,替代方式是還原序列化為 JsonElement 或 JsonNode。 任何有效的 JSON 屬性都可以還原序列化為 JsonElement 或 JsonNode。 選擇 JsonElement 建立 不可變 的物件,或 JsonNode 建立 可變 物件。

下列範例示範針對包含 JsonElement 和 JsonNode 類型屬性的類別,如何從 JSON 轉換後再轉回 JSON。

using System.Text.Json;
using System.Text.Json.Nodes;

namespace RoundtripJsonElementAndNode
{
    public class WeatherForecast
    {
        public DateTimeOffset Date { get; set; }
        public int TemperatureCelsius { get; set; }
        public string? Summary { get; set; }
        public JsonElement DatesAvailable { get; set; }
        public JsonNode? SummaryWords { get; set; }
    }
        public class Program
    {
        public static void Main()
        {
            string jsonString =
@"{
  ""Date"": ""2019-08-01T00:00:00-07:00"",
  ""TemperatureCelsius"": 25,
  ""Summary"": ""Hot"",
  ""DatesAvailable"": [
    ""2019-08-01T00:00:00-07:00"",
    ""2019-08-02T00:00:00-07:00""
  ],
  ""SummaryWords"": [
    ""Cool"",
    ""Windy"",
    ""Humid""
  ]
}";
            WeatherForecast? weatherForecast = 
                JsonSerializer.Deserialize<WeatherForecast>(jsonString);

            var serializeOptions = new JsonSerializerOptions { WriteIndented = true };
            jsonString = JsonSerializer.Serialize(weatherForecast, serializeOptions);
            Console.WriteLine(jsonString);
        }
    }
}
// output:
//{
//  "Date": "2019-08-01T00:00:00-07:00",
//  "TemperatureCelsius": 25,
//  "Summary": "Hot",
//  "DatesAvailable": [
//    "2019-08-01T00:00:00-07:00",
//    "2019-08-02T00:00:00-07:00"
//  ],
//  "SummaryWords": [
//    "Cool",
//    "Windy",
//    "Humid"
//  ]
//}

另請參閱