Обработка переполнения JSON или использование JsonElement или JsonNode

В этой статье показано, как обрабатывать переполнение JSON с System.Text.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] к свойству типа Dictionary<string,object> или Dictionary<string,JsonElement>:

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

В следующей таблице показан результат десериализации JSON, показанного ранее в этом примере. Дополнительные данные становятся парами ExtensionData "ключ-значение" свойства:

Свойство Значение Примечания.
Date "8/1/2019 12:00:00 AM -07:00"
TemperatureCelsius 0 Несовпадение с учетом регистра (temperatureCelsius в коде JSON), поэтому свойство не задано.
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 выполнить круговой путь без потери дополнительных данных, которые в противном случае не будут десериализованы.

В следующем примере показано круговое путешествие из 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 создать изменяемый объект.

В следующем примере показана круговая поездка из JSON и обратно в JSON для класса, включающего свойства типа JsonElement и JsonNode.

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"
//  ]
//}

См. также