JsonConverterAttribute не используется JsonSerializer

Я пытаюсь сериализовать свойство типа Encoding. Для этой цели я написал класс конвертера. Когда я использую JsonSerializerOptions, все работает, но когда я использую JsonConverterAttribute, я получаю исключение. Я не уверен, что делаю неправильно, используя JsonConverterAttribute, поэтому надеюсь на вашу помощь.

Пример кода и исключения:

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

namespace TestJsonConverterBug
{
    internal class Program
    {
        static readonly JsonSerializerOptions _options = new();

        static void Main()
        {
            _options.Converters.Add(new JsonEncodingConverter());

            TestOptions();
            TestAttributeSerialize();
            TestAttributeDeserialize();
        }

        // WORKS WITH OPTIONS
        static void TestOptions()
        {
            var obj = new SerializableClass1();

            var json = JsonSerializer.Serialize(obj, _options);

            var enc = JsonSerializer.Deserialize<SerializableClass1>(json, _options);
        }

        // DOESN'T WORK WITH CONVERTER ATTRIBUTE
        static void TestAttributeSerialize()
        {
            var obj = new SerializableClass2();

            var json = JsonSerializer.Serialize(obj);
        }

        // DOESN'T WORK WITH CONVERTER ATTRIBUTE
        static void TestAttributeDeserialize()
        {
            var obj = new SerializableClass2();

            var json = JsonSerializer.Serialize(obj, _options);

            var enc = JsonSerializer.Deserialize<SerializableClass2>(json);
        }
    }

    internal class SerializableClass1
    {
        public SerializableClass1()
        {
            Encoding = Encoding.ASCII;
        }

        public Encoding Encoding { get; set; }
    }

    internal class SerializableClass2
    {
        public SerializableClass2()
        {
            Encoding = Encoding.ASCII;
        }

        [JsonConverter(typeof(JsonEncodingConverter))]
        public Encoding Encoding { get; set; }
    }

    internal class JsonEncodingConverter : JsonConverter<Encoding>
    {
        public override bool CanConvert(Type typeToConvert)
            => typeToConvert.IsAssignableTo(typeof(Encoding));

        public override Encoding? Read(
            ref Utf8JsonReader reader,
            Type typeToConvert,
            JsonSerializerOptions options
        ) => Encoding.GetEncoding(reader.GetString()!);

        public override void Write(
            Utf8JsonWriter writer,
            Encoding value,
            JsonSerializerOptions options
        ) => writer.WriteStringValue(value.EncodingName);
    }
}

Исключение TestAttributeSerialize:

System.InvalidOperationException
  HResult=0x80131509
  Message=The type 'System.ReadOnlySpan`1[System.Byte]' of property 'Preamble' on type 'System.Text.Encoding' is invalid for serialization or deserialization because it is a pointer type, is a ref struct, or contains generic parameters that have not been replaced by specific types.
  Source=System.Text.Json
  StackTrace:
   at System.Text.Json.ThrowHelper.ThrowInvalidOperationException_CannotSerializeInvalidType(Type typeToConvert, Type declaringType, MemberInfo memberInfo)
   at System.Text.Json.Serialization.Metadata.DefaultJsonTypeInfoResolver.CreatePropertyInfo(JsonTypeInfo typeInfo, Type typeToConvert, MemberInfo memberInfo, JsonSerializerOptions options, Boolean shouldCheckForRequiredKeyword, Boolean hasJsonIncludeAttribute)
   at System.Text.Json.Serialization.Metadata.DefaultJsonTypeInfoResolver.AddMembersDeclaredBySuperType(JsonTypeInfo typeInfo, Type currentType, Boolean constructorHasSetsRequiredMembersAttribute, PropertyHierarchyResolutionState& state)
   at System.Text.Json.Serialization.Metadata.DefaultJsonTypeInfoResolver.PopulateProperties(JsonTypeInfo typeInfo)
   at System.Text.Json.Serialization.Metadata.DefaultJsonTypeInfoResolver.CreateTypeInfoCore(Type type, JsonConverter converter, JsonSerializerOptions options)
   at System.Text.Json.Serialization.Metadata.DefaultJsonTypeInfoResolver.GetTypeInfo(Type type, JsonSerializerOptions options)
   at System.Text.Json.JsonSerializerOptions.GetTypeInfoNoCaching(Type type)
   at System.Text.Json.JsonSerializerOptions.CachingContext.CreateCacheEntry(Type type, CachingContext context)
--- End of stack trace from previous location ---
   at System.Text.Json.JsonSerializerOptions.GetTypeInfoInternal(Type type, Boolean ensureConfigured, Nullable`1 ensureNotNull, Boolean resolveIfMutable, Boolean fallBackToNearestAncestorType)
   at System.Text.Json.Serialization.Metadata.JsonPropertyInfo.Configure()
   at System.Text.Json.Serialization.Metadata.JsonTypeInfo.ConfigureProperties()
   at System.Text.Json.Serialization.Metadata.JsonTypeInfo.Configure()
   at System.Text.Json.Serialization.Metadata.JsonTypeInfo.<EnsureConfigured>g__ConfigureSynchronized|172_0()
   at System.Text.Json.JsonSerializerOptions.GetTypeInfoInternal(Type type, Boolean ensureConfigured, Nullable`1 ensureNotNull, Boolean resolveIfMutable, Boolean fallBackToNearestAncestorType)
   at System.Text.Json.JsonSerializerOptions.GetTypeInfoForRootType(Type type, Boolean fallBackToNearestAncestorType)
   at System.Text.Json.JsonSerializer.GetTypeInfo[T](JsonSerializerOptions options)
   at System.Text.Json.JsonSerializer.Serialize[TValue](TValue value, JsonSerializerOptions options)
   at TestJsonConverterBug.Program.TestAttributeSerialize() in ...\TestJsonConverterBug\Program.cs:line 35
   at TestJsonConverterBug.Program.Main() in ...\TestJsonConverterBug\Program.cs:line 16

Исключение TestAttributeDeserialize:

System.InvalidOperationException
  HResult=0x80131509
  Message=The type 'System.ReadOnlySpan`1[System.Byte]' of property 'Preamble' on type 'System.Text.Encoding' is invalid for serialization or deserialization because it is a pointer type, is a ref struct, or contains generic parameters that have not been replaced by specific types.
  Source=System.Text.Json
  StackTrace:
   at System.Text.Json.ThrowHelper.ThrowInvalidOperationException_CannotSerializeInvalidType(Type typeToConvert, Type declaringType, MemberInfo memberInfo)
   at System.Text.Json.Serialization.Metadata.DefaultJsonTypeInfoResolver.CreatePropertyInfo(JsonTypeInfo typeInfo, Type typeToConvert, MemberInfo memberInfo, JsonSerializerOptions options, Boolean shouldCheckForRequiredKeyword, Boolean hasJsonIncludeAttribute)
   at System.Text.Json.Serialization.Metadata.DefaultJsonTypeInfoResolver.AddMembersDeclaredBySuperType(JsonTypeInfo typeInfo, Type currentType, Boolean constructorHasSetsRequiredMembersAttribute, PropertyHierarchyResolutionState& state)
   at System.Text.Json.Serialization.Metadata.DefaultJsonTypeInfoResolver.PopulateProperties(JsonTypeInfo typeInfo)
   at System.Text.Json.Serialization.Metadata.DefaultJsonTypeInfoResolver.CreateTypeInfoCore(Type type, JsonConverter converter, JsonSerializerOptions options)
   at System.Text.Json.Serialization.Metadata.DefaultJsonTypeInfoResolver.GetTypeInfo(Type type, JsonSerializerOptions options)
   at System.Text.Json.JsonSerializerOptions.GetTypeInfoNoCaching(Type type)
   at System.Text.Json.JsonSerializerOptions.CachingContext.CreateCacheEntry(Type type, CachingContext context)
--- End of stack trace from previous location ---
   at System.Text.Json.JsonSerializerOptions.GetTypeInfoInternal(Type type, Boolean ensureConfigured, Nullable`1 ensureNotNull, Boolean resolveIfMutable, Boolean fallBackToNearestAncestorType)
   at System.Text.Json.Serialization.Metadata.JsonPropertyInfo.Configure()
   at System.Text.Json.Serialization.Metadata.JsonTypeInfo.ConfigureProperties()
   at System.Text.Json.Serialization.Metadata.JsonTypeInfo.Configure()
   at System.Text.Json.Serialization.Metadata.JsonTypeInfo.<EnsureConfigured>g__ConfigureSynchronized|172_0()
   at System.Text.Json.JsonSerializerOptions.GetTypeInfoInternal(Type type, Boolean ensureConfigured, Nullable`1 ensureNotNull, Boolean resolveIfMutable, Boolean fallBackToNearestAncestorType)
   at System.Text.Json.JsonSerializerOptions.GetTypeInfoForRootType(Type type, Boolean fallBackToNearestAncestorType)
   at System.Text.Json.JsonSerializer.GetTypeInfo[T](JsonSerializerOptions options)
   at System.Text.Json.JsonSerializer.Deserialize[TValue](String json, JsonSerializerOptions options)
   at TestJsonConverterBug.Program.TestAttributeDeserialize() in ...\TestJsonConverterBug\Program.cs:line 45
   at TestJsonConverterBug.Program.Main() in ...\TestJsonConverterBug\Program.cs:line 17

Связанная проблема с GitHub: Разница в поведении JsonSerializer между .NET7 и .NET8 p4 #86419

Yong Shun 18.04.2024 15:22

@YongShun, я видел эту проблему. Проблема в том, что Encoding нельзя сериализовать как есть. В обсуждении проблемы предлагается использовать JsonConverter, но я уже использую JsonConverter.

Ruafel 18.04.2024 15:39
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
3
251
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Это известная регрессия в .NET 8, о которой уже сообщалось. Видеть:

Похоже, это непреднамеренный(?) побочный эффект преднамеренного критического изменения, отмеченного в [Критическое изменение]: Десериализатор на основе отражения System.Text.Json с готовностью обрабатывает все метаданные свойств #37041. В этом выпуске Эйрик Царпалис из MSFT рекомендует:

Удаление неподдерживаемого свойства, создание собственного преобразователя для неподдерживаемого типа или добавление JsonIgnoreAttribute следующим образом...

Поскольку добавление конвертера не работает, вместо этого вы можете пометить свойство Encoding значком [JsonIgnore] и добавить суррогатное свойство string с именем кодировки:

internal class SerializableClass2
{
    public SerializableClass2()
    {
        Encoding = Encoding.ASCII;
    }

    [JsonInclude, JsonPropertyName("Encoding")]
    string EncodingName { get => Encoding?.EncodingName!; set => Encoding = Encoding.GetEncoding(value); }

    [JsonIgnore]
    public Encoding Encoding { get; set; }
}

Суррогатный объект может быть частным, если он помечен значком [JsonInclude].

Демо-рабочий пример здесь.

Другие вопросы по теме