Description
Background and Motivation
Currently, the default json converters used for objects and collection types are internal to System.Text.Json. The library uses hardcoded conventions to decide what default converters to apply to each type: for instance classes that implement IEnumerable<T>
are automatically treated as collections, and classes that implement IAsyncEnumerable<T>
can only be handled by the async-only IAsyncEnumerable converter. As of today, System.Text.Json offers no mechanism for users to override this convention, and we have received a number of questions from the community about this issue:
- Provide option for the serializer to treat IEnumerables like objects with members #1808
- System.Text.Json Serialization Regression in NET6 (Net5 worked) #61044
- System.Text.Json unable to deserialize an object that implement IEnumerable but is itself not a list #81084
Currently the best available workaround is for users to either reimplement the default converters, or wrap/cast the values into a type that produces the desired serialization contract. At the same time, we offer no mechanism for users to tweak the serialization behaviour of the default converters other than using attribute annotations (to be ameliorated once #63686 has been implemented). Related issues:
- Investigate ways to extend deserialization support for arbitrary collection types #38514
- [System.Text.Json] JsonConverter lacks ability to specify converter for items in a collection #54189
- NullReferenceException when composing a custom converter with a default converter #57280
Proposal
This story proposes making the default internal converters public, so that users are free to opt in to specific converter strategies for their types. Consider the following example:
public class MyClass : IEnumerable<int>, IAsyncEnumerable<int>
{
}
By default, members of type MyClass
are serialized as IAsyncEnumerables. Users can still force IEnumerable semantics if they cast the instance to IEnumerable<int>
, however there is currently no way to serialize the type as an object. By exposing the default converter types, we can decorate our type definition with attributes:
[JsonConverter(typeof(JsonObjectConverter))] // serialize as object
// [JsonConverter(typeof(JsonCollectionConverter))] // serialize as collection
// [JsonConverter(typeof(JsonAsyncEnumerableConverter)] // serialize as IAsyncEnumerable
public class MyClass : IEnumerable<int>, IAsyncEnumerable<int>
{
}
Moreover, we should consider exposing parameterization on the converters so that users can configure the contract (for example what #38514 is asking for).
NB we should update the source generator so that relevant JsonPropertyAttribute
annotations are recognized and trigger relevant metadata generation, cf. #82001 (comment)
Progress
- Prototyping work
- API proposal
- Implementation & tests
- Documentation