Closed
Description
Continuation of #63686 - some API additions we wanted to make in this release.
API changes
namespace System.Text.Json.Serialization.Metadata;
public class JsonTypeInfo
{
// Maps to IJsonOnSerializing
public Action<object>? OnSerializing { get; set; } = null;
// Maps to IJsonOnSerialized
public Action<object>? OnSerialized { get; set; } = null;
// Maps to IJsonOnDeserializing
public Action<object>? OnDeserializing { get; set; } = null;
// Maps to IJsonOnDeserialized
public Action<object>? OnDeserialized { get; set; } = null;
}
public abstract partial class JsonPropertyInfo
{
// Abstracts backing member, DefaultJsonTypeInfoResolver will return instance of PropertyInfo/FieldInfo
public System.Reflection.ICustomAttributeProvider? AttributeProvider { get; set; } = null;
// Maps to JsonPropertyOrderAttribute
public int Order { get; set; } = 0;
// Maps to JsonExtensionDataAttribute
// Alternatively this could be a nullable JsonPropertyInfo on JsonTypeInfo.
public bool IsExtensionData { get; set; } = false;
}
[EditorBrowsable(EditorBrowsableState.Never)]
public static partial class JsonMetadataServices
{
public static JsonConverter<T?> GetNullableConverter<T>(JsonSerializerOptions options) where T : struct { throw null; }
}
namespace System.Text.Json;
public partial class JsonSerializerOptions
{
// Existing GetConverter API
public JsonConverter GetConverter(Type type);
// Gets metadata that has been cached by this options instance
// Can be used to plug metadata directly into JsonSerializer overloads that accept JsonTypeInfo<T>
public JsonTypeInfo GetTypeInfo(Type type);
}
Sample
public static void DataContractAttributeModifier(JsonTypeInfo jsonTypeInfo)
{
if (jsonTypeInfo.Kind == JsonTypeInfoKind.Object &&
type.GetCustomAttribute<DataContractAttribute>() is not null)
{
foreach (JsonPropertyInfo propertyInfo in jsonTypeInfo.Properties)
{
DataMemberAttribute? dataMemberAttribute = (DataMemberAttribute?)propertyInfo.AttributeProvider.GetCustomAttributes(typeof(DataMemberAttribute), inherit: false).FirstOrDefault()
if (dataMemberAttribute is not null)
{
propertyInfo.Name = dataMemberAttribute.Name;
propertyInfo.Order = dataMemberAttribute.Order;
}
}
}
}
Remaining work
- Remaining comments in JSON contract customization #70435
- Adjust polymorphism APIs to match approved API shape
- check trimmed app size before and after contract customization changes
- JsonTypeInfo instances should never encapsulate mutable JsonSerializerOptions instances.
- Investigate all logic consuming the
JsonSerializerOptions.SerializerContext
property as combined resolvers might invalidate their intentions. - More JsonSerializeContext + JsonTypeInfoResolver.Combine tests
- More functional tests, review issues and implement scenarios
- Re-iterate on the behavior in
StaticInitialization_DeserializationWithJsonTypeInfoWithoutSettingTypeInfoResolverThrows
andStaticInitialization_SerializationWithJsonTypeInfoWithoutSettingTypeInfoResolverThrows
- once everything above is done ensure no regressions are made in performance suite (we've done checkpoint run already for JSON contract customization #70435 and no regressions)
- XML docs in public APIs need improvement, e.g.
<summary>
sections should be one-liners, should use<see />
elements where applicable, should use<remarks>
sections where applicable, etc. Wording seems to deviate with how we document elsewhere. - Regressions in System.Text.Json.Serialization.Tests.ColdStartSerialization<SimpleStructWithProperties> #71392
- Unify options/sourcegen root-level serialization helpers to use
JsonTypeInfo<T>
. - TODOs in the code/tests
- Tests with RemoteExecute, make sure static initialization works correctly
- Examine nullability/linkability of JsonSerializerOptions.TypeInfoResolver
- Walk through all existing APIs and double check they will cooperate well with the design