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
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:
Since adding a converter does not work, you can instead marking the
Encoding
property with[JsonIgnore]
and add a surrogatestring
property with the encoding name:The surrogate can be private as long as it is marked with
[JsonInclude]
.Demo fiddle here.
JsonSerializer does not support converter attributes on properties. Converter attributes can only be used on class or struct definitions.
Instead try this:
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.