Closed
Description
Background and motivation
When shipped in .NET 7, the contract customization feature added support for chaining resolvers by means of the JsonTypeInfoResolver.Combine
method:
var options = new JsonTypeInfoResolver
{
TypeInfoResolver = JsonTypeInfoResolver.Combine(ContextA.Default, ContextB.Default, ContextC.Default);
};
Based on feedback we've received, this approach has a couple of usability issues:
- It necessitates specifying all chained components at one call site -- resolvers cannot be prepended or appended to the chain after the fact.
- Because the chaining implementation is abstracted behind a
IJsonTypeInfoResolver
implementation, there is no way for users to introspect the chain or remove components from it.
API Proposal
namespace System.Text.Json;
public partial class JsonSerializerOptions
{
public IJsonTypeInfoResolver? TypeInfoResolver { get; set; }
+ public IList<IJsonTypeInfoResolver> TypeInfoResolverChain { get; }
}
With this API added, we should also revert the recent change to AddContext
in #80698 since it provided an inadequate attempt to address the same underlying issue:
- It only allows appending resolvers, prepending is not possible
- It only works with JsonSerializerContext, not resolvers in general.
- It introduces a breaking change compared to the
AddContext
method in .NET 7.
API Usage
The new property should complement and be mutually consistent with the existing TypeInfoResolver
property. Here are a few examples:
Setting a resolver to TypeInfoResolver
JsonSerializerOptions options = new();
options.TypeInfoResolver = new DefaultJsonTypeInfoResolver();
Assert.Equal(new IJsonTypeInfoResolver[] { options.TypeInfoResolver }, options.ChainedTypeInfoResolvers);
Setting a combined resolver to TypeInfoResolver
JsonSerializerOptions options = new();
options.TypeInfoResolver = JsonTypeInfoResolver.Combine(CtxA.Default, CtxB.Default, CtxC.Default);
Assert.Equal(new IJsonTypeInfoResolver[] { CtxA.Default, CtxB.Default, CtxC.Default }, options.ChainedTypeInfoResolvers);
Assert.Same(options.TypeInfoResolver, options.ChainedTypeInfoResolvers);
Appending a resolver to ChainedTypeInfoResolvers
JsonSerializerOptions options = new();
options.ChainedTypeInfoResolvers.Add(CtxA.Default);
options.ChainedTypeInfoResolvers.Add(CtxB.Default);
options.ChainedTypeInfoResolvers.Add(CtxC.Default);
Assert.Equal(new IJsonTypeInfoResolver[] { CtxA.Default, CtxB.Default, CtxC.Default }, options.ChainedTypeInfoResolvers);
Assert.Same(options.TypeInfoResolver, options.ChainedTypeInfoResolvers);
Prepending a resolver to ChainedTypeInfoResolvers
:
JsonSerializerOptions options = new();
DefaultJsonTypeInfoResolver defaultResolver = new();
options.TypeInfoResolver = defaultResolver ;
Assert.Equal(new IJsonTypeInfoResolver[] { defaultResolver }, options.ChainedTypeInfoResolvers);
options.ChainedTypeInfoResolvers.Insert(0, CtxA.Default);
Assert.Equal(new IJsonTypeInfoResolver[] { CtxA.Default, defaultResolver }, options.ChainedTypeInfoResolvers);