Примечание.
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
Библиотека System.Text.Json создает контракт JSON для каждого типа .NET, который определяет способ сериализации и десериализации типа. Контракт определяется формой типа, которая включает такие характеристики, как его свойства и поля, а также то, реализует ли он интерфейс IEnumerable или IDictionary. Типы сопоставляются с контрактами в среде выполнения с помощью отражения или во время компиляции с помощью генератора источника.
Начиная с .NET 7, вы можете настроить эти контракты JSON, чтобы обеспечить более контроль над преобразованием типов в JSON и наоборот. В следующем списке показаны лишь некоторые примеры типов настроек, которые можно сделать для сериализации и десериализации:
- Сериализация частных полей и свойств.
- Поддержка нескольких имен для одного свойства (например, если предыдущая версия библиотеки использовала другое имя).
- Игнорировать свойства с определенным именем, типом или значением.
- Различает явные
nullзначения и отсутствие значения в полезных данных JSON. - Поддерживаемые атрибуты System.Runtime.Serialization, такие как DataContractAttribute. Дополнительные сведения см. в разделе Атрибуты System.Runtime.Serialization.
- Создает исключение, если JSON содержит свойство, которое не является частью целевого типа. Дополнительные сведения см. в разделе "Обработка отсутствующих элементов".
Как принять участие
Существует два способа подключения к настройке. Оба включают получение сопоставителя, задание которого заключается в предоставлении экземпляра JsonTypeInfo для каждого типа, который необходимо сериализовать.
Вызывая DefaultJsonTypeInfoResolver() конструктор для получения JsonSerializerOptions.TypeInfoResolver, добавляйте пользовательские действия в его свойство Modifiers.
Например:
JsonSerializerOptions options = new() { TypeInfoResolver = new DefaultJsonTypeInfoResolver { Modifiers = { MyCustomModifier1, MyCustomModifier2 } } };Если добавить несколько модификаторов, они будут вызываться последовательно.
Написав пользовательский сопоставитель, реализующий IJsonTypeInfoResolver.
- Если тип не обрабатывается, IJsonTypeInfoResolver.GetTypeInfo возвращается
nullдля этого типа. - Вы также можете объединить настраиваемый сопоставитель с другими сопоставителями, например, с сопоставителем по умолчанию. Разрешающие системы будут последовательно запрашиваться до тех пор, пока для типа не будет возвращено ненулевое значение JsonTypeInfo.
- Если тип не обрабатывается, IJsonTypeInfoResolver.GetTypeInfo возвращается
Настраиваемые аспекты
Свойство JsonTypeInfo.Kind указывает, как преобразователь сериализует заданный тип, например как объект или массив, а также сериализуется ли его свойства. Это свойство можно запросить, чтобы определить, какие аспекты контракта JSON типа можно настроить. Существует четыре различных типа:
JsonTypeInfo.Kind |
Описание |
|---|---|
| JsonTypeInfoKind.Object | Преобразователь сериализует тип в объект JSON и использует его свойства. Этот вид используется для большинства типов классов и структур и обеспечивает большую гибкость. |
| JsonTypeInfoKind.Enumerable | Преобразователь сериализует тип в массив JSON. Этот тип используется для типов, таких как List<T> и массив. |
| JsonTypeInfoKind.Dictionary | Преобразователь сериализует тип в объект JSON. Этот тип используется для таких типов, как Dictionary<K, V>. |
| JsonTypeInfoKind.None | Преобразователь не указывает, как будет сериализовать тип или какие JsonTypeInfo свойства он будет использовать. Этот тип используется для таких типов, как System.Object, intи stringдля всех типов, использующих настраиваемый преобразователь. |
Модификаторы
Модификатор — это Action<JsonTypeInfo> или метод с параметром JsonTypeInfo, который получает текущее состояние контракта в качестве аргумента и вносит изменения в контракт. Например, можно выполнить итерацию по предварительно заполненным свойствам указанного JsonTypeInfo , чтобы найти интересующий вас объект, а затем изменить его JsonPropertyInfo.Get свойство (для сериализации) или JsonPropertyInfo.Set свойства (для десериализации). Кроме того, можно создать новое свойство с помощью JsonTypeInfo.CreateJsonPropertyInfo(Type, String) и добавить его в коллекцию JsonTypeInfo.Properties .
В следующей таблице показаны изменения, которые можно сделать и как их достичь.
| Изменение | Применимый JsonTypeInfo.Kind |
Как достичь этого | Пример |
|---|---|---|---|
| Настройка значения свойства | JsonTypeInfoKind.Object |
Измените JsonPropertyInfo.Get делегат (для сериализации) или JsonPropertyInfo.Set делегат (для десериализации) для свойства. | Увеличение значения свойства |
| Добавление или удаление свойств | JsonTypeInfoKind.Object |
Добавьте или удалите элементы из JsonTypeInfo.Properties списка. | Сериализация частных полей |
| Условно сериализовать свойство | JsonTypeInfoKind.Object |
Измените JsonPropertyInfo.ShouldSerialize предикат для свойства. | Игнорировать свойства с определенным типом |
| Настройка обработки чисел для определенного типа | JsonTypeInfoKind.None |
Измените JsonTypeInfo.NumberHandling значение типа. | Разрешить значениям int быть строками |
Пример. Увеличение значения свойства
Рассмотрим следующий пример, когда модификатор увеличивает значение определенного свойства при десериализации путем изменения его JsonPropertyInfo.Set делегата. Помимо определения модификатора, в примере также представлен новый атрибут, который он использует для поиска свойства, значение которого следует увеличить. Это пример настройки свойства.
using System.Text.Json;
using System.Text.Json.Serialization.Metadata;
namespace Serialization
{
// Custom attribute to annotate the property
// we want to be incremented.
[AttributeUsage(AttributeTargets.Property)]
class SerializationCountAttribute : Attribute
{
}
// Example type to serialize and deserialize.
class Product
{
public string Name { get; set; } = "";
[SerializationCount]
public int RoundTrips { get; set; }
}
public class SerializationCountExample
{
// Custom modifier that increments the value
// of a specific property on deserialization.
static void IncrementCounterModifier(JsonTypeInfo typeInfo)
{
foreach (JsonPropertyInfo propertyInfo in typeInfo.Properties)
{
if (propertyInfo.PropertyType != typeof(int))
continue;
object[] serializationCountAttributes = propertyInfo.AttributeProvider?.GetCustomAttributes(typeof(SerializationCountAttribute), true) ?? Array.Empty<object>();
SerializationCountAttribute? attribute = serializationCountAttributes.Length == 1 ? (SerializationCountAttribute)serializationCountAttributes[0] : null;
if (attribute != null)
{
Action<object, object?>? setProperty = propertyInfo.Set;
if (setProperty is not null)
{
propertyInfo.Set = (obj, value) =>
{
if (value != null)
{
// Increment the value by 1.
value = (int)value + 1;
}
setProperty (obj, value);
};
}
}
}
}
public static void RunIt()
{
var product = new Product
{
Name = "Aquafresh"
};
JsonSerializerOptions options = new()
{
TypeInfoResolver = new DefaultJsonTypeInfoResolver
{
Modifiers = { IncrementCounterModifier }
}
};
// First serialization and deserialization.
string serialized = JsonSerializer.Serialize(product, options);
Console.WriteLine(serialized);
// {"Name":"Aquafresh","RoundTrips":0}
Product deserialized = JsonSerializer.Deserialize<Product>(serialized, options)!;
Console.WriteLine($"{deserialized.RoundTrips}");
// 1
// Second serialization and deserialization.
serialized = JsonSerializer.Serialize(deserialized, options);
Console.WriteLine(serialized);
// { "Name":"Aquafresh","RoundTrips":1}
deserialized = JsonSerializer.Deserialize<Product>(serialized, options)!;
Console.WriteLine($"{deserialized.RoundTrips}");
// 2
}
}
}
Обратите внимание, что значение RoundTrips увеличивается каждый раз, когда экземпляр Product десериализуется.
Пример. Сериализация частных полей
По умолчанию System.Text.Json игнорирует частные поля и свойства. В этом примере добавляется новый атрибут на уровне класса, JsonIncludePrivateFieldsAttributeчтобы изменить значение по умолчанию. Если модификатор находит атрибут в типе, он добавляет все частные поля в тип в качестве новых свойств JsonTypeInfo.
using System.Reflection;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.Json.Serialization.Metadata;
namespace Serialization
{
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)]
public class JsonIncludePrivateFieldsAttribute : Attribute { }
[JsonIncludePrivateFields]
public class Human
{
private string _name;
private int _age;
public Human()
{
// This constructor should be used only by deserializers.
_name = null!;
_age = 0;
}
public static Human Create(string name, int age)
{
Human h = new()
{
_name = name,
_age = age
};
return h;
}
[JsonIgnore]
public string Name
{
get => _name;
set => throw new NotSupportedException();
}
[JsonIgnore]
public int Age
{
get => _age;
set => throw new NotSupportedException();
}
}
public class PrivateFieldsExample
{
static void AddPrivateFieldsModifier(JsonTypeInfo jsonTypeInfo)
{
if (jsonTypeInfo.Kind != JsonTypeInfoKind.Object)
return;
if (!jsonTypeInfo.Type.IsDefined(typeof(JsonIncludePrivateFieldsAttribute), inherit: false))
return;
foreach (FieldInfo field in jsonTypeInfo.Type.GetFields(BindingFlags.Instance | BindingFlags.NonPublic))
{
JsonPropertyInfo jsonPropertyInfo = jsonTypeInfo.CreateJsonPropertyInfo(field.FieldType, field.Name);
jsonPropertyInfo.Get = field.GetValue;
jsonPropertyInfo.Set = field.SetValue;
jsonTypeInfo.Properties.Add(jsonPropertyInfo);
}
}
public static void RunIt()
{
var options = new JsonSerializerOptions
{
TypeInfoResolver = new DefaultJsonTypeInfoResolver
{
Modifiers = { AddPrivateFieldsModifier }
}
};
var human = Human.Create("Julius", 37);
string json = JsonSerializer.Serialize(human, options);
Console.WriteLine(json);
// {"_name":"Julius","_age":37}
Human deserializedHuman = JsonSerializer.Deserialize<Human>(json, options)!;
Console.WriteLine($"[Name={deserializedHuman.Name}; Age={deserializedHuman.Age}]");
// [Name=Julius; Age=37]
}
}
}
Совет
Если имена частных полей начинаются с подчеркивания, рассмотрите возможность удаления подчеркивания из имен при добавлении полей в качестве новых свойств JSON.
Пример: игнорировать свойства с определенным типом
Возможно, у вашей модели есть свойства с определенными именами или типами, которые вы не хотите предоставлять пользователям. Например, у вас может быть свойство, которое хранит учетные данные или некоторую информацию, которая не нужна в полезной нагрузке.
В следующем примере показано, как отфильтровать свойства с определенным типом SecretHolder. Это делается с помощью IList<T> метода расширения для удаления любых свойств, имеющих указанный тип из JsonTypeInfo.Properties списка. Отфильтрованные свойства полностью исчезают из контракта, что означает, что System.Text.Json также не учитывает их во время сериализации и десериализации.
using System.Text.Json;
using System.Text.Json.Serialization.Metadata;
namespace Serialization
{
class ExampleClass
{
public string Name { get; set; } = "";
public SecretHolder? Secret { get; set; }
}
class SecretHolder
{
public string Value { get; set; } = "";
}
class IgnorePropertiesWithType
{
private readonly Type[] _ignoredTypes;
public IgnorePropertiesWithType(params Type[] ignoredTypes)
=> _ignoredTypes = ignoredTypes;
public void ModifyTypeInfo(JsonTypeInfo ti)
{
if (ti.Kind != JsonTypeInfoKind.Object)
return;
ti.Properties.RemoveAll(prop => _ignoredTypes.Contains(prop.PropertyType));
}
}
public class IgnoreTypeExample
{
public static void RunIt()
{
var modifier = new IgnorePropertiesWithType(typeof(SecretHolder));
JsonSerializerOptions options = new()
{
TypeInfoResolver = new DefaultJsonTypeInfoResolver
{
Modifiers = { modifier.ModifyTypeInfo }
}
};
ExampleClass obj = new()
{
Name = "Password",
Secret = new SecretHolder { Value = "MySecret" }
};
string output = JsonSerializer.Serialize(obj, options);
Console.WriteLine(output);
// {"Name":"Password"}
}
}
public static class ListHelpers
{
// IList<T> implementation of List<T>.RemoveAll method.
public static void RemoveAll<T>(this IList<T> list, Predicate<T> predicate)
{
for (int i = 0; i < list.Count; i++)
{
if (predicate(list[i]))
{
list.RemoveAt(i--);
}
}
}
}
}
Пример: Разрешить значениям int быть строками
Возможно, входной код JSON может содержать кавычки вокруг одного из числовых типов, но не на других. Если бы у вас был контроль над классом, вы могли бы поместить JsonNumberHandlingAttribute на тип, чтобы исправить это, но вы этого не можете. Прежде чем .NET 7, необходимо написать настраиваемый преобразователь для исправления этого поведения, который требует написания достаточного количества кода. С помощью настройки контракта можно настроить поведение обработки чисел для любого типа.
В следующем примере изменяется поведение для всех int значений. Пример можно легко настроить для применения к любому типу или к конкретному свойству любого типа.
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.Json.Serialization.Metadata;
namespace Serialization
{
public class Point
{
public int X { get; set; }
public int Y { get; set; }
}
public class AllowIntsAsStringsExample
{
static void SetNumberHandlingModifier(JsonTypeInfo jsonTypeInfo)
{
if (jsonTypeInfo.Type == typeof(int))
{
jsonTypeInfo.NumberHandling = JsonNumberHandling.AllowReadingFromString;
}
}
public static void RunIt()
{
JsonSerializerOptions options = new()
{
TypeInfoResolver = new DefaultJsonTypeInfoResolver
{
Modifiers = { SetNumberHandlingModifier }
}
};
// Triple-quote syntax is a C# 11 feature.
Point point = JsonSerializer.Deserialize<Point>("""{"X":"12","Y":"3"}""", options)!;
Console.WriteLine($"({point.X},{point.Y})");
// (12,3)
}
}
}
Без модификатора, разрешающего чтение значений int из строки, программа бы завершилась с исключением:
Необработанное исключение. System.Text.Json.JsonException: не удалось преобразовать значение JSON в System.Int32. Путь: $.X | Номер строки: 0 | Позиция байта в строке: 9.
Другие способы настройки сериализации
Помимо настройки контракта, существуют и другие способы влияния на поведение сериализации и десериализации, в том числе следующие:
- Используя атрибуты, производные от JsonAttribute, например, JsonIgnoreAttribute и JsonPropertyOrderAttribute.
- Изменив JsonSerializerOptions, например, чтобы задать политику именования или сериализовать значения перечисления в виде строк вместо чисел.
- При написании пользовательского преобразователя, который выполняет фактическую запись JSON и создание объекта во время десериализации.
Настройка контракта является улучшением предсуществующих настроек, так как у вас может не быть доступа к типу для добавления атрибутов. Кроме того, написание пользовательского преобразователя является сложным и вредит производительности.