skip to Main Content

I am trying to serialize a property of Encoding type. I wrote a converter class for that purpose. When I use JsonSerializerOptions everything works, but when I use JsonConverterAttribute I get an exception. I am not sure what I am doing wrong to use JsonConverterAttribute so I hope for your help.

Example code and exceptions:

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 exception:

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 ...TestJsonConverterBugProgram.cs:line 35
   at TestJsonConverterBug.Program.Main() in ...TestJsonConverterBugProgram.cs:line 16

TestAttributeDeserialize exception:

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 ...TestJsonConverterBugProgram.cs:line 45
   at TestJsonConverterBug.Program.Main() in ...TestJsonConverterBugProgram.cs:line 17

2

Answers


  1. This is a known regression in .NET 8 which has already been reported. See:

    It seems to be an unintended side effect of the intentional breaking change noted in [Breaking change]: The System.Text.Json reflection-based deserializer is resolving all property metadata eagerly #37041. In that issue, MSFT’s Eirik Tsarpalis recommends:

    Removing the unsupported property, authoring a custom converter for the unsupported type, or adding the JsonIgnoreAttribute like so…

    Since adding a converter does not work, you can instead marking the Encoding property with [JsonIgnore] and add a surrogate string property with the encoding name:

    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; }
    }
    

    The surrogate can be private as long as it is marked with [JsonInclude].

    Demo fiddle here.

    Login or Signup to reply.
  2. JsonSerializer does not support converter attributes on properties. Converter attributes can only be used on class or struct definitions.

    Instead try this:

    namespace TestJsonConverterBug  
    {  
        internal class Program  
        {  
            static readonly JsonSerializerOptions _options = new();  
      
            static void Main()  
            {  
                _options.Converters.Add(new JsonEncodingConverter());  
      
                TestOptions();  
                TestAttributeSerialize();  
                TestAttributeDeserialize();  
            }  
      
            static void TestOptions()  
            {  
                var obj = new SerializableClass1();  
      
                var json = JsonSerializer.Serialize(obj, _options);  
      
                var enc = JsonSerializer.Deserialize<SerializableClass1>(json, _options);  
            }  
      
            static void TestAttributeSerialize()  
            {  
                var obj = new SerializableClass2();  
      
                var json = JsonSerializer.Serialize(obj, _options);  
            }  
      
            static void TestAttributeDeserialize()  
            {  
                var obj = new SerializableClass2();  
      
                var json = JsonSerializer.Serialize(obj, _options);  
      
                var enc = JsonSerializer.Deserialize<SerializableClass2>(json, _options);  
            }  
        }  
      
        internal class SerializableClass1  
        {  
            public SerializableClass1()  
            {  
                Encoding = Encoding.ASCII;  
            }  
      
            public Encoding Encoding { get; set; }  
        }  
      
        internal class SerializableClass2  
        {  
            public SerializableClass2()  
            {  
                Encoding = Encoding.ASCII;  
            }  
      
            [JsonIgnore]  
            public Encoding Encoding { get; set; }  
      
            [JsonPropertyName("Encoding")]  
            public string EncodingName  
            {  
                get => Encoding.EncodingName;  
                set => Encoding = Encoding.GetEncoding(value);  
            }  
        }  
      
        internal class JsonEncodingConverter : JsonConverter<Encoding>  
        {  
            public override Encoding? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)  
            {  
                throw new NotSupportedException();  
            }  
      
            public override void Write(Utf8JsonWriter writer, Encoding value, JsonSerializerOptions options)  
            {  
                throw new NotSupportedException();  
            }  
        } 
     }
    

    Use [JsonIgnore] on the Encoding property, and then introduce a new property called EncodingName (this is what is actually serialized and deserialized). The EncodingName property uses the JsonPropertyName attribute to specify the JSON property name, and its getter and setter convert between the Encoding property and its string representation.

    Note that the JsonEncodingConverter is no longer used, as it is not needed.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search