-
Notifications
You must be signed in to change notification settings - Fork 4.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
System.Text.Json Serialization Regression in NET6 (Net5 worked) #61044
Comments
Tagging subscribers to this area: @dotnet/area-system-text-json Issue DetailsDescriptionI Serialize a Class with an IQueryable in it, looks like this: public class WebsocketResponse { Reproduction StepsTest Project: use following 2 nugets:
and following code:
Expected behaviorSerialization should work as it did before. Actual behaviorSystem.NotSupportedException: 'The type 'LinqToDB.Linq.Table`1[...]' can only be serialized using async serialization methods.' Regression?Yes, this did work in Net5 Known WorkaroundsNo response ConfigurationNo response Other informationNo response
|
If you run the example code in net5 it will work. (it will still throw an exception, but you see that it trys to enumerate the IQueryable) |
A workaround I could use atm:
|
The error message looks related to |
Agree with @huoyaoyuan's assessment. Presumably this happens because the ITable<T> interface inherits from |
@eiriktsarpalis Some libraries like EF treats |
Hmm. IAsyncEnumerable does take precedence over IEnumerable since it is assumed that types implementing both interfaces will block when iterating as At the same time the IAsyncEnumerable converter statically has no visibility over whether I think the best solution in the future might be to expose IEnumerable converter factory so that users are able to register that behavior for the types they need. A similar request has been filed for users needing to opt into the object converter for classes implementing IEnumerable. |
That all means the breaking change will stay as is in Net6? But why does System.Text.Json use IAsyncEnumerable at all, in the synchronous serialization methods, when it can only serialize with async serialization methods? |
I think System.Text.json should, when I use the synchronous methods, stop using the IAsyncEnumerable interface. |
Unfortunately that is not technically possible with the current converter model. Each converted type/member is fixed to a specific converter instance (and from the converter's perspective it's serializing a The only real workaround is to force blocking enumeration of the |
FWIW this is documented in a relevant breaking change document: https://docs.microsoft.com/en-us/dotnet/core/compatibility/serialization/6.0/iasyncenumerable-serialization#new-behavior |
in the breaking change you list that the async methods will enumerate the iasyncenumerable, not that the synchronous will fail. The only real workaround is to force blocking enumeration of the IAsyncEnumerable, but again this is something we would like to avoid unless the user opted in to that. |
Here's how I would write it. Be warned that a) this hasn't been tested and b) it forces blocking enumeration in the async case as well: public class BlockingAsyncEnumerableConverter : JsonConverterFactory
{
public override bool CanConvert(Type typeToConvert) => ImplementsAsyncEnumerable(typeToConvert, out _);
public override JsonConverter? CreateConverter(Type typeToConvert, JsonSerializerOptions options)
{
if (ImplementsAsyncEnumerable(typeToConvert, out Type? elementType))
{
Type converterType = typeof(BlockingAsyncEnumerableConverter<,>).MakeGenericType(typeToConvert, elementType);
return (JsonConverter)Activator.CreateInstance(converterType)!;
}
throw new ArgumentException(nameof(typeToConvert));
}
private static bool ImplementsAsyncEnumerable(Type type, [NotNullWhen(true)] out Type? elementType)
{
if (type.IsInterface && IsAsyncEnumerable(type, out elementType))
{
return true;
}
foreach (Type interfaceTy in type.GetInterfaces())
{
if (IsAsyncEnumerable(type, out elementType))
{
return true;
}
}
elementType = null;
return false;
static bool IsAsyncEnumerable(Type type, [NotNullWhen(true)] out Type? elementType)
{
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IAsyncEnumerable<>))
{
elementType = type.GetGenericArguments()[0];
return true;
}
elementType = null;
return false;
}
}
}
public sealed class BlockingAsyncEnumerableConverter<TCollection, TElement> : JsonConverter<TCollection>
where TCollection : IAsyncEnumerable<TElement>
{
private readonly static JsonConverter<TCollection> s_defaultConverter = (JsonConverter<TCollection>)new JsonSerializerOptions().GetConverter(typeof(TCollection));
private JsonConverter<TElement>? _elementConverter;
public override TCollection? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return s_defaultConverter.Read(ref reader, typeToConvert, options);
}
public override void Write(Utf8JsonWriter writer, TCollection value, JsonSerializerOptions options)
{
var elementConverter = _elementConverter ??= (JsonConverter<TElement>)options.GetConverter(typeof(TElement));
var enumerator = value.GetAsyncEnumerator();
try
{
writer.WriteStartArray();
while (enumerator.MoveNextAsync().ConfigureAwait(false).GetAwaiter().GetResult())
{
elementConverter.Write(writer, enumerator.Current, options);
}
writer.WriteEndArray();
}
finally
{
enumerator.DisposeAsync().ConfigureAwait(false).GetAwaiter().GetResult();
}
}
} |
Related to #1808. |
Triage: issue can be worked around by either casting the value to IEnumerable or registering a custom converter. In the future we should explore adding additional APIs that let users opt-in to internal converters that are not resolved by default for the given type (cf. #1808). Moving to 7.0.0 |
I think the breaking changes need to be fixed, so people also know that the Synchronous methods are also changed. And I think I tried to use AsEnumerable(), but that did not work (but I have to check again) |
Before ` System.ArgumentNullException: Value cannot be null. (Parameter 'obj') Stack Trace: You can work around this with a custom converter but it would be nice if System.Text.Json was returned to the original operation as the converter workaround only works if you use the generic version of the |
@mwwhited I might be misunderstanding your problem but it seems unrelated to the original issue. I would recommend creating a new issue preferably with a minimal reproduction so we can understand what the problem is. |
It's the same fundamental issue. It was a breaking change introduced when you guys add all the null checks and additional abstractions in .Net 5 without sanity checking the changes to see if they changed conditional logic. |
I'm not sure I see the connection. The original report specifically refers to IAsyncEnumerable serialization. I think a minimal repro in a separate issue would help. Thanks. |
Closing in favor of #63791. |
Description
I Serialize a Class with an IQueryable in it, looks like this:
public class WebsocketResponse {
public object Response {get;set;}
}
Reproduction Steps
Test Project:
use following 2 nugets:
and following code:
Expected behavior
Serialization should work as it did before.
Actual behavior
System.NotSupportedException: 'The type 'LinqToDB.Linq.Table`1[...]' can only be serialized using async serialization methods.'
Regression?
Yes, this did work in Net5
Known Workarounds
No response
Configuration
No response
Other information
No response
The text was updated successfully, but these errors were encountered: