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