System text json c как подключить
Перейти к содержимому

System text json c как подключить

  • автор:

Работа с JSON

JSON (JavaScript Object Notation) является одним из наиболее популярных форматов для хранения и передачи данных. И платформа .NET предоставляет функционал для работы с JSON.

Основная функциональность по работе с JSON сосредоточена в пространстве имен System.Text.Json . Ключевым типом является класс JsonSerializer , который и позволяет сериализовать объект в json и, наоборот, десериализовать код json в объект C#.

Для сохранения объекта в json в классе JsonSerializer определен статический метод Serialize() и его асинхронный двойник SerializeAsyc() , которые имеют ряд перегруженных версий. Некоторые из них:

  • string Serialize(Object obj, Type type, JsonSerializerOptions options) : сериализует объект obj типа type и возвращает код json в виде строки. Последний необязательный параметр options позволяет задать дополнительные опции сериализации
  • string Serialize(T obj, JsonSerializerOptions options) : типизированная версия сериализует объект obj типа T и возвращает код json в виде строки.
  • Task SerializeAsync(Stream utf8Json, Object obj, Type type, JsonSerializerOptions options) : сериализует объект obj типа type и записывает его в поток utf8Json. Последний необязательный параметр options позволяет задать дополнительные опции сериализации
  • Task SerializeAsync(Stream utf8Json, T obj, JsonSerializerOptions options) : типизированная версия сериализует объект obj типа T в поток utf8Json.

Для десериализации кода json в объект C# применяется метод Deserialize() и его асинхронный двойник DeserializeAsync() , которые имеют различные версии. Некоторые из них:

  • object? Deserialize(string json, Type type, JsonSerializerOptions options) : десериализует строку json в объект типа type и возвращает десериализованный объект. Последний необязательный параметр options позволяет задать дополнительные опции десериализации
  • T? Deserialize(string json, JsonSerializerOptions options) : десериализует строку json в объект типа T и возвращает его.
  • ValueTask DeserializeAsync(Stream utf8Json, Type type, JsonSerializerOptions options, CancellationToken token) : десериализует данные из потока utf8Json, который представляет объект JSON, в объект типа type. Последние два параметра необязательны: options позволяет задать дополнительные опции десериализации, а token устанавливает CancellationToken для отмены задачи. Возвращается десериализованный объект, обернутый в ValueTask
  • ValueTask DeserializeAsync(Stream utf8Json, JsonSerializerOptions options, CancellationToken token) : десериализует данные из потока utf8Json, который представляет объект JSON, в объект типа T. Возвращается десериализованный объект, обернутый в ValueTask

Рассмотрим применение класса на простом примере. Сериализуем и десериализуем простейший объект:

using System.Text.Json; Person tom = new Person("Tom", 37); string json = JsonSerializer.Serialize(tom); Console.WriteLine(json); Person? restoredPerson = JsonSerializer.Deserialize(json); Console.WriteLine(restoredPerson?.Name); // Tom class Person < public string Name < get;>public int Age < get; set; >public Person(string name, int age) < Name = name; Age = age; >>

Здесь вначале сериализуем с помощью метода JsonSerializer.Serialize() объект типа Person в стоку с кодом json. Затем обратно получаем из этой строки объект Person посредством метода JsonSerializer.Deserialize() .

Хотя в примере выше сериализовался/десериализовался объект класса, но подобным способом мы также можем сериализовать/десериализовать структуры.

Некоторые замечания по сериализации/десериализации

Объект, который подвергается десериализации, должен иметь либо конструктор без параметров, либо конструктор, для всех параметров которого в десериализуемом json-объекте есть значения (соответствие между параметрами конструктора и свойствами json-объекта устанавливается на основе названий, причем регистр не играет значения).

Сериализации подлежат только публичные свойства объекта (с модификатором public).

Запись и чтение файла json

Поскольку методы SerializeAsyc/DeserializeAsync могут принимать поток типа Stream, то соответственно мы можем использовать файловый поток для сохранения и последующего извлечения данных:

using System.Text.Json; // сохранение данных using (FileStream fs = new FileStream("user.json", FileMode.OpenOrCreate)) < Person tom = new Person("Tom", 37); await JsonSerializer.SerializeAsync(fs, tom); Console.WriteLine("Data has been saved to file"); > // чтение данных using (FileStream fs = new FileStream("user.json", FileMode.OpenOrCreate)) < Person? person = await JsonSerializer.DeserializeAsync(fs); Console.WriteLine($"Name: Age: "); > class Person < public string Name < get;>public int Age < get; set; >public Person(string name, int age) < Name = name; Age = age; >>

В данном случае вначале данные сохраняются в файл user.json и затем считываются из него.

Настройка сериализации с помощью JsonSerializerOptions

По умолчанию JsonSerializer сериализует объекты в минимифицированный код. С помощью дополнительного параметра типа JsonSerializerOptions можно настроить механизм сериализации/десериализации, используя свойства JsonSerializerOptions. Некоторые из его свойств:

  • AllowTrailingCommas : устанавливает, надо ли добавлять после последнего элемента в json запятую. Если равно true , запятая добавляется
  • DefaultIgnoreCondition : устанавливает, будут ли сериализоваться/десериализоваться в json свойства со значениями по умолчанию
  • IgnoreReadOnlyProperties : аналогично устанавливает, будут ли сериализоваться свойства, предназначенные только для чтения
  • WriteIndented : устанавливает, будут ли добавляться в json пробелы (условно говоря, для красоты). Если равно true устанавливаются дополнительные пробелы
using System.Text.Json; Person tom = new Person("Tom", 37); var options = new JsonSerializerOptions < WriteIndented = true >; string json = JsonSerializer.Serialize(tom, options); Console.WriteLine(json); Person? restoredPerson = JsonSerializer.Deserialize(json); Console.WriteLine(restoredPerson?.Name);

Настройка сериализации с помощью атрибутов

По умолчанию сериализации подлежат все публичные свойства. Кроме того, в выходном объекте json все названия свойств соответствуют названиям свойств объекта C#. Однако с помощью атрибутов JsonIgnore и JsonPropertyName .

Атрибут JsonIgnore позволяет исключить из сериализации определенное свойство. А JsonPropertyName позволяет замещать оригинальное название свойства. Пример использования:

using System.Text.Json; using System.Text.Json.Serialization; Person tom = new Person("Tom", 37); string json = JsonSerializer.Serialize(tom); Console.WriteLine(json); Person? person = JsonSerializer.Deserialize(json); Console.WriteLine($"Name: Age: "); class Person < [JsonPropertyName("firstname")] public string Name < get;>[JsonIgnore] public int Age < get; set; >public Person(string name, int age) < Name = name; Age = age; >>

В данном случае свойство Age будет игнорироваться, а для свойства Name будет использоваться псевдоним «firstname». Консольный вывод:

 Name: Tom Age: 0

Обратите внимание, что, поскольку свойство Age не было сериализовано, то при десериализации для него используется значение по умолчанию.

Как использовать создание источника в System.Text.Json

Создание System.Text.Json источника доступно в .NET 6 и более поздних версиях. При использовании в приложении языковая версия приложения должна быть C# 9.0 или более поздней. В этой статье показано, как использовать сериализацию с поддержкой исходного поколения в приложениях.

Сведения о различных режимах создания источника см. в разделе «Режимы создания источников».

Использование значений по умолчанию для создания источников

Чтобы использовать создание источника со всеми значениями по умолчанию (оба режима, параметры по умолчанию):

  1. Создайте частичный класс, производный от JsonSerializerContext.
  2. Укажите тип для сериализации или десериализации, применяя JsonSerializableAttribute к классу контекста.
  3. JsonSerializer Вызовите метод, который:
    • JsonTypeInfo Принимает экземпляр или
    • JsonSerializerContext Принимает экземпляр или
    • JsonSerializerOptions Принимает экземпляр и задается его JsonSerializerOptions.TypeInfoResolver свойство Default свойством типа контекста (только .NET 7 и более поздних версий).

По умолчанию оба режима создания источника используются, если не указать один. Сведения о том, как указать режим использования, см. далее в этой статье.

Ниже приведен тип, используемый в следующих примерах:

public class WeatherForecast < public DateTime Date < get; set; >public int TemperatureCelsius < get; set; >public string? Summary < get; set; >> 

Ниже приведен класс контекста, настроенный для создания источника для предыдущего WeatherForecast класса:

[JsonSourceGenerationOptions(WriteIndented = true)] [JsonSerializable(typeof(WeatherForecast))] internal partial class SourceGenerationContext : JsonSerializerContext

Типы WeatherForecast элементов не нужно явно указывать с [JsonSerializable] атрибутами. Члены, объявленные как object исключение из этого правила. Тип среды выполнения для элемента, объявленного как object необходимо указать. Например, предположим, что у вас есть следующий класс:

public class WeatherForecast < public object? Data < get; set; >public List? DataList < get; set; >> 

И вы знаете, что во время выполнения он может иметь boolean и int объекты:

WeatherForecast wf = new() < Data = true, DataList = new List < true, 1 >>; 

Затем boolean и int должны быть объявлены следующим образом [JsonSerializable] :

[JsonSerializable(typeof(WeatherForecast))] [JsonSerializable(typeof(bool))] [JsonSerializable(typeof(int))] public partial class WeatherForecastContext : JsonSerializerContext

Чтобы указать создание источника для коллекции, используйте [JsonSerializable] его с типом коллекции. Например: [JsonSerializable(typeof(List))] .

JsonSerializer методы, использующие создание источника

В следующих примерах статическое Default свойство типа контекста предоставляет экземпляр типа контекста с параметрами по умолчанию. Экземпляр контекста предоставляет WeatherForecast свойство, возвращающее JsonTypeInfo экземпляр. Для этого свойства можно указать другое имя, используя TypeInfoPropertyName свойство атрибута [JsonSerializable] .

Примеры сериализации
jsonString = JsonSerializer.Serialize( weatherForecast!, SourceGenerationContext.Default.WeatherForecast); 
jsonString = JsonSerializer.Serialize( weatherForecast, typeof(WeatherForecast), SourceGenerationContext.Default); 
sourceGenOptions = new JsonSerializerOptions < TypeInfoResolver = SourceGenerationContext.Default >; jsonString = JsonSerializer.Serialize( weatherForecast, typeof(WeatherForecast), sourceGenOptions); 
Примеры десериализации
weatherForecast = JsonSerializer.Deserialize( jsonString, SourceGenerationContext.Default.WeatherForecast); 
weatherForecast = JsonSerializer.Deserialize( jsonString, typeof(WeatherForecast), SourceGenerationContext.Default) as WeatherForecast; 
var sourceGenOptions = new JsonSerializerOptions < TypeInfoResolver = SourceGenerationContext.Default >; weatherForecast = JsonSerializer.Deserialize( jsonString, typeof(WeatherForecast), sourceGenOptions) as WeatherForecast; 

Полный пример программы

Ниже приведены приведенные выше примеры в полной программе:

using System.Text.Json; using System.Text.Json.Serialization; namespace BothModesNoOptions < public class WeatherForecast < public DateTime Date < get; set; >public int TemperatureCelsius < get; set; >public string? Summary < get; set; >> [JsonSourceGenerationOptions(WriteIndented = true)] [JsonSerializable(typeof(WeatherForecast))] internal partial class SourceGenerationContext : JsonSerializerContext < >public class Program < public static void Main() < string jsonString = """ < "Date": "2019-08-01T00:00:00", "TemperatureCelsius": 25, "Summary": "Hot" >"""; WeatherForecast? weatherForecast; weatherForecast = JsonSerializer.Deserialize( jsonString, SourceGenerationContext.Default.WeatherForecast); Console.WriteLine($"Date="); // output: //Date=8/1/2019 12:00:00 AM weatherForecast = JsonSerializer.Deserialize( jsonString, typeof(WeatherForecast), SourceGenerationContext.Default) as WeatherForecast; Console.WriteLine($"Date="); // output: //Date=8/1/2019 12:00:00 AM var sourceGenOptions = new JsonSerializerOptions < TypeInfoResolver = SourceGenerationContext.Default >; weatherForecast = JsonSerializer.Deserialize( jsonString, typeof(WeatherForecast), sourceGenOptions) as WeatherForecast; Console.WriteLine($"Date="); // output: //Date=8/1/2019 12:00:00 AM jsonString = JsonSerializer.Serialize( weatherForecast!, SourceGenerationContext.Default.WeatherForecast); Console.WriteLine(jsonString); // output: // jsonString = JsonSerializer.Serialize( weatherForecast, typeof(WeatherForecast), SourceGenerationContext.Default); Console.WriteLine(jsonString); // output: // sourceGenOptions = new JsonSerializerOptions < TypeInfoResolver = SourceGenerationContext.Default >; jsonString = JsonSerializer.Serialize( weatherForecast, typeof(WeatherForecast), sourceGenOptions); Console.WriteLine(jsonString); // output: // > > > 

Указание режима создания источника

Можно указать режим на основе метаданных или режим оптимизации сериализации для всего контекста, который может включать несколько типов. Или можно указать режим для отдельного типа. В обоих случаях спецификация режима для типа выигрывает.

  • Для всего контекста JsonSourceGenerationOptionsAttribute.GenerationMode используйте свойство.
  • Для отдельного типа используйте JsonSerializableAttribute.GenerationMode свойство.

Пример режима оптимизации сериализации (быстрый путь)

  • Для всего контекста:
[JsonSourceGenerationOptions(GenerationMode = JsonSourceGenerationMode.Serialization)] [JsonSerializable(typeof(WeatherForecast))] internal partial class SerializeOnlyContext : JsonSerializerContext
[JsonSerializable(typeof(WeatherForecast), GenerationMode = JsonSourceGenerationMode.Serialization)] internal partial class SerializeOnlyWeatherForecastOnlyContext : JsonSerializerContext
using System.Text.Json; using System.Text.Json.Serialization; namespace SerializeOnlyNoOptions < public class WeatherForecast < public DateTime Date < get; set; >public int TemperatureCelsius < get; set; >public string? Summary < get; set; >> [JsonSourceGenerationOptions(GenerationMode = JsonSourceGenerationMode.Serialization)] [JsonSerializable(typeof(WeatherForecast))] internal partial class SerializeOnlyContext : JsonSerializerContext < >[JsonSerializable(typeof(WeatherForecast), GenerationMode = JsonSourceGenerationMode.Serialization)] internal partial class SerializeOnlyWeatherForecastOnlyContext : JsonSerializerContext < >public class Program < public static void Main() < string jsonString; WeatherForecast weatherForecast = new() < Date = DateTime.Parse("2019-08-01"), TemperatureCelsius = 25, Summary = "Hot" >; // Use context that selects Serialization mode only for WeatherForecast. jsonString = JsonSerializer.Serialize(weatherForecast, SerializeOnlyWeatherForecastOnlyContext.Default.WeatherForecast); Console.WriteLine(jsonString); // output: // // Use a context that selects Serialization mode. jsonString = JsonSerializer.Serialize(weatherForecast, SerializeOnlyContext.Default.WeatherForecast); Console.WriteLine(jsonString); // output: // > > > 

Пример режима на основе метаданных

  • Для всего контекста:
[JsonSourceGenerationOptions(GenerationMode = JsonSourceGenerationMode.Metadata)] [JsonSerializable(typeof(WeatherForecast))] internal partial class MetadataOnlyContext : JsonSerializerContext
jsonString = JsonSerializer.Serialize( weatherForecast!, MetadataOnlyContext.Default.WeatherForecast); 
weatherForecast = JsonSerializer.Deserialize( jsonString, MetadataOnlyContext.Default.WeatherForecast); 
[JsonSerializable(typeof(WeatherForecast), GenerationMode = JsonSourceGenerationMode.Metadata)] internal partial class MetadataOnlyWeatherForecastOnlyContext : JsonSerializerContext
jsonString = JsonSerializer.Serialize( weatherForecast!, MetadataOnlyWeatherForecastOnlyContext.Default.WeatherForecast); 
weatherForecast = JsonSerializer.Deserialize( jsonString, MetadataOnlyWeatherForecastOnlyContext.Default.WeatherForecast); 
using System.Text.Json; using System.Text.Json.Serialization; namespace MetadataOnlyNoOptions < public class WeatherForecast < public DateTime Date < get; set; >public int TemperatureCelsius < get; set; >public string? Summary < get; set; >> [JsonSerializable(typeof(WeatherForecast), GenerationMode = JsonSourceGenerationMode.Metadata)] internal partial class MetadataOnlyWeatherForecastOnlyContext : JsonSerializerContext < >[JsonSourceGenerationOptions(GenerationMode = JsonSourceGenerationMode.Metadata)] [JsonSerializable(typeof(WeatherForecast))] internal partial class MetadataOnlyContext : JsonSerializerContext < >public class Program < public static void Main() < string jsonString = """ < "Date": "2019-08-01T00:00:00", "TemperatureCelsius": 25, "Summary": "Hot" >"""; WeatherForecast? weatherForecast; // Deserialize with context that selects metadata mode only for WeatherForecast only. weatherForecast = JsonSerializer.Deserialize( jsonString, MetadataOnlyWeatherForecastOnlyContext.Default.WeatherForecast); Console.WriteLine($"Date="); // output: //Date=8/1/2019 12:00:00 AM // Serialize with context that selects metadata mode only for WeatherForecast only. jsonString = JsonSerializer.Serialize( weatherForecast!, MetadataOnlyWeatherForecastOnlyContext.Default.WeatherForecast); Console.WriteLine(jsonString); // output: // // Deserialize with context that selects metadata mode only. weatherForecast = JsonSerializer.Deserialize( jsonString, MetadataOnlyContext.Default.WeatherForecast); Console.WriteLine($"Date="); // output: //Date=8/1/2019 12:00:00 AM // Serialize with context that selects metadata mode only. jsonString = JsonSerializer.Serialize( weatherForecast!, MetadataOnlyContext.Default.WeatherForecast); Console.WriteLine(jsonString); // output: // > > > 

Поддержка создания источников в ASP.NET Core

В приложениях Blazor используйте перегрузки HttpClientJsonExtensions.GetFromJsonAsync методов расширения, HttpClientJsonExtensions.PostAsJsonAsync которые принимают контекст создания источника или TypeInfo .

Начиная с .NET 8, можно также использовать перегрузки HttpClientJsonExtensions.GetFromJsonAsAsyncEnumerable методов расширения, которые принимают контекст создания источника или TypeInfo .

В приложениях Razor Pages, MVC, SignalR и веб-API используйте JsonSerializerOptions.TypeInfoResolver свойство для указания контекста.

[JsonSerializable(typeof(WeatherForecast[]))] internal partial class MyJsonContext : JsonSerializerContext
var serializerOptions = new JsonSerializerOptions < TypeInfoResolver = MyJsonContext.Default >; services.AddControllers().AddJsonOptions( static options => options.JsonSerializerOptions.TypeInfoResolverChain.Add(MyJsonContext.Default)); 

В приложениях Razor Pages, MVC, SignalR и веб-API используйте JsonSerializerOptions.TypeInfoResolver свойство для указания контекста.

[JsonSerializable(typeof(WeatherForecast[]))] internal partial class MyJsonContext : JsonSerializerContext
var serializerOptions = new JsonSerializerOptions < TypeInfoResolver = MyJsonContext.Default >; services.AddControllers().AddJsonOptions( static options => options.JsonSerializerOptions = serializerOptions); 

В Приложениях Razor Pages, MVC, SignalR и Веб-API используйте AddContext метод JsonSerializerOptions, как показано в следующем примере:

[JsonSerializable(typeof(WeatherForecast[]))] internal partial class MyJsonContext : JsonSerializerContext
services.AddControllers().AddJsonOptions(options => options.JsonSerializerOptions.AddContext()); 

JsonSourceGenerationMode.SerializationСериализация с быстрым путем не поддерживается для асинхронной сериализации.

В .NET 7 и более ранних версиях это ограничение также применяется к синхронным перегрузкам JsonSerializer.Serialize , которые принимают Stream. Начиная с .NET 8, несмотря на то, что потоковая сериализация требует моделей на основе метаданных, она вернется к быстрому пути, если полезные данные, как известно, достаточно малы, чтобы соответствовать заданному размеру буфера. Дополнительные сведения см. в разделе https://devblogs.microsoft.com/dotnet/performance-improvements-in-net-8/#json.

Отключение значений по умолчанию для отражения

Так как System.Text.Json использует отражение по умолчанию, вызов базового метода сериализации может нарушить собственные приложения AOT, которые не поддерживают все необходимые API отражения. Эти разрывы могут быть сложными для диагностики, так как они могут быть непредсказуемыми, и приложения часто отлаживаются с помощью среды выполнения CoreCLR, где работает отражение. Вместо этого при явной отключении сериализации на основе отражения разрывы проще диагностировать. Код, использующий сериализацию на основе отражения, приведет InvalidOperationException к возникновению описательного сообщения во время выполнения.

Чтобы отключить отражение по умолчанию в приложении, задайте JsonSerializerIsReflectionEnabledByDefault для свойства MSBuild значение false в файле проекта:

 false  
  • Поведение этого свойства согласовано независимо от среды выполнения, CoreCLR или Native AOT.
  • Если вы не указываете это свойство и параметр PublishTrimmed включен, сериализация на основе отражения автоматически отключается.

Вы можете программно проверка, отключается ли отражение с помощью JsonSerializer.IsReflectionEnabledByDefault свойства. В следующем фрагменте кода показано, как настроить сериализатор в зависимости от того, включена ли отражение:

static JsonSerializerOptions CreateDefaultOptions() < return new() < TypeInfoResolver = JsonSerializer.IsReflectionEnabledByDefault ? new DefaultJsonTypeInfoResolver() : MyContext.Default >; > 

Так как свойство рассматривается как константа времени связи, предыдущий метод не корень сопоставителя на основе отражения в приложениях, работающих в машинном AOT.

Указание параметров

В .NET 8 и более поздних версиях большинство параметров, которые можно задать с помощью JsonSerializerOptions атрибута JsonSourceGenerationOptionsAttribute , можно также задать. Преимущество настройки параметров через атрибут заключается в том, что конфигурация указана во время компиляции, которая гарантирует, что созданное MyContext.Default свойство предварительно настроено со всеми соответствующими наборами параметров.

В следующем коде показано, как задать параметры с помощью атрибута JsonSourceGenerationOptionsAttribute .

[JsonSourceGenerationOptions( WriteIndented = true, PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase, GenerationMode = JsonSourceGenerationMode.Serialization)] [JsonSerializable(typeof(WeatherForecast))] internal partial class SerializationModeOptionsContext : JsonSerializerContext

При использовании JsonSourceGenerationOptionsAttribute для указания параметров сериализации вызовите один из следующих методов сериализации:

    Метод JsonSerializer.Serialize , который принимает TypeInfo . Передайте свойство Default. класса контекста:

jsonString = JsonSerializer.Serialize( weatherForecast, SerializationModeOptionsContext.Default.WeatherForecast); 
jsonString = JsonSerializer.Serialize( weatherForecast, typeof(WeatherForecast), SerializationModeOptionsContext.Default); 

При вызове метода, который позволяет передавать собственный экземпляр Utf8JsonWriter , параметр записи Indented учитывается вместо JsonSourceGenerationOptionsAttribute.WriteIndented параметра.

При создании и использовании экземпляра контекста путем вызова конструктора, принимающего JsonSerializerOptions экземпляр, указанный экземпляр будет использоваться вместо параметров, указанных в параметре JsonSourceGenerationOptionsAttribute .

Ниже приведены приведенные выше примеры в полной программе:

using System.Text.Json; using System.Text.Json.Serialization; namespace SerializeOnlyWithOptions < public class WeatherForecast < public DateTime Date < get; set; >public int TemperatureCelsius < get; set; >public string? Summary < get; set; >> [JsonSourceGenerationOptions( WriteIndented = true, PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase, GenerationMode = JsonSourceGenerationMode.Serialization)] [JsonSerializable(typeof(WeatherForecast))] internal partial class SerializationModeOptionsContext : JsonSerializerContext < >public class Program < public static void Main() < string jsonString; WeatherForecast weatherForecast = new() < Date = DateTime.Parse("2019-08-01"), TemperatureCelsius = 25, Summary = "Hot" >; // Serialize using TypeInfo provided by the context // and options specified by [JsonSourceGenerationOptions]. jsonString = JsonSerializer.Serialize( weatherForecast, SerializationModeOptionsContext.Default.WeatherForecast); Console.WriteLine(jsonString); // output: // < // "date": "2019-08-01T00:00:00", // "temperatureCelsius": 0, // "summary": "Hot" //>// Serialize using Default context // and options specified by [JsonSourceGenerationOptions]. jsonString = JsonSerializer.Serialize( weatherForecast, typeof(WeatherForecast), SerializationModeOptionsContext.Default); Console.WriteLine(jsonString); // output: // < // "date": "2019-08-01T00:00:00", // "temperatureCelsius": 0, // "summary": "Hot" //>> > > 

Указание параметров с помощью JsonSerializerOptions

Некоторые параметры JsonSerializerOptions не могут быть заданы с помощью JsonSourceGenerationOptionsAttribute. Указание параметров с помощью JsonSerializerOptions:

  • Создайте экземпляр JsonSerializerOptions .
  • Создайте экземпляр класса, производный от JsonSerializerContextэтого, и передайте JsonSerializerOptions экземпляр конструктору.
  • Вызов методов сериализации или десериализации, которые принимают экземпляр контекста JsonSerializer или TypeInfo .

Ниже приведен пример класса контекста, за которым следует сериализация и десериализация кода:

[JsonSerializable(typeof(WeatherForecast))] internal partial class OptionsExampleContext : JsonSerializerContext
jsonString = JsonSerializer.Serialize( weatherForecast, typeof(WeatherForecast), new OptionsExampleContext( new JsonSerializerOptions(JsonSerializerDefaults.Web))); 
weatherForecast = JsonSerializer.Deserialize( jsonString, typeof(WeatherForecast), new OptionsExampleContext( new JsonSerializerOptions(JsonSerializerDefaults.Web))) as WeatherForecast; 

Ниже приведены приведенные выше примеры в полной программе:

using System.Text.Json; using System.Text.Json.Serialization; namespace JsonSerializerOptionsExample < public class WeatherForecast < public DateTime Date < get; set; >public int TemperatureCelsius < get; set; >public string? Summary < get; set; >> [JsonSerializable(typeof(WeatherForecast))] internal partial class OptionsExampleContext : JsonSerializerContext < >public class Program < public static void Main() < string jsonString = """ < "date": "2019-08-01T00:00:00", "temperatureCelsius": 25, "summary": "Hot" >"""; WeatherForecast? weatherForecast; weatherForecast = JsonSerializer.Deserialize( jsonString, typeof(WeatherForecast), new OptionsExampleContext( new JsonSerializerOptions(JsonSerializerDefaults.Web))) as WeatherForecast; Console.WriteLine($"Date="); // output: //Date=8/1/2019 12:00:00 AM jsonString = JsonSerializer.Serialize( weatherForecast, typeof(WeatherForecast), new OptionsExampleContext( new JsonSerializerOptions(JsonSerializerDefaults.Web))); Console.WriteLine(jsonString); // output: // < "date":"2019-08-01T00:00:00","temperatureCelsius":25,"summary":"Hot">> > > 

Объединение генераторов источников

Вы можете объединить контракты из нескольких исходных контекстов внутри одного JsonSerializerOptions экземпляра. JsonSerializerOptions.TypeInfoResolver Используйте свойство для цепочки нескольких контекстов, объединенных с помощью JsonTypeInfoResolver.Combine(IJsonTypeInfoResolver[]) метода.

var options = new JsonSerializerOptions < TypeInfoResolver = JsonTypeInfoResolver.Combine(ContextA.Default, ContextB.Default, ContextC.Default); >; 

Начиная с .NET 8, если позже вы хотите добавить или добавить другой контекст, это можно сделать с помощью JsonSerializerOptions.TypeInfoResolverChain свойства. Порядок цепочки имеет важное значение: JsonSerializerOptions запрашивает каждый из сопоставителей в указанном порядке и возвращает первый результат, который не имеет значения NULL.

options.TypeInfoResolverChain.Add(ContextD.Default); // Append to the end of the list. options.TypeInfoResolverChain.Insert(0, ContextE.Default); // Insert at the beginning of the list. 

Любые изменения, TypeInfoResolverChain внесенные в свойство, отражаются TypeInfoResolver и наоборот.

Сериализация полей перечисления в виде строк

По умолчанию перечисления сериализуются как числа. Чтобы сериализовать поля конкретного перечисления в виде строк при использовании создания источника, заметите его с JsonStringEnumConverter помощью преобразователя. Или, чтобы задать политику одеяла для всех перечислений, используйте JsonSourceGenerationOptionsAttribute атрибут.

JsonStringEnumConverter Конвертер

Чтобы сериализовать имена перечислений в виде строк с помощью создания источника, используйте преобразователь JsonStringEnumConverter . (Не универсальный JsonStringEnumConverter тип не поддерживается средой выполнения AOT в машинном коде.)

public class WeatherForecastWithPrecipEnum < public DateTimeOffset Date < get; set; >public int TemperatureCelsius < get; set; >public Precipitation? Precipitation < get; set; >> [JsonConverter(typeof(JsonStringEnumConverter))] public enum Precipitation
[JsonSerializable(typeof(WeatherForecastWithPrecipEnum))] public partial class Context1 : JsonSerializerContext

Следующий код сериализует имена перечислений вместо числовых значений:

var weatherForecast = new WeatherForecastWithPrecipEnum < Date = DateTime.Parse("2019-08-01"), TemperatureCelsius = 25, Precipitation = Precipitation.Sleet >; var options = new JsonSerializerOptions < WriteIndented = true, TypeInfoResolver = Context1.Default, >; string? jsonString = JsonSerializer.Serialize(weatherForecast, options); 

Итоговый код JSON выглядит следующим образом:

Политика одеяла

[JsonSourceGenerationOptions(UseStringEnumConverter = true)] [JsonSerializable(typeof(WeatherForecast2WithPrecipEnum))] public partial class Context2 : JsonSerializerContext

Обратите внимание, что перечисление не имеет :JsonConverterAttribute

public class WeatherForecast2WithPrecipEnum < public DateTimeOffset Date < get; set; >public int TemperatureCelsius < get; set; >public Precipitation2? Precipitation < get; set; >> public enum Precipitation2

См. также

  • Сериализация и десериализация JSON в .NET — обзор
  • Использование библиотеки

Совместная работа с нами на GitHub

Источник этого содержимого можно найти на GitHub, где также можно создавать и просматривать проблемы и запросы на вытягивание. Дополнительные сведения см. в нашем руководстве для участников.

Как записать объекты .NET в формате JSON (сериализация)

В этой статье показано, как использовать System.Text.Json пространство имен для сериализации в нотации объектов JavaScript (JSON). Если вы переносите существующий код из Newtonsoft.Json , ознакомьтесь со статьей Переход с Newtonsoft.Json на System.Text.Json System.Text.Json .

Чтобы записать JSON в строку или в файл, вызовите метод JsonSerializer.Serialize.

В следующем примере показано создание JSON в виде строки:

using System.Text.Json; namespace SerializeBasic < public class WeatherForecast < public DateTimeOffset Date < get; set; >public int TemperatureCelsius < get; set; >public string? Summary < get; set; >> public class Program < public static void Main() < var weatherForecast = new WeatherForecast < Date = DateTime.Parse("2019-08-01"), TemperatureCelsius = 25, Summary = "Hot" >; string jsonString = JsonSerializer.Serialize(weatherForecast); Console.WriteLine(jsonString); > > > // output: //
Dim jsonString As String 

Выходные данные JSON минифицируются (пробелы, отступы и новые символы строки удаляются) по умолчанию.

В примере ниже для создания JSON-файла используется синхронный код:

using System.Text.Json; namespace SerializeToFile < public class WeatherForecast < public DateTimeOffset Date < get; set; >public int TemperatureCelsius < get; set; >public string? Summary < get; set; >> public class Program < public static void Main() < var weatherForecast = new WeatherForecast < Date = DateTime.Parse("2019-08-01"), TemperatureCelsius = 25, Summary = "Hot" >; string fileName = "WeatherForecast.json"; string jsonString = JsonSerializer.Serialize(weatherForecast); File.WriteAllText(fileName, jsonString); Console.WriteLine(File.ReadAllText(fileName)); > > > // output: //
jsonString = JsonSerializer.Serialize(weatherForecast1) File.WriteAllText(fileName, jsonString) 

В следующем примере для создания JSON-файла используется асинхронный код:

using System.Text.Json; namespace SerializeToFileAsync < public class WeatherForecast < public DateTimeOffset Date < get; set; >public int TemperatureCelsius < get; set; >public string? Summary < get; set; >> public class Program < public static async Task Main() < var weatherForecast = new WeatherForecast < Date = DateTime.Parse("2019-08-01"), TemperatureCelsius = 25, Summary = "Hot" >; string fileName = "WeatherForecast.json"; await using FileStream createStream = File.Create(fileName); await JsonSerializer.SerializeAsync(createStream, weatherForecast); Console.WriteLine(File.ReadAllText(fileName)); > > > // output: //
Dim createStream As FileStream = File.Create(fileName) Await JsonSerializer.SerializeAsync(createStream, weatherForecast1) 

В предыдущих примерах для сериализуемого типа используется определение типа. Перегрузка Serialize() принимает параметр универсального типа:

using System.Text.Json; namespace SerializeWithGenericParameter < public class WeatherForecast < public DateTimeOffset Date < get; set; >public int TemperatureCelsius < get; set; >public string? Summary < get; set; >> public class Program < public static void Main() < var weatherForecast = new WeatherForecast < Date = DateTime.Parse("2019-08-01"), TemperatureCelsius = 25, Summary = "Hot" >; string jsonString = JsonSerializer.Serialize(weatherForecast); Console.WriteLine(jsonString); > > > // output: //
jsonString = JsonSerializer.Serialize(Of WeatherForecastWithPOCOs)(weatherForecast) 

Поведение сериализации

  • По умолчанию все открытые свойства сериализуются. Вы можете указать свойства, которые нужно игнорировать. Вы также можете включить частные члены.
  • Кодировщик по умолчанию экранирует символы, не относящиеся к ASCII, символы, учитывающие HTML, в пределах диапазона ASCII и символы, которые должны быть экранированы в соответствии со спецификацией JSON RFC 8259.
  • По умолчанию JSON сокращается. Вы можете структурировать JSON.
  • По умолчанию регистр имен JSON соответствует именам в .NET. Вы можете настроить регистр имен JSON.
  • По умолчанию обнаруживаются циклические ссылки и создаются исключения. Вы можете сохранять ссылки и обрабатывать циклические ссылки.
  • По умолчанию поля игнорируются. Вы можете включить поля.

При косвенном использовании System.Text.Json в приложении ASP.NET Core некоторые поведения по умолчанию отличаются. Дополнительные сведения см. в разделе Стандартные параметры веб-приложений для JsonSerializerOptions.

К поддерживаемым типам относятся:

  • Примитивы .NET, которые сопоставляются с примитивами JavaScript, например числовыми типами, строками и логическими значениями.
  • Определяемые пользователем объекты POCO (традиционные объекты среды CLR).
  • Одномерные массивы и массивы массивов ( T[][] ).
  • Коллекции и словари из следующих пространств имен:
    • System.Collections
    • System.Collections.Generic
    • System.Collections.Immutable
    • System.Collections.Concurrent
    • System.Collections.Specialized
    • System.Collections.ObjectModel

    Для обработки дополнительных типов или для обеспечения функциональности, которая не поддерживается встроенными преобразователями, можно реализовать пользовательские преобразователи.

    Ниже приведен пример, показывающий, как класс, содержащий свойства коллекции, и определяемый пользователем тип сериализуется:

    using System.Text.Json; namespace SerializeExtra < public class WeatherForecast < public DateTimeOffset Date < get; set; >public int TemperatureCelsius < get; set; >public string? Summary < get; set; >public string? SummaryField; public IList? DatesAvailable < get; set; >public Dictionary? TemperatureRanges < get; set; >public string[]? SummaryWords < get; set; >> public class HighLowTemps < public int High < get; set; >public int Low < get; set; >> public class Program < public static void Main() < var weatherForecast = new WeatherForecast < Date = DateTime.Parse("2019-08-01"), TemperatureCelsius = 25, Summary = "Hot", SummaryField = "Hot", DatesAvailable = new List() < DateTime.Parse("2019-08-01"), DateTime.Parse("2019-08-02") >, TemperatureRanges = new Dictionary < ["Cold"] = new HighLowTemps < High = 20, Low = -10 >, ["Hot"] = new HighLowTemps < High = 60 , Low = 20 >>, SummaryWords = new[] < "Cool", "Windy", "Humid" >>; var options = new JsonSerializerOptions < WriteIndented = true >; string jsonString = JsonSerializer.Serialize(weatherForecast, options); 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" // ], // "TemperatureRanges": < // "Cold": < // "High": 20, // "Low": -10 // >, // "Hot": < // "High": 60, // "Low": 20 // >// >, // "SummaryWords": [ // "Cool", // "Windy", // "Humid" // ] //> 
    Public Class WeatherForecastWithPOCOs Public Property [Date] As DateTimeOffset Public Property TemperatureCelsius As Integer Public Property Summary As String Public SummaryField As String Public Property DatesAvailable As IList(Of DateTimeOffset) Public Property TemperatureRanges As Dictionary(Of String, HighLowTemps) Public Property SummaryWords As String() End Class Public Class HighLowTemps Public Property High As Integer Public Property Low As Integer End Class ' serialization output formatted (pretty-printed with whitespace and indentation): ' < ' "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" ' ], ' "TemperatureRanges": < ' "Cold": < ' "High": 20, ' "Low": -10 ' >, ' "Hot": < ' "High": 60, ' "Low": 20 ' >' >, ' "SummaryWords": [ ' "Cool", ' "Windy", ' "Humid" ' ] ' > 

    Сериализация в UTF-8

    Это 5-10 % быстрее сериализовать в массив байтов UTF-8, чем использовать строковые методы. Это связано с тем, что байты (как UTF-8) не нужно преобразовать в строки (UTF-16).

    Чтобы сериализовать массив байтов UTF-8, вызовите JsonSerializer.SerializeToUtf8Bytes метод:

    byte[] jsonUtf8Bytes =JsonSerializer.SerializeToUtf8Bytes(weatherForecast); 
    Dim jsonUtf8Bytes As Byte() Dim options As JsonSerializerOptions = New JsonSerializerOptions With < .WriteIndented = True >jsonUtf8Bytes = JsonSerializer.SerializeToUtf8Bytes(weatherForecast1, options) 

    Также доступна перегрузка Serialize, которая принимает Utf8JsonWriter.

    Сериализация в форматированный JSON

    Чтобы структурировать выходные данные JSON, задайте для JsonSerializerOptions.WriteIndented значение true .

    using System.Text.Json; namespace SerializeWriteIndented < public class WeatherForecast < public DateTimeOffset Date < get; set; >public int TemperatureCelsius < get; set; >public string? Summary < get; set; >> public class Program < public static void Main() < var weatherForecast = new WeatherForecast < Date = DateTime.Parse("2019-08-01"), TemperatureCelsius = 25, Summary = "Hot" >; var options = new JsonSerializerOptions < WriteIndented = true >; string jsonString = JsonSerializer.Serialize(weatherForecast, options); Console.WriteLine(jsonString); > > > // output: // < // "Date": "2019-08-01T00:00:00-07:00", // "TemperatureCelsius": 25, // "Summary": "Hot" //>
    Dim options As JsonSerializerOptions = New JsonSerializerOptions With < .WriteIndented = True >jsonString = JsonSerializer.Serialize(weatherForecast, options) 

    При многократном использовании JsonSerializerOptions с одинаковыми параметрами не создавайте новый экземпляр JsonSerializerOptions при каждом использовании. Повторно используйте один и тот же экземпляр для каждого вызова. Дополнительные сведения см. в разделе Повторное использование экземпляров JsonSerializerOptions.

    Совместная работа с нами на GitHub

    Источник этого содержимого можно найти на GitHub, где также можно создавать и просматривать проблемы и запросы на вытягивание. Дополнительные сведения см. в нашем руководстве для участников.

    Кастомный JsonConverter: уменьшаем связность и экономим ресурсы

    Технически это продолжение публикации Как не дать пользователю заснуть во время загрузки большого набора данных. Провожу «капитальный рефакторинг» корпоративной системы, которая используется 20 лет. Некоторые свои решения излагаю здесь в надежде, что кому-то пригодится, а также чтобы узнать что-то новое из комментариев.

    Проблема №1

    У нас есть старая база данных, в которой за 20 лет чего только не завелось. Также в ней первичные ключи из 2 полей, когда-то то было актуально. Новую систему приходится строить на ней, чтобы можно было раньше начать ею пользоваться, какое-то время даже параллельно, постепенно перенося функциональность. Когда-то, возможно, эта структура базы данных будет заменена тоже. Мы хотим, чтобы модели ни на сервере приложений, ни на клиенте ничего не знали об устройстве БД . Очевидное решение — загружать из БД и передавать на клиента объекты с ключами БД, а использовать их через интерфейсы, без ключей.

    Проблема №2

    Объекты для таблицы для таблицы не должны содержать полную структуру, достаточно нескольких отображаемых полей для визуального контакта, поиска и сортировки. С другой стороны, не хотелось бы заводить разные классы для вывода в таблицу и загрузки единичного объекта. Логично использовать один класс, но заполнять его сущности в зависимости от потребностей, пользуясь для доступа разными интерфейсами.

    Что можно сделать штатными средствами System.Text.Json?

    Рассмотрим несколько вариантов.

    public interface IPreCat < string Breed < get; >> public interface ICatForListing < string Breed < get; >string Name < get; >> public interface IPaw < Longitude Longitude < get; >Latitude Latitude < get; >List Claws < get; >> public interface IClaw < double Sharpness < get; >> public interface IMustache < double Length < get; >> public interface ITail < double Length < get; >double Thickness < get; >> public class StringIntId < public string StringId < get; set; >public int IntId < get; set; >> public class Cat: PreCat, ICat, ICatForListing < . public StringIntId Id < get; set; >public string Name < get; set; >public List Paws < get; init; >= new(); public IMustache? Mustache < get; set; >= null; public ITail? Tail < get; set; >= null; public override string ToString() < return $":\n\tbreed: ,\n\tname: ,\n\tpaws: [\n\t\t\n\t],\n\tmustache: ,\n\ttail: \n>>"; > > . [Test] public void Test1() < // (1) Cat cat = CreateCat() as Cat; Console.WriteLine(cat); // (2) string json = JsonSerializer.Serialize(cat); Console.WriteLine(json); // (3) json = JsonSerializer.Serialize(cat); Console.WriteLine(json); // (4) json = JsonSerializer.Serialize(cat); Console.WriteLine(json); // (5) json = JsonSerializer.Serialize(cat); Console.WriteLine(json); >

    Строим котика (1), смотрим, как он выводится строкой:

    , , , ], mustache: , tail: >

    Пишем его в JSON типизируя своим классом (2):

    ,"Name":"Murka", "Paws":[,, ,,]>, ,, ,]>, <"Longitude":1,"Latitude":2, "Claws":[,, ,,]>, <"Longitude":2,"Latitude":2, "Claws":[,,]>], "Mustache":null,"Tail":,"Breed":"Havana"> 

    Пишем его в JSON типизируя object (3):

    ,"Name":"Murka", "Paws":[,, ,,]>, ,, ,]>, <"Longitude":1,"Latitude":2, "Claws":[,, ,,]>, <"Longitude":2,"Latitude":2, "Claws":[,,]>], "Mustache":null,"Tail":,"Breed":"Havana"> 

    Видим, что результаты (1) и (2) — одинаковые. Даже сюда попал Id , но только у самого котика, так как усы, лапы, когти и хвост представлены интерфейсами. Если бы мы их представили реализациями, то мы не смогли бы либо обратиться через интерфейсы к самому котику, либо эти интерфейсы зависели бы от реализаций частей кота. Оба эти варианта нам не подходят. Также нам придётся тащить в таблицу много лишних свойств. И ещё не очень хорошо, по-моему, что enum попадает в виде числа (например, . «Longitude»:1,»Latitude»:1. , здесь они означают «перед-зад» и «лево-право»). В принципе, можно настроить, чтобы значения по умолчанию (default) не передавались, но, например, у нас список лап создаётся в конструкторе, и вообще, объекты могли быть загружены раньше и полностью, и уже потом вдруг понадобилось их в таблицу на клиента передать.

    Запишем в JSON упрощённого кота для таблицы (3):

    public interface ICatForListing < string Breed < get; >string Name < get; >>

    Что же, получилось коротко, но без ключей.

    И наконец, запишем полного кота (4):

    ,, ,,]>, ,, ,]>, <"Longitude":1,"Latitude":2, "Claws":[,, ,,]>, <"Longitude":2,"Latitude":2, "Claws":[,, ]>], "Mustache":null, "Tail":> 

    Само собой, нет ключей, пропала порода ( Breed ), так как мы её в ICat не включили по какой-то причине.

    Получается, что ни один вариант нас не удовлетворил, и нам ничего другого не остаётся делать, кроме как написать .

    . кастомный конвертер

    На всякий случай напомню, как кастомный конвертер встраивается в классы сериализации JSON, предоставляемые в пространстве имен System.Text.Json. С точки зрения паттернов проектирования здесь применяется «Стратегия». Объект нашего конвертера (или фабрики конвертеров, что мы будем на самом деле использовать) добавляется в список Converters объекта класса JsonSerializerOptions , который передаётся методам JsonSerializer.Serialize(. ) и JsonSerializer.Deserialize(. ) . Наш конвертер должен уметь отвечать на вопрос, конвертирует ли он объекты запрошенного типа. Если да, то такие объекты будут в дальнейшем передаваться ему.

    OurCuctomConverter converter = new(); JsonSerializerOptions options = new(); options.Converters.Add(converter); string json = JsonSerializer.Serialize(cat, options);

    Подумаем, что мы хотели бы получить.

    • Чтобы можно было зарегистрировать любое количество интерфейсов и классов, и, если класс или его предок, или какой-то из их интерфейсов зарегистрированы, то свойства из этого зарегистрированного типа попадают в JSON.
    • Чтобы помеченное специальным атрибутом [Key] свойство также попадало в JSON.
    • Иметь возможность предоставлять готовый объект для его заполнения из JSON.
    • Если какое-то свойство целевого объекта само является и уже присвоено, заполнять его, а не присваивать новый объект.
    • Если всё же необходимо создать новый объект, то использовать механизм внедрения зависимостей.
    • Для массива верхнего уровня, то есть когда мы получаем JSON: [, , . ] , хотим, чтобы можно было заполнить существующий список/коллекцию, причём, одним из двух способов: заполняя заново или дописывая в хвост. Второй вариант можно использовать, например, чтобы подгружать данные при большом их количестве (см. https://habr.com/ru/post/653395/)

    В обоих случаях:

    • Так как нужно конвертировать несколько разных типов, наш конвертер должен быть не JsonConverter , а JsonConverterFactory .

    Итак, наследуем от System.Text.Json.Serialization.JsonConverterFactory :

    public class TransferJsonConverterFactory : JsonConverterFactory 

    Нам нужно реализовать абстрактные методы:

    public abstract bool CanConvert(Type typeToConvert); public abstract JsonConverter? CreateConverter(Type typeToConvert, JsonSerializerOptions options);

    К реализации вернёмся позже, когда рассмотрим, как регистрировать типы и внедрять зависимости.

    Внедрение зависимостей и регистрация типов

    Попытаемся это совместить. Причиной для этого может служить, то, что некоторые типы могут быть уже зарегистрированы на хосте как сервисы, а другие - нет. Так как в системном IServiceProvider нам уже ничего не зарегистрировать, заведём свой, а системный, если он доступен, будем использовать. Для этого создадим класс, реализующий этот интерфейс:

    internal class ServiceProviderImpl : IServiceProvider < private readonly IServiceProvider? _parentServiceProvider; private readonly Dictionary?> _services = new(); public ServiceProviderImpl(IServiceProvider? parentServiceProvider = null) < _parentServiceProvider = parentServiceProvider; >public void AddTransient(Type key, Func? func) < _services[key] = func; >public bool IsRegistered() < return IsRegistered(typeof(T)); >public bool IsRegistered(Type serviceType) < return _services.ContainsKey(serviceType); >public List GetRegistered() < return _services.Keys.ToList(); >#region Реализация IServiceProvider public object? GetService(Type serviceType) < if (_services.ContainsKey(serviceType)) < if (_services[serviceType] is <>service) < return service.Invoke(this); >if (serviceType.IsClass && serviceType.GetConstructor(new Type[] < >) is <>) < object? result = _parentServiceProvider? .GetService(serviceType); if (result is <>) < return result; >return Activator.CreateInstance(serviceType); > > return _parentServiceProvider?.GetService(serviceType); > #endregion >

    Мы ассоциировали некоторый внешний сервис-провайдер. Мы можем зарегистрировать тип с помощью одного из перегруженных методов AddTransient(. ) . Имя метода нам как бы напоминает, что объект должен создаваться при каждом вызове GetService(. ) или GetRequiredService(. ) . Мы можем передать истанциируемый тип или фабричный метод, тогда будет создаваться этот тип или работать фабричный метод, независимо от внешнего сервис-провайдера. Если передаём только регистрируемый тип, то пытаемся получить новый объект из внешнего сервис-провайдера, а если там его не делают, то вызвать публичный конструктор без параметров. Также наша реализация отвечает на вопрос, зарегистрирован ли тип.

    Нашу реализацию сервис-провайдера мы включаем отношением композиции:

    internal ServiceProviderImpl ServiceProvider < get; init; >public TransferJsonConverterFactory(IServiceProvider? serviceProvider)

    И вот перед нами реализация первого абстрактного метода:

    public override bool CanConvert(Type typeToConvert) < // Если вызвана десериализация для одного из типов-заглушек: // AppendableListStub<>или RewritableListStub<>, if (ServiceProvider.GetRegistered().Any(t => typeof(ListStub<>) .MakeGenericType(new Type[] < t >) .IsAssignableFrom(typeToConvert)) ) < return true; >return ServiceProvider.IsRegistered(typeToConvert); >

    Заглушки, которые проверяются сначала, используются для десеризации JSON-массива, как мы хотели выше. То есть, если мы десериализуем в новый список, то просто вызываем десериализатор с нужным типом, и наш кастомный конвертер вообще не участвует. Например:

    List cats = JsonSerializer.Deserialize(json);

    В случае, если мы предоставили свой список, мы поступаем по-другому. Например, для заполнения списка заново:

    ObservableCollection cats; . TransferJsonConverterFactory serializer = new TransferJsonConverterFactory(null) .AddTransient() ; JsonSerializerOptions options = new(); options.Converters.Add(serializer); serializer.Target = cats; JsonSerializer.Deserialize( jsonString, options);

    При этом, если в списке есть объекты которые мы десериализуем нашим конвертером, то их тушки используются повторно. Такое вот переселение душ.

    Обратим внимание на свойство:

    public object? Target

    Как раз сюда мы цепляем существующий объект, чтобы заполнять его.

    А вот реализация второго абстрактного метода:

    public override JsonConverter? CreateConverter(Type typeToConvert, JsonSerializerOptions options) < JsonConverter converter; Type? type = ServiceProvider.GetRegistered().Where( t =>typeof(ListStub<>).MakeGenericType(new Type[] < t >) .IsAssignableFrom(typeToConvert) ).FirstOrDefault((Type?)null); if (type is not null) < converter = (JsonConverter)Activator.CreateInstance( typeof(ListDeserializer<>) .MakeGenericType(new Type[] < type >), args: new object[] < this, typeToConvert == typeof(AppendableListStub<>) .MakeGenericType(new Type[] < type >) > )!; > else < converter = (JsonConverter)Activator.CreateInstance( typeof(DtoConverter<>).MakeGenericType( new Type[] < typeToConvert >), args: new object[] < this >)!; > return converter; > 

    Здесь действуем почти так же, как в случае CanConvert(. ) : если запрашивается один из типов-заглушек для списков, создаём конвертер ListDeserializer<> , в противном случае - DtoConverter<> . Оба класса являются наследниками JsonConverter<> .

    Не будем их код здесь приводить, так как он достаточно объёмный. При желании его можно посмотреть в исходниках.

    Обратим только внимание на то, что наша фабрика ассоциируется в эти объекты, поэтому, хотя мы не имеем к ним прямого доступа, как и они друг к другу, но через фабрику осуществляется доступ к зарегистрированным типам и целевому объекту.

    Вывод

    Кастомные конвертеры нам строить и жить помогают.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *