public static partial class JsonTypeInfoResolver
{
public static System.Text.Json.Serialization.Metadata.IJsonTypeInfoResolver Combine(params System.Text.Json.Serialization.Metadata.IJsonTypeInfoResolver?[] resolvers) { throw null; }
+ public static System.Text.Json.Serialization.Metadata.IJsonTypeInfoResolver WithAddedModifier(this System.Text.Json.Serialization.Metadata.IJsonTypeInfoResolver resolver, System.Action<System.Text.Json.Serialization.Metadata.JsonTypeInfo> modifier) { throw null; }
}
public sealed partial class JsonTypeInfo<T> : System.Text.Json.Serialization.Metadata.JsonTypeInfo
{
<Compile Include="System\Text\Json\Serialization\Metadata\JsonTypeInfoResolver.cs" />
<Compile Include="System\Text\Json\Serialization\Metadata\JsonTypeInfoKind.cs" />
<Compile Include="System\Text\Json\Serialization\Metadata\JsonTypeInfoResolverChain.cs" />
+ <Compile Include="System\Text\Json\Serialization\Metadata\JsonTypeInfoResolverWithAddedModifiers.cs" />
<Compile Include="System\Text\Json\Serialization\PolymorphicSerializationState.cs" />
<Compile Include="System\Text\Json\Writer\Utf8JsonWriterCache.cs" />
<Compile Include="System\Text\Json\Serialization\ReferenceEqualsWrapper.cs" />
TypeInfoResolver = JsonSerializer.IsReflectionEnabledByDefault
? DefaultJsonTypeInfoResolver.RootDefaultInstance()
- : new JsonTypeInfoResolverChain(),
+ : JsonTypeInfoResolver.Empty,
_isReadOnly = true,
};
[RequiresUnreferencedCode(JsonSerializer.SerializationUnreferencedCodeMessage)]
[RequiresDynamicCode(JsonSerializer.SerializationRequiresDynamicCodeMessage)]
- internal static JsonConverter? GetCustomConverterForMember(Type typeToConvert, MemberInfo memberInfo, JsonSerializerOptions options)
+ private static JsonConverter? GetCustomConverterForMember(Type typeToConvert, MemberInfo memberInfo, JsonSerializerOptions options)
{
Debug.Assert(memberInfo is FieldInfo or PropertyInfo);
Debug.Assert(typeToConvert != null);
{
if (resolvers is null)
{
- throw new ArgumentNullException(nameof(resolvers));
+ ThrowHelper.ThrowArgumentNullException(nameof(resolvers));
}
var resolverChain = new JsonTypeInfoResolverChain();
}
/// <summary>
+ /// Creates a resolver applies modifications to the metadata generated by the source <paramref name="resolver"/>.
+ /// </summary>
+ /// <param name="resolver">The source resolver generating <see cref="JsonTypeInfo"/> metadata.</param>
+ /// <param name="modifier">The delegate modifying non-null <see cref="JsonTypeInfo"/> results.</param>
+ /// <returns>A new <see cref="IJsonTypeInfoResolver"/> instance applying the modifications.</returns>
+ /// <remarks>
+ /// This method is closely related to <see cref="DefaultJsonTypeInfoResolver.Modifiers"/> property
+ /// extended to arbitrary <see cref="IJsonTypeInfoResolver"/> instances.
+ /// </remarks>
+ public static IJsonTypeInfoResolver WithAddedModifier(this IJsonTypeInfoResolver resolver, Action<JsonTypeInfo> modifier)
+ {
+ if (resolver is null)
+ {
+ ThrowHelper.ThrowArgumentNullException(nameof(resolver));
+ }
+ if (modifier is null)
+ {
+ ThrowHelper.ThrowArgumentNullException(nameof(modifier));
+ }
+
+ return resolver is JsonTypeInfoResolverWithAddedModifiers resolverWithModifiers
+ ? resolverWithModifiers.WithAddedModifier(modifier)
+ : new JsonTypeInfoResolverWithAddedModifiers(resolver, new[] { modifier });
+ }
+
+ /// <summary>
+ /// Gets a resolver that returns null <see cref="JsonTypeInfo"/> for every type.
+ /// </summary>
+ internal static IJsonTypeInfoResolver Empty { get; } = new JsonTypeInfoResolverChain();
+
+ /// <summary>
/// Indicates whether the metadata generated by the current resolver
/// are compatible with the run time specified <see cref="JsonSerializerOptions"/>.
/// </summary>
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Diagnostics;
+
+namespace System.Text.Json.Serialization.Metadata
+{
+ internal sealed class JsonTypeInfoResolverWithAddedModifiers : IJsonTypeInfoResolver
+ {
+ private readonly IJsonTypeInfoResolver _source;
+ private readonly Action<JsonTypeInfo>[] _modifiers;
+
+ public JsonTypeInfoResolverWithAddedModifiers(IJsonTypeInfoResolver source, Action<JsonTypeInfo>[] modifiers)
+ {
+ Debug.Assert(modifiers.Length > 0);
+ _source = source;
+ _modifiers = modifiers;
+ }
+
+ public JsonTypeInfoResolverWithAddedModifiers WithAddedModifier(Action<JsonTypeInfo> modifier)
+ {
+ var newModifiers = new Action<JsonTypeInfo>[_modifiers.Length + 1];
+ _modifiers.CopyTo(newModifiers, 0);
+ newModifiers[_modifiers.Length] = modifier;
+
+ return new JsonTypeInfoResolverWithAddedModifiers(_source, newModifiers);
+ }
+
+ public JsonTypeInfo? GetTypeInfo(Type type, JsonSerializerOptions options)
+ {
+ JsonTypeInfo? typeInfo = _source.GetTypeInfo(type, options);
+
+ if (typeInfo != null)
+ {
+ foreach (Action<JsonTypeInfo> modifier in _modifiers)
+ {
+ modifier(typeInfo);
+ }
+ }
+
+ return typeInfo;
+ }
+ }
+}
JsonSerializerOptions defaultOptions = DefaultOptions;
return new JsonSerializerOptions(defaultOptions)
{
- TypeInfoResolver = defaultOptions.TypeInfoResolver.WithModifier(modifier)
+ TypeInfoResolver = defaultOptions.TypeInfoResolver.WithAddedModifier(modifier)
};
}
if (modifier != null && options.TypeInfoResolver != null)
{
- options.TypeInfoResolver = DefaultOptions.TypeInfoResolver.WithModifier(modifier);
+ options.TypeInfoResolver = DefaultOptions.TypeInfoResolver.WithAddedModifier(modifier);
}
if (customConverters != null)
return options;
}
}
-
- public static class JsonTypeInfoResolverExtensions
- {
- public static IJsonTypeInfoResolver WithModifier(this IJsonTypeInfoResolver resolver, Action<JsonTypeInfo> modifier)
- => new JsonTypeInfoResolverWithModifier(resolver, modifier);
-
- private class JsonTypeInfoResolverWithModifier : IJsonTypeInfoResolver
- {
- private readonly IJsonTypeInfoResolver _source;
- private readonly Action<JsonTypeInfo> _modifier;
-
- public JsonTypeInfoResolverWithModifier(IJsonTypeInfoResolver source, Action<JsonTypeInfo> modifier)
- {
- _source = source;
- _modifier = modifier;
- }
-
- public JsonTypeInfo? GetTypeInfo(Type type, JsonSerializerOptions options)
- {
- JsonTypeInfo? typeInfo = _source.GetTypeInfo(type, options);
-
- if (typeInfo != null)
- {
- _modifier(typeInfo);
- }
-
- return typeInfo;
- }
- }
- }
}
{
public class DefaultJsonPropertyInfoTests_DefaultJsonTypeInfoResolver : DefaultJsonPropertyInfoTests
{
- protected override IJsonTypeInfoResolver CreateResolverWithModifiers(params Action<JsonTypeInfo>[] modifiers)
- {
- var resolver = new DefaultJsonTypeInfoResolver();
-
- foreach (var modifier in modifiers)
- {
- resolver.Modifiers.Add(modifier);
- }
-
- return resolver;
- }
+ protected override IJsonTypeInfoResolver Resolver { get; } = new DefaultJsonTypeInfoResolver();
}
public class DefaultJsonPropertyInfoTests_SerializerContextNoWrapping : DefaultJsonPropertyInfoTests
{
- protected override bool ModifiersNotSupported => true;
-
- protected override IJsonTypeInfoResolver CreateResolverWithModifiers(params Action<JsonTypeInfo>[] modifiers)
- {
- if (modifiers.Length != 0)
- {
- Assert.Fail($"Testing non wrapped JsonSerializerContext but modifier is provided. Make sure to check {nameof(ModifiersNotSupported)}.");
- }
-
- return Context.Default;
- }
+ protected override IJsonTypeInfoResolver Resolver { get; } = Context.Default;
}
public class DefaultJsonPropertyInfoTests_SerializerContextWrapped : DefaultJsonPropertyInfoTests
{
- protected override IJsonTypeInfoResolver CreateResolverWithModifiers(params Action<JsonTypeInfo>[] modifiers)
- => new ContextWithModifiers(Context.Default, modifiers);
-
- private class ContextWithModifiers : IJsonTypeInfoResolver
- {
- private IJsonTypeInfoResolver _context;
- private Action<JsonTypeInfo>[] _modifiers;
-
- public ContextWithModifiers(JsonSerializerContext context, Action<JsonTypeInfo>[] modifiers)
- {
- _context = context;
- _modifiers = modifiers;
- }
-
- public JsonTypeInfo? GetTypeInfo(Type type, JsonSerializerOptions options)
- {
- JsonTypeInfo? typeInfo = _context.GetTypeInfo(type, options);
-
- if (typeInfo != null)
- {
- foreach (var modifier in _modifiers)
- {
- modifier(typeInfo);
- }
- }
-
- return typeInfo;
- }
- }
+ protected override IJsonTypeInfoResolver Resolver { get; } = Context.Default;
}
public abstract partial class DefaultJsonPropertyInfoTests
{
- protected virtual bool ModifiersNotSupported => false;
- protected abstract IJsonTypeInfoResolver CreateResolverWithModifiers(params Action<JsonTypeInfo>[] modifiers);
+ protected abstract IJsonTypeInfoResolver Resolver { get; }
- private JsonSerializerOptions CreateOptionsWithModifiers(params Action<JsonTypeInfo>[] modifiers)
- => new JsonSerializerOptions()
+ private JsonSerializerOptions CreateOptionsWithModifier(Action<JsonTypeInfo> modifier)
+ => new JsonSerializerOptions
{
- TypeInfoResolver = CreateResolverWithModifiers(modifiers)
+ TypeInfoResolver = Resolver.WithAddedModifier(modifier)
};
- private JsonSerializerOptions CreateOptions() => CreateOptionsWithModifiers();
+ private JsonSerializerOptions CreateOptions() => new JsonSerializerOptions { TypeInfoResolver = Resolver };
[Fact]
public void RequiredAttributesGetDetectedAndFailDeserializationWhenValuesNotPresent()
[Fact]
public void RequiredMemberCanBeModifiedToNonRequired()
{
- if (ModifiersNotSupported)
- return;
-
- JsonSerializerOptions options = CreateOptionsWithModifiers(ti =>
+ JsonSerializerOptions options = CreateOptionsWithModifier(ti =>
{
if (ti.Type == typeof(ClassWithRequiredCustomAttributes))
{
[Fact]
public void NonRequiredMemberCanBeModifiedToRequired()
{
- if (ModifiersNotSupported)
- return;
-
- JsonSerializerOptions options = CreateOptionsWithModifiers(ti =>
+ JsonSerializerOptions options = CreateOptionsWithModifier(ti =>
{
if (ti.Type == typeof(ClassWithRequiredCustomAttributes))
{
[Fact]
public void RequiredExtensionDataPropertyCanBeFixedToNotBeRequiredWithResolver()
{
- if (ModifiersNotSupported)
- return;
-
- JsonSerializerOptions options = CreateOptionsWithModifiers(ti =>
+ JsonSerializerOptions options = CreateOptionsWithModifier(ti =>
{
if (ti.Type == typeof(ClassWithRequiredCustomAttributeAndDataExtensionProperty))
{
[Fact]
public void RequiredExtensionDataPropertyCanBeFixedToNotBeExtensionDataWithResolver()
{
- if (ModifiersNotSupported)
- return;
-
- JsonSerializerOptions options = CreateOptionsWithModifiers(ti =>
+ JsonSerializerOptions options = CreateOptionsWithModifier(ti =>
{
if (ti.Type == typeof(ClassWithRequiredCustomAttributeAndDataExtensionProperty))
{
[Fact]
public void RequiredReadOnlyPropertyCanBeFixedToNotBeRequiredWithResolver()
{
- if (ModifiersNotSupported)
- return;
-
- JsonSerializerOptions options = CreateOptionsWithModifiers(ti =>
+ JsonSerializerOptions options = CreateOptionsWithModifier(ti =>
{
if (ti.Type == typeof(ClassWithRequiredCustomAttributeAndReadOnlyProperty))
{
[Fact]
public void RequiredReadOnlyPropertyCanBeFixedToBeWritableWithResolver()
{
- if (ModifiersNotSupported)
- return;
-
- JsonSerializerOptions options = CreateOptionsWithModifiers(ti =>
+ JsonSerializerOptions options = CreateOptionsWithModifier(ti =>
{
if (ti.Type == typeof(ClassWithRequiredCustomAttributeAndReadOnlyProperty))
{
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-using System;
using System.Collections.Generic;
-using System.Reflection;
-using System.Text;
using System.Text.Json.Serialization.Metadata;
-using System.Threading.Tasks;
using Xunit;
namespace System.Text.Json.Serialization.Tests
}
[Fact]
+ public static void WithAddedModifier_CallsModifierOnResolvedMetadata()
+ {
+ int modifierInvocationCount = 0;
+ JsonSerializerOptions options = new();
+ TestResolver resolver = new(JsonTypeInfo.CreateJsonTypeInfo);
+
+ IJsonTypeInfoResolver resolverWithModifier = resolver.WithAddedModifier(_ => modifierInvocationCount++);
+
+ Assert.NotNull(resolverWithModifier.GetTypeInfo(typeof(int), options));
+ Assert.Equal(1, modifierInvocationCount);
+
+ Assert.NotNull(resolverWithModifier.GetTypeInfo(typeof(string), options));
+ Assert.Equal(2, modifierInvocationCount);
+
+ Assert.NotNull(resolverWithModifier.GetTypeInfo(typeof(int), options));
+ Assert.Equal(3, modifierInvocationCount);
+ }
+
+ [Fact]
+ public static void WithAddedModifier_DoesNotCallModifierOnUnResolvedMetadata()
+ {
+ int modifierInvocationCount = 0;
+ JsonSerializerOptions options = new();
+ TestResolver resolver = new((_,_) => null);
+
+ IJsonTypeInfoResolver resolverWithModifier = resolver.WithAddedModifier(_ => modifierInvocationCount++);
+
+ Assert.Null(resolverWithModifier.GetTypeInfo(typeof(int), options));
+ Assert.Equal(0, modifierInvocationCount);
+
+ Assert.Null(resolverWithModifier.GetTypeInfo(typeof(string), options));
+ Assert.Equal(0, modifierInvocationCount);
+ }
+
+ [Fact]
+ public static void WithAddedModifier_CanChainMultipleModifiers()
+ {
+ int modifier1InvocationCount = 0;
+ int modifier2InvocationCount = 0;
+ JsonSerializerOptions options = new();
+ TestResolver resolver = new(JsonTypeInfo.CreateJsonTypeInfo);
+
+ IJsonTypeInfoResolver resolverWithModifier = resolver
+ .WithAddedModifier(_ => modifier1InvocationCount++)
+ .WithAddedModifier(_ => Assert.Equal(modifier1InvocationCount, ++modifier2InvocationCount)); // Validates order of modifier evaluation.
+
+ Assert.NotNull(resolverWithModifier.GetTypeInfo(typeof(int), options));
+ Assert.Equal(1, modifier1InvocationCount);
+ Assert.Equal(1, modifier2InvocationCount);
+ }
+
+ [Fact]
+ public static void WithAddedModifier_ChainingDoesNotMutateIntermediateResolvers()
+ {
+ int modifier1InvocationCount = 0;
+ int modifier2InvocationCount = 0;
+ JsonSerializerOptions options = new();
+ TestResolver resolver = new(JsonTypeInfo.CreateJsonTypeInfo);
+
+ IJsonTypeInfoResolver resolverWithModifier = resolver
+ .WithAddedModifier(_ => modifier1InvocationCount++);
+
+ IJsonTypeInfoResolver resolverWithChainedModifier = resolverWithModifier
+ .WithAddedModifier(_ => Assert.Equal(modifier1InvocationCount, ++modifier2InvocationCount)); // Validates order of modifier evaluation.
+
+ Assert.NotSame(resolverWithModifier, resolverWithChainedModifier);
+
+ Assert.NotNull(resolverWithChainedModifier.GetTypeInfo(typeof(int), options));
+ Assert.Equal(1, modifier1InvocationCount);
+ Assert.Equal(1, modifier2InvocationCount);
+
+ Assert.NotNull(resolverWithModifier.GetTypeInfo(typeof(int), options));
+ Assert.Equal(2, modifier1InvocationCount);
+ Assert.Equal(1, modifier2InvocationCount);
+ }
+
+ [Fact]
+ public static void WithAddedModifier_ThrowsOnNullArguments()
+ {
+ TestResolver resolver = new(JsonTypeInfo.CreateJsonTypeInfo);
+
+ Assert.Throws<ArgumentNullException>(() => ((IJsonTypeInfoResolver)null!).WithAddedModifier(_ => { }));
+ Assert.Throws<ArgumentNullException>(() => resolver.WithAddedModifier(null));
+ }
+
+ [Fact]
public static void NullResolver_ReturnsObjectMetadata()
{
var options = new JsonSerializerOptions();