refactor: remove obsolete StreamingContext from enum deserialization

Pass DeserializationErrorHandler directly to TolerantStringEnumConverter
through a contract resolver instead of using StreamingContext.

Remove the global JsonConvert.DefaultSettings mutation and obsolete
StreamingContext helper classes. Preserve guidance for reporting unknown
VK API enum values.
This commit is contained in:
CaCTuCaTu4ECKuu 2026-10-11 02:05:01 +02:00
parent 3d9db2caba
commit 761c2b3d19
5 changed files with 43 additions and 119 deletions

View file

@ -1,27 +0,0 @@
using System;
using JetBrains.Annotations;
namespace VkNet.Infrastructure;
/// <summary>
/// Интерфейс словаря контекста
/// </summary>
public interface IStreamingContextTypeDataDictionary
{
/// <summary>
/// Добавить данные
/// </summary>
/// <param name="type">Тип</param>
/// <param name="data">Данные</param>
public void AddData(Type type, [CanBeNull] object data);
/// <summary>
/// Попытка получения данных
/// </summary>
/// <param name="type">Тип</param>
/// <param name="data">Данные</param>
/// <returns>
/// Успешность получения данных
/// </returns>
public bool TryGetData(Type type, [CanBeNull] out object data);
}

View file

@ -1,55 +0,0 @@
using System;
using System.Runtime.Serialization;
using JetBrains.Annotations;
namespace VkNet.Infrastructure;
/// <summary>
/// Класс для кэширования данных в StreamingContext
/// </summary>
public static class StreamingContextExtensions
{
/// <summary>
/// Добавить данные в контекст
/// </summary>
/// <param name="context">Контекст данных</param>
/// <param name="type">Тип</param>
/// <param name="data">Данные</param>
/// <returns>
/// Контекст
/// </returns>
public static StreamingContext AddTypeData(this StreamingContext context, Type type, [CanBeNull] object data)
{
var dictionary = context.Context switch
{
null => new StreamingContextTypeDataDictionary(),
IStreamingContextTypeDataDictionary d => d,
var _ => throw new InvalidOperationException($"context.Context is already populated with {context.Context}")
};
dictionary.AddData(type, data);
return new(context.State, dictionary);
}
/// <summary>
/// Попытка получения данных из контекста
/// </summary>
/// <param name="context">Контекст</param>
/// <param name="type">Тип</param>
/// <param name="data">Данные</param>
/// <returns>
/// Успешность получения данных
/// </returns>
public static bool TryGetTypeData(this StreamingContext context, Type type, [CanBeNull] out object data)
{
if (context.Context is IStreamingContextTypeDataDictionary dictionary)
{
return dictionary.TryGetData(type, out data);
}
data = null;
return false;
}
}

View file

@ -1,16 +0,0 @@
using System;
using System.Collections.Generic;
namespace VkNet.Infrastructure;
/// <inheritdoc cref="IStreamingContextTypeDataDictionary" />
internal class StreamingContextTypeDataDictionary : IStreamingContextTypeDataDictionary
{
private readonly Dictionary<Type, object> _dictionary = new();
/// <inheritdoc />
public void AddData(Type type, object data) => _dictionary.Add(type, data);
/// <inheritdoc />
public bool TryGetData(Type type, out object data) => _dictionary.TryGetValue(type, out data);
}

View file

@ -2,16 +2,25 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Serialization;
using VkNet.Infrastructure;
namespace VkNet.Utils.JsonConverter;
/// <inheritdoc />
public class TolerantStringEnumConverter : StringEnumConverter
{
private readonly bool? _ignoreUnknownValues;
/// <inheritdoc />
public TolerantStringEnumConverter() => NamingStrategy = new SnakeCaseNamingStrategy();
/// <summary>
/// Создает конвертер с указанным поведением для неизвестных значений enum.
/// </summary>
/// <param name="ignoreUnknownValues">
/// Следует ли заменить неизвестное значение nullable enum на <see langword="null"/>.
/// </param>
public TolerantStringEnumConverter(bool? ignoreUnknownValues) : this() => _ignoreUnknownValues = ignoreUnknownValues;
/// <inheritdoc />
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
@ -19,15 +28,12 @@ public class TolerantStringEnumConverter : StringEnumConverter
{
return base.ReadJson(reader, objectType, existingValue, serializer);
}
catch (System.Exception e)
catch (System.Exception)
{
var parameter = serializer.Context.TryGetTypeData(typeof(TolerantStringEnumConverter), out var s)
? (bool?) s
: null;
if (IsNullableType(objectType) && parameter is true)
if (IsNullableType(objectType) && _ignoreUnknownValues is true)
{
//_logger.LogError($"\nПОЖАЛУЙСТА ОТКРОЙТЕ ISSUE: {$"https://github.com/vknet/vk/issues/new?title=Add%20new%20value%20to%20{objectType.GenericTypeArguments.FirstOrDefault()}&body={reader.Value}"}\n" + e.Message);
// Неизвестное значение может означать, что VK API добавил новый элемент enum.
// Создайте Issue: https://github.com/vknet/vk/issues/new, чтобы добавить его в соответствующий enum библиотеки.
return null;
}
@ -37,3 +43,29 @@ public class TolerantStringEnumConverter : StringEnumConverter
private static bool IsNullableType(Type t) => t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Nullable<>);
}
/// <summary>
/// Настраивает конвертеры enum, заданные через <see cref="JsonConverterAttribute"/>.
/// </summary>
internal sealed class TolerantStringEnumContractResolver(bool? ignoreUnknownValues) : DefaultContractResolver
{
protected override JsonContract CreateContract(Type objectType)
{
var contract = base.CreateContract(objectType);
contract.Converter = ReplaceTolerantConverter(contract.Converter);
return contract;
}
protected override JsonProperty CreateProperty(System.Reflection.MemberInfo member, MemberSerialization memberSerialization)
{
var property = base.CreateProperty(member, memberSerialization);
property.Converter = ReplaceTolerantConverter(property.Converter);
return property;
}
private Newtonsoft.Json.JsonConverter ReplaceTolerantConverter(Newtonsoft.Json.JsonConverter converter) => converter is TolerantStringEnumConverter
? new TolerantStringEnumConverter(ignoreUnknownValues)
: converter;
}

View file

@ -2,7 +2,6 @@
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.Serialization;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
@ -271,22 +270,13 @@ public class VkApi : IVkApi
{
var answer = CallBase(methodName, parameters, skipAuthorization);
var context = new StreamingContext(StreamingContextStates.All)
.AddTypeData(typeof(TolerantStringEnumConverter), DeserializationErrorHandler);
JsonConvert.DefaultSettings = () => new()
{
Context = context
};
var settings = new JsonSerializerSettings
{
Converters = [],
ContractResolver = new DefaultContractResolver
ContractResolver = new TolerantStringEnumContractResolver(DeserializationErrorHandler)
{
NamingStrategy = new SnakeCaseNamingStrategy()
},
Context = context,
MaxDepth = null,
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
};