diff --git a/src/libraries/System.Text.Json/Common/JsonHelpers.cs b/src/libraries/System.Text.Json/Common/JsonHelpers.cs index 96a2872621f40..c2fede2dd6d43 100644 --- a/src/libraries/System.Text.Json/Common/JsonHelpers.cs +++ b/src/libraries/System.Text.Json/Common/JsonHelpers.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Runtime.InteropServices; using System.Collections.Generic; namespace System.Text.Json @@ -22,6 +23,42 @@ public static bool TryAdd(this Dictionary dictionary return false; #else return dictionary.TryAdd(key, value); +#endif + } + + /// + /// Provides an in-place, stable sorting implementation for List. + /// + internal static void StableSortByKey(this List items, Func keySelector) + where TKey : unmanaged, IComparable + { +#if NET6_0_OR_GREATER + Span span = CollectionsMarshal.AsSpan(items); + + // Tuples implement lexical ordering OOTB which can be used to encode stable sorting + // using the actual key as the first element and index as the second element. + const int StackallocThreshold = 32; + Span<(TKey, int)> keys = span.Length <= StackallocThreshold + ? (stackalloc (TKey, int)[StackallocThreshold]).Slice(0, span.Length) + : new (TKey, int)[span.Length]; + + for (int i = 0; i < keys.Length; i++) + { + keys[i] = (keySelector(span[i]), i); + } + + MemoryExtensions.Sort(keys, span); +#else + T[] arrayCopy = items.ToArray(); + (TKey, int)[] keys = new (TKey, int)[arrayCopy.Length]; + for (int i = 0; i < keys.Length; i++) + { + keys[i] = (keySelector(arrayCopy[i]), i); + } + + Array.Sort(keys, arrayCopy); + items.Clear(); + items.AddRange(arrayCopy); #endif } } diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs index ce9ed21677861..bd5686edbb954 100644 --- a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs +++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs @@ -4,7 +4,6 @@ using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; -using System.Collections.Immutable; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; @@ -16,7 +15,6 @@ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.DotnetRuntime.Extensions; -using Microsoft.CodeAnalysis.Text; namespace System.Text.Json.SourceGeneration { diff --git a/src/libraries/System.Text.Json/src/Resources/Strings.resx b/src/libraries/System.Text.Json/src/Resources/Strings.resx index c951a42f10b11..70d01c2a50d59 100644 --- a/src/libraries/System.Text.Json/src/Resources/Strings.resx +++ b/src/libraries/System.Text.Json/src/Resources/Strings.resx @@ -668,4 +668,7 @@ Parameter already associated with a different JsonTypeInfo instance. + + Serialization callbacks are not supported in metadata kind '{0}'. + diff --git a/src/libraries/System.Text.Json/src/System.Text.Json.csproj b/src/libraries/System.Text.Json/src/System.Text.Json.csproj index ba9de5afc0979..fbf37e1fd5eab 100644 --- a/src/libraries/System.Text.Json/src/System.Text.Json.csproj +++ b/src/libraries/System.Text.Json/src/System.Text.Json.csproj @@ -364,6 +364,7 @@ The System.Text.Json library is built-in as part of the shared framework in .NET + diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/JsonPropertyDictionary.KeyCollection.cs b/src/libraries/System.Text.Json/src/System/Text/Json/JsonPropertyDictionary.KeyCollection.cs index 997ee59563900..ac74da021b8bc 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/JsonPropertyDictionary.KeyCollection.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/JsonPropertyDictionary.KeyCollection.cs @@ -30,7 +30,7 @@ public KeyCollection(JsonPropertyDictionary jsonObject) IEnumerator IEnumerable.GetEnumerator() { - foreach (KeyValuePair item in _parent) + foreach (KeyValuePair item in _parent) { yield return item.Key; } @@ -49,7 +49,7 @@ public void CopyTo(string[] propertyNameArray, int index) ThrowHelper.ThrowArgumentOutOfRangeException_ArrayIndexNegative(nameof(index)); } - foreach (KeyValuePair item in _parent) + foreach (KeyValuePair item in _parent) { if (index >= propertyNameArray.Length) { @@ -62,7 +62,7 @@ public void CopyTo(string[] propertyNameArray, int index) public IEnumerator GetEnumerator() { - foreach (KeyValuePair item in _parent) + foreach (KeyValuePair item in _parent) { yield return item.Key; } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/JsonPropertyDictionary.ValueCollection.cs b/src/libraries/System.Text.Json/src/System/Text/Json/JsonPropertyDictionary.ValueCollection.cs index c81a67cd29dbf..58785de5c1ae1 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/JsonPropertyDictionary.ValueCollection.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/JsonPropertyDictionary.ValueCollection.cs @@ -10,12 +10,12 @@ internal sealed partial class JsonPropertyDictionary { private ValueCollection? _valueCollection; - public ICollection GetValueCollection() + public ICollection GetValueCollection() { return _valueCollection ??= new ValueCollection(this); } - private sealed class ValueCollection : ICollection + private sealed class ValueCollection : ICollection { private readonly JsonPropertyDictionary _parent; @@ -30,26 +30,26 @@ public ValueCollection(JsonPropertyDictionary jsonObject) IEnumerator IEnumerable.GetEnumerator() { - foreach (KeyValuePair item in _parent) + foreach (KeyValuePair item in _parent) { yield return item.Value; } } - public void Add(T? jsonNode) => ThrowHelper.ThrowNotSupportedException_CollectionIsReadOnly(); + public void Add(T jsonNode) => ThrowHelper.ThrowNotSupportedException_CollectionIsReadOnly(); public void Clear() => ThrowHelper.ThrowNotSupportedException_CollectionIsReadOnly(); - public bool Contains(T? jsonNode) => _parent.ContainsValue(jsonNode); + public bool Contains(T jsonNode) => _parent.ContainsValue(jsonNode); - public void CopyTo(T?[] nodeArray, int index) + public void CopyTo(T[] nodeArray, int index) { if (index < 0) { ThrowHelper.ThrowArgumentOutOfRangeException_ArrayIndexNegative(nameof(index)); } - foreach (KeyValuePair item in _parent) + foreach (KeyValuePair item in _parent) { if (index >= nodeArray.Length) { @@ -60,15 +60,15 @@ public void CopyTo(T?[] nodeArray, int index) } } - public IEnumerator GetEnumerator() + public IEnumerator GetEnumerator() { - foreach (KeyValuePair item in _parent) + foreach (KeyValuePair item in _parent) { yield return item.Value; } } - bool ICollection.Remove(T? node) => throw ThrowHelper.GetNotSupportedException_CollectionIsReadOnly(); + bool ICollection.Remove(T node) => throw ThrowHelper.GetNotSupportedException_CollectionIsReadOnly(); } } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/JsonPropertyDictionary.cs b/src/libraries/System.Text.Json/src/System/Text/Json/JsonPropertyDictionary.cs index 5eeac2cda951d..a6976566438e9 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/JsonPropertyDictionary.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/JsonPropertyDictionary.cs @@ -3,38 +3,39 @@ using System.Collections.Generic; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; namespace System.Text.Json { /// - /// Keeps both a List and Dictionary in sync to enable determinstic enumeration ordering of List + /// Keeps both a List and Dictionary in sync to enable deterministic enumeration ordering of List /// and performance benefits of Dictionary once a threshold is hit. /// - internal sealed partial class JsonPropertyDictionary where T : class + internal sealed partial class JsonPropertyDictionary where T : class? { private const int ListToDictionaryThreshold = 9; - private Dictionary? _propertyDictionary; - private readonly List> _propertyList; + private Dictionary? _propertyDictionary; + private readonly List> _propertyList; - private StringComparer _stringComparer; + private readonly StringComparer _stringComparer; public JsonPropertyDictionary(bool caseInsensitive) { _stringComparer = caseInsensitive ? StringComparer.OrdinalIgnoreCase : StringComparer.Ordinal; - _propertyList = new List>(); + _propertyList = new List>(); } public JsonPropertyDictionary(bool caseInsensitive, int capacity) { _stringComparer = caseInsensitive ? StringComparer.OrdinalIgnoreCase : StringComparer.Ordinal; - _propertyList = new List>(capacity); + _propertyList = new List>(capacity); } // Enable direct access to the List for performance reasons. - public List> List => _propertyList; + public List> List => _propertyList; - public void Add(string propertyName, T? value) + public void Add(string propertyName, T value) { if (IsReadOnly) { @@ -49,7 +50,7 @@ public void Add(string propertyName, T? value) AddValue(propertyName, value); } - public void Add(KeyValuePair property) + public void Add(KeyValuePair property) { if (IsReadOnly) { @@ -116,9 +117,9 @@ public bool Remove(string propertyName) return TryRemoveProperty(propertyName, out _); } - public bool Contains(KeyValuePair item) + public bool Contains(KeyValuePair item) { - foreach (KeyValuePair existing in this) + foreach (KeyValuePair existing in this) { if (ReferenceEquals(item.Value, existing.Value) && _stringComparer.Equals(item.Key, existing.Key)) { @@ -129,14 +130,14 @@ public bool Contains(KeyValuePair item) return false; } - public void CopyTo(KeyValuePair[] array, int index) + public void CopyTo(KeyValuePair[] array, int index) { if (index < 0) { ThrowHelper.ThrowArgumentOutOfRangeException_ArrayIndexNegative(nameof(index)); } - foreach (KeyValuePair item in _propertyList) + foreach (KeyValuePair item in _propertyList) { if (index >= array.Length) { @@ -147,9 +148,9 @@ public void CopyTo(KeyValuePair[] array, int index) } } - public IEnumerator> GetEnumerator() + public IEnumerator> GetEnumerator() { - foreach (KeyValuePair item in _propertyList) + foreach (KeyValuePair item in _propertyList) { yield return item; } @@ -157,9 +158,9 @@ public void CopyTo(KeyValuePair[] array, int index) public ICollection Keys => GetKeyCollection(); - public ICollection Values => GetValueCollection(); + public ICollection Values => GetValueCollection(); - public bool TryGetValue(string propertyName, out T? value) + public bool TryGetValue(string propertyName, [MaybeNullWhen(false)] out T value) { if (propertyName is null) { @@ -172,7 +173,7 @@ public bool TryGetValue(string propertyName, out T? value) } else { - foreach (KeyValuePair item in _propertyList) + foreach (KeyValuePair item in _propertyList) { if (_stringComparer.Equals(propertyName, item.Key)) { @@ -188,6 +189,7 @@ public bool TryGetValue(string propertyName, out T? value) public bool IsReadOnly { get; set; } + [DisallowNull] public T? this[string propertyName] { get @@ -207,7 +209,7 @@ public T? this[string propertyName] } } - public T? SetValue(string propertyName, T? value, Action? assignParent = null) + public T? SetValue(string propertyName, T value, Action? assignParent = null) { if (IsReadOnly) { @@ -229,7 +231,7 @@ public T? this[string propertyName] if (JsonHelpers.TryAdd(_propertyDictionary, propertyName, value)) { assignParent?.Invoke(); - _propertyList.Add(new KeyValuePair(propertyName, value)); + _propertyList.Add(new KeyValuePair(propertyName, value)); return null; } @@ -250,7 +252,7 @@ public T? this[string propertyName] } else { - KeyValuePair current = _propertyList[i]; + KeyValuePair current = _propertyList[i]; if (ReferenceEquals(current.Value, value)) { // Ignore if the same value. @@ -261,20 +263,20 @@ public T? this[string propertyName] } assignParent?.Invoke(); - _propertyList[i] = new KeyValuePair(propertyName, value); + _propertyList[i] = new KeyValuePair(propertyName, value); } else { assignParent?.Invoke(); _propertyDictionary?.Add(propertyName, value); - _propertyList.Add(new KeyValuePair(propertyName, value)); + _propertyList.Add(new KeyValuePair(propertyName, value)); Debug.Assert(existing == null); } return existing; } - private void AddValue(string propertyName, T? value) + private void AddValue(string propertyName, T value) { if (!TryAddValue(propertyName, value)) { @@ -282,7 +284,7 @@ private void AddValue(string propertyName, T? value) } } - internal bool TryAddValue(string propertyName, T? value) + internal bool TryAddValue(string propertyName, T value) { if (IsReadOnly) { @@ -307,7 +309,7 @@ internal bool TryAddValue(string propertyName, T? value) } } - _propertyList.Add(new KeyValuePair(propertyName, value)); + _propertyList.Add(new KeyValuePair(propertyName, value)); return true; } @@ -319,9 +321,9 @@ private void CreateDictionaryIfThresholdMet() } } - internal bool ContainsValue(T? value) + internal bool ContainsValue(T value) { - foreach (T? item in GetValueCollection()) + foreach (T item in GetValueCollection()) { if (ReferenceEquals(item, value)) { @@ -332,9 +334,9 @@ internal bool ContainsValue(T? value) return false; } - public KeyValuePair? FindValue(T? value) + public KeyValuePair? FindValue(T value) { - foreach (KeyValuePair item in this) + foreach (KeyValuePair item in this) { if (ReferenceEquals(item.Value, value)) { @@ -352,7 +354,7 @@ private bool ContainsProperty(string propertyName) return _propertyDictionary.ContainsKey(propertyName); } - foreach (KeyValuePair item in _propertyList) + foreach (KeyValuePair item in _propertyList) { if (_stringComparer.Equals(propertyName, item.Key)) { @@ -367,7 +369,7 @@ private int FindValueIndex(string propertyName) { for (int i = 0; i < _propertyList.Count; i++) { - KeyValuePair current = _propertyList[i]; + KeyValuePair current = _propertyList[i]; if (_stringComparer.Equals(propertyName, current.Key)) { return i; @@ -377,9 +379,9 @@ private int FindValueIndex(string propertyName) return -1; } - public bool TryGetPropertyValue(string propertyName, out T? value) => TryGetValue(propertyName, out value); + public bool TryGetPropertyValue(string propertyName, [MaybeNullWhen(false)] out T value) => TryGetValue(propertyName, out value); - public bool TryRemoveProperty(string propertyName, out T? existing) + public bool TryRemoveProperty(string propertyName, [MaybeNullWhen(false)] out T existing) { if (IsReadOnly) { @@ -399,7 +401,7 @@ public bool TryRemoveProperty(string propertyName, out T? existing) for (int i = 0; i < _propertyList.Count; i++) { - KeyValuePair current = _propertyList[i]; + KeyValuePair current = _propertyList[i]; if (_stringComparer.Equals(current.Key, propertyName)) { diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/JsonPropertyInfoDictionaryValueList.cs b/src/libraries/System.Text.Json/src/System/Text/Json/JsonPropertyInfoDictionaryValueList.cs index 5640a9baef555..a700581faad9b 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/JsonPropertyInfoDictionaryValueList.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/JsonPropertyInfoDictionaryValueList.cs @@ -13,7 +13,7 @@ internal sealed class JsonPropertyInfoDictionaryValueList : IList _parent; private List? _items; - private JsonTypeInfo _parentTypeInfo; + private readonly JsonTypeInfo _parentTypeInfo; [MemberNotNullWhen(false, nameof(_items))] public bool IsReadOnly => _items == null; @@ -21,7 +21,7 @@ internal sealed class JsonPropertyInfoDictionaryValueList : IList IsReadOnly ? _parent.List[index].Value! : _items[index]; + get => IsReadOnly ? _parent.List[index].Value : _items[index]; set { if (IsReadOnly) @@ -47,10 +47,8 @@ public JsonPropertyInfoDictionaryValueList(JsonPropertyDictionary(_parent.Count); - foreach (var kv in _parent.List) + foreach (KeyValuePair kv in _parent.List) { - Debug.Assert(kv.Value != null, $"{nameof(JsonPropertyDictionary)} contains null value"); - // we need to do this so that property cannot be copied over elsewhere // since source gen properties do not have parents by default kv.Value.EnsureChildOf(parentTypeInfo); @@ -65,8 +63,10 @@ public void FinishEditingAndMakeReadOnly(Type parentType) // We do not know if any of the keys needs to be updated therefore we need to re-create cache _parent.Clear(); + // Perform a final sort of properties before rebuilding the cache + _items.StableSortByKey(static prop => prop.Order); - foreach (var item in _items) + foreach (JsonPropertyInfo item in _items) { string key = item.Name; if (!_parent.TryAddValue(key, item)) @@ -110,14 +110,14 @@ public void CopyTo(JsonPropertyInfo[] array, int index) if (IsReadOnly) { - foreach (KeyValuePair item in _parent) + foreach (KeyValuePair item in _parent) { if (index >= array.Length) { ThrowHelper.ThrowArgumentException_ArrayTooSmall(nameof(array)); } - array[index++] = item.Value!; + array[index++] = item.Value; } } else @@ -131,7 +131,7 @@ public int IndexOf(JsonPropertyInfo item) if (IsReadOnly) { int index = 0; - foreach (var kv in _parent.List) + foreach (KeyValuePair kv in _parent.List) { if (kv.Value == item) { @@ -181,9 +181,9 @@ public IEnumerator GetEnumerator() { if (IsReadOnly) { - foreach (KeyValuePair item in _parent) + foreach (KeyValuePair item in _parent) { - yield return item.Value!; + yield return item.Value; } } else diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonObject.IDictionary.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonObject.IDictionary.cs index 131389b4d1e14..bf843dac3788a 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonObject.IDictionary.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonObject.IDictionary.cs @@ -10,7 +10,7 @@ namespace System.Text.Json.Nodes { public partial class JsonObject : IDictionary { - private JsonPropertyDictionary? _dictionary; + private JsonPropertyDictionary? _dictionary; /// /// Adds an element with the provided property name and value to the . @@ -267,7 +267,7 @@ private void InitializeIfRequired() } bool caseInsensitive = Options.HasValue ? Options.Value.PropertyNameCaseInsensitive : false; - var dictionary = new JsonPropertyDictionary(caseInsensitive); + var dictionary = new JsonPropertyDictionary(caseInsensitive); if (_jsonElement.HasValue) { JsonElement jElement = _jsonElement.Value; diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/ObjectDefaultConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/ObjectDefaultConverter.cs index 19a14dac9980f..9871d76f83d5b 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/ObjectDefaultConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/ObjectDefaultConverter.cs @@ -38,10 +38,7 @@ internal override bool OnTryRead(ref Utf8JsonReader reader, Type typeToConvert, obj = jsonTypeInfo.CreateObject()!; - if (obj is IJsonOnDeserializing onDeserializing) - { - onDeserializing.OnDeserializing(); - } + jsonTypeInfo.OnDeserializing?.Invoke(obj); // Process all properties. while (true) @@ -142,10 +139,7 @@ internal override bool OnTryRead(ref Utf8JsonReader reader, Type typeToConvert, state.ReferenceId = null; } - if (obj is IJsonOnDeserializing onDeserializing) - { - onDeserializing.OnDeserializing(); - } + jsonTypeInfo.OnDeserializing?.Invoke(obj); state.Current.ReturnValue = obj; state.Current.ObjectState = StackFrameObjectState.CreatedObject; @@ -255,10 +249,7 @@ internal override bool OnTryRead(ref Utf8JsonReader reader, Type typeToConvert, } } - if (obj is IJsonOnDeserialized onDeserialized) - { - onDeserialized.OnDeserialized(); - } + jsonTypeInfo.OnDeserialized?.Invoke(obj); // Unbox Debug.Assert(obj != null); @@ -292,15 +283,12 @@ internal sealed override bool OnTryWrite( JsonSerializer.WriteMetadataForObject(this, ref state, writer); } - if (obj is IJsonOnSerializing onSerializing) - { - onSerializing.OnSerializing(); - } + jsonTypeInfo.OnSerializing?.Invoke(obj); - List> properties = jsonTypeInfo.PropertyCache!.List; + List> properties = jsonTypeInfo.PropertyCache!.List; for (int i = 0; i < properties.Count; i++) { - JsonPropertyInfo jsonPropertyInfo = properties[i].Value!; + JsonPropertyInfo jsonPropertyInfo = properties[i].Value; if (jsonPropertyInfo.CanSerialize) { // Remember the current property for JsonPath support if an exception is thrown. @@ -316,14 +304,14 @@ internal sealed override bool OnTryWrite( } // Write extension data after the normal properties. - JsonPropertyInfo? dataExtensionProperty = jsonTypeInfo.DataExtensionProperty; - if (dataExtensionProperty?.CanSerialize == true) + JsonPropertyInfo? extensionDataProperty = jsonTypeInfo.ExtensionDataProperty; + if (extensionDataProperty?.CanSerialize == true) { // Remember the current property for JsonPath support if an exception is thrown. - state.Current.JsonPropertyInfo = dataExtensionProperty; - state.Current.NumberHandling = dataExtensionProperty.EffectiveNumberHandling; + state.Current.JsonPropertyInfo = extensionDataProperty; + state.Current.NumberHandling = extensionDataProperty.EffectiveNumberHandling; - bool success = dataExtensionProperty.GetMemberAndWriteJsonExtensionData(obj, ref state, writer); + bool success = extensionDataProperty.GetMemberAndWriteJsonExtensionData(obj, ref state, writer); Debug.Assert(success); state.Current.EndProperty(); @@ -342,19 +330,15 @@ internal sealed override bool OnTryWrite( JsonSerializer.WriteMetadataForObject(this, ref state, writer); } - if (obj is IJsonOnSerializing onSerializing) - { - onSerializing.OnSerializing(); - } + jsonTypeInfo.OnSerializing?.Invoke(obj); state.Current.ProcessedStartToken = true; } - List>? propertyList = jsonTypeInfo.PropertyCache!.List!; + List> propertyList = jsonTypeInfo.PropertyCache!.List; while (state.Current.EnumeratorIndex < propertyList.Count) { - JsonPropertyInfo? jsonPropertyInfo = propertyList![state.Current.EnumeratorIndex].Value; - Debug.Assert(jsonPropertyInfo != null); + JsonPropertyInfo jsonPropertyInfo = propertyList[state.Current.EnumeratorIndex].Value; if (jsonPropertyInfo.CanSerialize) { state.Current.JsonPropertyInfo = jsonPropertyInfo; @@ -383,14 +367,14 @@ internal sealed override bool OnTryWrite( // Write extension data after the normal properties. if (state.Current.EnumeratorIndex == propertyList.Count) { - JsonPropertyInfo? dataExtensionProperty = jsonTypeInfo.DataExtensionProperty; - if (dataExtensionProperty?.CanSerialize == true) + JsonPropertyInfo? extensionDataProperty = jsonTypeInfo.ExtensionDataProperty; + if (extensionDataProperty?.CanSerialize == true) { // Remember the current property for JsonPath support if an exception is thrown. - state.Current.JsonPropertyInfo = dataExtensionProperty; - state.Current.NumberHandling = dataExtensionProperty.EffectiveNumberHandling; + state.Current.JsonPropertyInfo = extensionDataProperty; + state.Current.NumberHandling = extensionDataProperty.EffectiveNumberHandling; - if (!dataExtensionProperty.GetMemberAndWriteJsonExtensionData(obj, ref state, writer)) + if (!extensionDataProperty.GetMemberAndWriteJsonExtensionData(obj, ref state, writer)) { return false; } @@ -416,10 +400,7 @@ internal sealed override bool OnTryWrite( } } - if (obj is IJsonOnSerialized onSerialized) - { - onSerialized.OnSerialized(); - } + jsonTypeInfo.OnSerialized?.Invoke(obj); return true; } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/ObjectWithParameterizedConstructorConverter.Large.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/ObjectWithParameterizedConstructorConverter.Large.cs index e9160133e4800..ba190f1bf1c1b 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/ObjectWithParameterizedConstructorConverter.Large.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/ObjectWithParameterizedConstructorConverter.Large.cs @@ -54,13 +54,12 @@ protected sealed override void InitializeConstructorArgumentCaches(ref ReadStack Debug.Assert(typeInfo.ParameterCache != null); - List> cache = typeInfo.ParameterCache.List; + List> cache = typeInfo.ParameterCache.List; object?[] arguments = ArrayPool.Shared.Rent(cache.Count); for (int i = 0; i < typeInfo.ParameterCount; i++) { - JsonParameterInfo? parameterInfo = cache[i].Value; - Debug.Assert(parameterInfo != null); + JsonParameterInfo parameterInfo = cache[i].Value; arguments[parameterInfo.ClrInfo.Position] = parameterInfo.DefaultValue; } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/ObjectWithParameterizedConstructorConverter.Small.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/ObjectWithParameterizedConstructorConverter.Small.cs index 9c36490f4adfb..a16958ec19be2 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/ObjectWithParameterizedConstructorConverter.Small.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/ObjectWithParameterizedConstructorConverter.Small.cs @@ -34,16 +34,16 @@ protected override bool ReadAndCacheConstructorArgument( switch (jsonParameterInfo.ClrInfo.Position) { case 0: - success = TryRead(ref state, ref reader, jsonParameterInfo, out arguments.Arg0); + success = TryRead(ref state, ref reader, jsonParameterInfo, out arguments.Arg0); break; case 1: - success = TryRead(ref state, ref reader, jsonParameterInfo, out arguments.Arg1); + success = TryRead(ref state, ref reader, jsonParameterInfo, out arguments.Arg1); break; case 2: - success = TryRead(ref state, ref reader, jsonParameterInfo, out arguments.Arg2); + success = TryRead(ref state, ref reader, jsonParameterInfo, out arguments.Arg2); break; case 3: - success = TryRead(ref state, ref reader, jsonParameterInfo, out arguments.Arg3); + success = TryRead(ref state, ref reader, jsonParameterInfo, out arguments.Arg3); break; default: Debug.Fail("More than 4 params: we should be in override for LargeObjectWithParameterizedConstructorConverter."); @@ -86,11 +86,10 @@ protected override void InitializeConstructorArgumentCaches(ref ReadStack state, var arguments = new Arguments(); - List> cache = typeInfo.ParameterCache!.List; + List> cache = typeInfo.ParameterCache!.List; for (int i = 0; i < typeInfo.ParameterCount; i++) { - JsonParameterInfo? parameterInfo = cache[i].Value; - Debug.Assert(parameterInfo != null); + JsonParameterInfo parameterInfo = cache[i].Value; // We can afford not to set default values for ctor arguments when we should't deserialize because the // type parameters of the `Arguments` type provide default semantics that work well with value types. diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/ObjectWithParameterizedConstructorConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/ObjectWithParameterizedConstructorConverter.cs index 9440b5375f1d8..4fa80afa6c806 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/ObjectWithParameterizedConstructorConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/ObjectWithParameterizedConstructorConverter.cs @@ -49,10 +49,7 @@ internal sealed override bool OnTryRead(ref Utf8JsonReader reader, Type typeToCo obj = (T)CreateObject(ref state.Current); - if (obj is IJsonOnDeserializing onDeserializing) - { - onDeserializing.OnDeserializing(); - } + jsonTypeInfo.OnDeserializing?.Invoke(obj); if (argumentState.FoundPropertyCount > 0) { @@ -83,7 +80,7 @@ internal sealed override bool OnTryRead(ref Utf8JsonReader reader, Type typeToCo if (useExtensionProperty) { - Debug.Assert(jsonPropertyInfo == state.Current.JsonTypeInfo.DataExtensionProperty); + Debug.Assert(jsonPropertyInfo == state.Current.JsonTypeInfo.ExtensionDataProperty); state.Current.JsonPropertyNameAsString = dataExtKey; JsonSerializer.CreateDataExtensionProperty(obj, jsonPropertyInfo, options); } @@ -175,10 +172,7 @@ internal sealed override bool OnTryRead(ref Utf8JsonReader reader, Type typeToCo state.ReferenceId = null; } - if (obj is IJsonOnDeserializing onDeserializing) - { - onDeserializing.OnDeserializing(); - } + jsonTypeInfo.OnDeserializing?.Invoke(obj); if (argumentState.FoundPropertyCount > 0) { @@ -194,7 +188,7 @@ internal sealed override bool OnTryRead(ref Utf8JsonReader reader, Type typeToCo } else { - Debug.Assert(jsonPropertyInfo == state.Current.JsonTypeInfo.DataExtensionProperty); + Debug.Assert(jsonPropertyInfo == state.Current.JsonTypeInfo.ExtensionDataProperty); JsonSerializer.CreateDataExtensionProperty(obj, jsonPropertyInfo, options); object extDictionary = jsonPropertyInfo.GetValueAsObject(obj)!; @@ -216,10 +210,7 @@ internal sealed override bool OnTryRead(ref Utf8JsonReader reader, Type typeToCo } } - if (obj is IJsonOnDeserialized onDeserialized) - { - onDeserialized.OnDeserialized(); - } + jsonTypeInfo.OnDeserialized?.Invoke(obj); // Unbox Debug.Assert(obj != null); diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandlePropertyName.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandlePropertyName.cs index f51a2cac4bbc3..8d134130184c9 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandlePropertyName.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandlePropertyName.cs @@ -48,7 +48,7 @@ internal static JsonPropertyInfo LookupProperty( // Determine if we should use the extension property. if (jsonPropertyInfo == JsonPropertyInfo.s_missingProperty) { - JsonPropertyInfo? dataExtProperty = state.Current.JsonTypeInfo.DataExtensionProperty; + JsonPropertyInfo? dataExtProperty = state.Current.JsonTypeInfo.ExtensionDataProperty; if (dataExtProperty != null && dataExtProperty.HasGetter && dataExtProperty.HasSetter) { state.Current.JsonPropertyNameAsString = JsonHelpers.Utf8GetString(unescapedPropertyName); diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonPropertyInfo.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonPropertyInfo.cs index ae19e452200a4..c9a77e5db5a89 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonPropertyInfo.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonPropertyInfo.cs @@ -36,14 +36,14 @@ public abstract class JsonPropertyInfo internal abstract JsonConverter EffectiveConverter { get; set; } /// - /// Custom converter override at the property level, equivalent to JsonConverterAttribute annotation + /// Custom converter override at the property level, equivalent to annotation. /// public JsonConverter? CustomConverter { get => _customConverter; set { - CheckMutable(); + VerifyMutable(); _customConverter = value; } } @@ -51,8 +51,11 @@ public JsonConverter? CustomConverter private JsonConverter? _customConverter; /// - /// Getter delegate. Property cannot be serialized without it. + /// Gets or sets a getter delegate for the property. /// + /// + /// Setting to will result in the property being skipped on serialization. + /// public Func? Get { get => _untypedGet; @@ -60,8 +63,11 @@ public JsonConverter? CustomConverter } /// - /// Setter delegate. Property cannot be deserialized without it. + /// Gets or sets a setter delegate for the property. /// + /// + /// Setting to will result in the property being skipped on deserialization. + /// public Action? Set { get => _untypedSet; @@ -75,15 +81,22 @@ public JsonConverter? CustomConverter private protected abstract void SetSetter(Delegate? setter); /// - /// Decides if property with given declaring object and property value should be serialized. - /// If not set it is equivalent to always returning true. + /// Gets or sets a predicate deciding whether the current property value should be serialized. /// + /// + /// The first parameter denotes the parent object, the second parameter denotes the property value. + /// + /// Setting the predicate to is equivalent to always serializing the property value. + /// + /// When serializing using , the value of + /// will map to this predicate. + /// public Func? ShouldSerialize { get => _shouldSerialize; set { - CheckMutable(); + VerifyMutable(); _shouldSerialize = value; // By default we will go through faster path (not using delegate) and use IgnoreCondition // If users sets it explicitly we always go through delegate @@ -116,11 +129,11 @@ internal static JsonPropertyInfo GetPropertyPlaceholder() } /// - /// Type associated with JsonPropertyInfo + /// Gets the type of the current property metadata. /// public Type PropertyType { get; private protected set; } = null!; - private protected void CheckMutable() + private protected void VerifyMutable() { if (_isConfigured) { @@ -184,13 +197,13 @@ internal void GetPolicies() Debug.Assert(MemberInfo != null); DeterminePropertyName(); - JsonPropertyOrderAttribute? orderAttr = GetAttribute(MemberInfo); + JsonPropertyOrderAttribute? orderAttr = MemberInfo.GetCustomAttribute(inherit: false); if (orderAttr != null) { Order = orderAttr.Order; } - JsonNumberHandlingAttribute? attribute = GetAttribute(MemberInfo); + JsonNumberHandlingAttribute? attribute = MemberInfo.GetCustomAttribute(inherit: false); NumberHandling = attribute?.Handling; } @@ -200,7 +213,7 @@ private void DeterminePropertyName() ClrName = MemberInfo.Name; - JsonPropertyNameAttribute? nameAttribute = GetAttribute(MemberInfo); + JsonPropertyNameAttribute? nameAttribute = MemberInfo.GetCustomAttribute(inherit: false); if (nameAttribute != null) { string name = nameAttribute.Name; @@ -428,11 +441,6 @@ private bool NumberHandingIsApplicable() potentialNumberType == JsonTypeInfo.ObjectType; } - internal static TAttribute? GetAttribute(MemberInfo memberInfo) where TAttribute : Attribute - { - return (TAttribute?)memberInfo.GetCustomAttribute(typeof(TAttribute), inherit: false); - } - internal abstract bool GetMemberAndWriteJson(object obj, ref WriteStack state, Utf8JsonWriter writer); internal abstract bool GetMemberAndWriteJsonExtensionData(object obj, ref WriteStack state, Utf8JsonWriter writer); @@ -486,17 +494,19 @@ internal abstract void Initialize( // 3) EscapedNameSection. The escaped verson of NameAsUtf8Bytes plus the wrapping quotes and a trailing colon. Used during serialization. /// - /// The name of the property. - /// It is either the actual .NET property name, - /// the value specified in JsonPropertyNameAttribute, - /// or the value returned from PropertyNamingPolicy. + /// Gets or sets the JSON property name used when serializing the property. /// + /// + /// This typically reflects the underlying .NET member name, + /// the name derived from , + /// or the value specified in , + /// public string Name { get => _name; set { - CheckMutable(); + VerifyMutable(); if (value == null) { @@ -525,9 +535,23 @@ public string Name public JsonSerializerOptions Options { get; internal set; } = null!; // initialized in Init method /// - /// The property order. + /// Gets or sets the serialization order for the current property. /// - internal int Order { get; set; } + /// + /// When using , properties annotated + /// with the will map to this value. + /// + internal int Order + { + get => _order; + set + { + VerifyMutable(); + _order = value; + } + } + + private int _order; internal bool ReadJsonAndAddExtensionProperty( object obj, @@ -594,7 +618,7 @@ JsonConverter GetDictionaryValueConverter(Type dictionaryValueType) internal bool ReadJsonExtensionDataValue(ref ReadStack state, ref Utf8JsonReader reader, out object? value) { - Debug.Assert(this == state.Current.JsonTypeInfo.DataExtensionProperty); + Debug.Assert(this == state.Current.JsonTypeInfo.ExtensionDataProperty); if (JsonTypeInfo.ElementType == JsonTypeInfo.ObjectType && reader.TokenType == JsonTokenType.Null) { @@ -695,7 +719,7 @@ public JsonNumberHandling? NumberHandling get => _numberHandling; set { - CheckMutable(); + VerifyMutable(); _numberHandling = value; } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonPropertyInfoOfT.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonPropertyInfoOfT.cs index 1cd99983fa73c..31c9ca3aa28f2 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonPropertyInfoOfT.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonPropertyInfoOfT.cs @@ -50,7 +50,7 @@ private protected override void SetGetter(Delegate? getter) { Debug.Assert(getter is null or Func or Func); - CheckMutable(); + VerifyMutable(); if (getter is null) { @@ -74,7 +74,7 @@ private protected override void SetSetter(Delegate? setter) { Debug.Assert(setter is null or Action or Action); - CheckMutable(); + VerifyMutable(); if (setter is null) { @@ -89,7 +89,7 @@ private protected override void SetSetter(Delegate? setter) else { Action untypedSet = (Action)setter; - _typedSet = ((obj, value) => untypedSet(obj, (T)value!)); + _typedSet = ((obj, value) => untypedSet(obj, value)); _untypedSet = untypedSet; } } @@ -137,7 +137,7 @@ internal override void Initialize( { case PropertyInfo propertyInfo: { - bool useNonPublicAccessors = GetAttribute(propertyInfo) != null; + bool useNonPublicAccessors = propertyInfo.GetCustomAttribute(inherit: false) != null; MethodInfo? getMethod = propertyInfo.GetMethod; if (getMethod != null && (getMethod.IsPublic || useNonPublicAccessors)) diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfo.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfo.cs index ea3752fbb7846..db5982be7c15f 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfo.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfo.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; -using System.ComponentModel; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Reflection; @@ -23,6 +22,11 @@ public abstract partial class JsonTypeInfo private JsonPropertyInfoDictionaryValueList? _properties; + private Action? _onSerializing; + private Action? _onSerialized; + private Action? _onDeserializing; + private Action? _onDeserialized; + /// /// Object constructor. If set to null type is not deserializable. /// @@ -41,7 +45,95 @@ public Func? CreateObject internal Func? CreateObjectForExtensionDataProperty { get; private protected set; } /// - /// Gets JsonPropertyInfo list. Only applicable when Kind is Object. + /// Gets or sets a callback to be invoked before serialization occurs. + /// + /// + /// Types implementing will map to this callback. + /// + internal Action? OnSerializing + { + get => _onSerializing; + set + { + VerifyMutable(); + + if (Kind != JsonTypeInfoKind.Object) + { + ThrowHelper.ThrowInvalidOperationException_SerializationCallbacksNotSupported(Kind); + } + + _onSerializing = value; + } + } + + /// + /// Gets or sets a callback to be invoked after serialization occurs. + /// + /// + /// Types implementing will map to this callback. + /// + internal Action? OnSerialized + { + get => _onSerialized; + set + { + VerifyMutable(); + + if (Kind != JsonTypeInfoKind.Object) + { + ThrowHelper.ThrowInvalidOperationException_SerializationCallbacksNotSupported(Kind); + } + + _onSerialized = value; + } + } + + /// + /// Gets or sets a callback to be invoked before deserialization occurs. + /// + /// + /// Types implementing will map to this callback. + /// + internal Action? OnDeserializing + { + get => _onDeserializing; + set + { + VerifyMutable(); + + if (Kind != JsonTypeInfoKind.Object) + { + ThrowHelper.ThrowInvalidOperationException_SerializationCallbacksNotSupported(Kind); + } + + _onDeserializing = value; + } + } + + /// + /// Gets or sets a callback to be invoked after deserialization occurs. + /// + /// + /// Types implementing will map to this callback. + /// + internal Action? OnDeserialized + { + get => _onDeserialized; + set + { + VerifyMutable(); + + if (Kind != JsonTypeInfoKind.Object) + { + ThrowHelper.ThrowInvalidOperationException_SerializationCallbacksNotSupported(Kind); + } + + _onDeserialized = value; + } + } + + /// + /// Gets JsonPropertyInfo list. Only applicable when equals . /// public IList Properties { @@ -70,6 +162,10 @@ public IList Properties /// /// Gets or sets a configuration object specifying polymorphism metadata. /// + /// + /// Configuration specified in the and + /// will automatically map to this property. + /// public JsonPolymorphismOptions? PolymorphismOptions { get => _polymorphismOptions; @@ -98,7 +194,30 @@ public JsonPolymorphismOptions? PolymorphismOptions // Add method delegate for non-generic Stack and Queue; and types that derive from them. internal object? AddMethodDelegate { get; set; } - internal JsonPropertyInfo? DataExtensionProperty { get; set; } + /// + /// Gets or sets an extension data property for the current type. + /// + /// + /// Properties annotated with + /// will appears here when using or . + /// + internal JsonPropertyInfo? ExtensionDataProperty + { + get => _extensionDataProperty; + set + { + VerifyMutable(); + + if (value != null && !IsValidDataExtensionProperty(value)) + { + ThrowHelper.ThrowInvalidOperationException_SerializationDataExtensionPropertyInvalid(Type, value); + } + + _extensionDataProperty = value; + } + } + + private JsonPropertyInfo? _extensionDataProperty; internal PolymorphicTypeResolver? PolymorphicTypeResolver { get; private set; } @@ -201,7 +320,7 @@ internal JsonTypeInfo? KeyTypeInfo internal Type? KeyType { get; set; } /// - /// Options associated with JsonTypeInfo + /// Gets the instance associated with . /// public JsonSerializerOptions Options { get; private set; } @@ -250,8 +369,11 @@ public JsonConverter Converter private DefaultValueHolder? _defaultValueHolder; /// - /// Type specific value overriding JsonSerializerOptions NumberHandling. For DefaultJsonTypeInfoResolver it is equivalent to JsonNumberHandlingAttribute value. + /// Gets or sets the type-level override. /// + /// + /// For it is equivalent to annotating the property with . + /// public JsonNumberHandling? NumberHandling { get => _numberHandling; @@ -362,10 +484,10 @@ internal virtual void Configure() LateAddProperties(); } - if (DataExtensionProperty != null) + if (ExtensionDataProperty != null) { - DataExtensionProperty.EnsureChildOf(this); - DataExtensionProperty.EnsureConfigured(); + ExtensionDataProperty.EnsureChildOf(this); + ExtensionDataProperty.EnsureConfigured(); } if (converter.ConverterStrategy == ConverterStrategy.Object) @@ -374,7 +496,7 @@ internal virtual void Configure() foreach (var jsonPropertyInfoKv in PropertyCache.List) { - JsonPropertyInfo jsonPropertyInfo = jsonPropertyInfoKv.Value!; + JsonPropertyInfo jsonPropertyInfo = jsonPropertyInfoKv.Value; jsonPropertyInfo.EnsureChildOf(this); jsonPropertyInfo.EnsureConfigured(); @@ -419,7 +541,7 @@ internal string GetDebugInfo() sb.AppendLine(" Properties: {"); foreach (var property in PropertyCache!.List) { - JsonPropertyInfo pi = property.Value!; + JsonPropertyInfo pi = property.Value; sb.AppendLine($" {property.Key}:"); sb.AppendLine($"{pi.GetDebugInfo(indent: 6)},"); } @@ -613,9 +735,9 @@ internal void InitializeConstructorParameters(JsonParameterInfoValues[] jsonPara // the parameter name to the object property name and do not use the JSON version of the name here. var nameLookup = new Dictionary(PropertyCache!.Count); - foreach (KeyValuePair kvp in PropertyCache.List) + foreach (KeyValuePair kvp in PropertyCache.List) { - JsonPropertyInfo jsonProperty = kvp.Value!; + JsonPropertyInfo jsonProperty = kvp.Value; string propertyName = jsonProperty.ClrName ?? jsonProperty.Name; ParameterLookupKey key = new(propertyName, jsonProperty.PropertyType); @@ -652,11 +774,11 @@ internal void InitializeConstructorParameters(JsonParameterInfoValues[] jsonPara parameterCache.Add(jsonPropertyInfo.Name, jsonParameterInfo); } // It is invalid for the extension data property to bind with a constructor argument. - else if (DataExtensionProperty != null && - StringComparer.OrdinalIgnoreCase.Equals(paramToCheck.Name, DataExtensionProperty.Name)) + else if (ExtensionDataProperty != null && + StringComparer.OrdinalIgnoreCase.Equals(paramToCheck.Name, ExtensionDataProperty.Name)) { - Debug.Assert(DataExtensionProperty.ClrName != null, "Custom property info cannot be data extension property"); - ThrowHelper.ThrowInvalidOperationException_ExtensionDataCannotBindToCtorParam(DataExtensionProperty.ClrName, DataExtensionProperty); + Debug.Assert(ExtensionDataProperty.ClrName != null, "Custom property info cannot be data extension property"); + ThrowHelper.ThrowInvalidOperationException_ExtensionDataCannotBindToCtorParam(ExtensionDataProperty.ClrName, ExtensionDataProperty); } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfoOfT.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfoOfT.cs index 9c1fe6e8a583d..d1e083a3ce976 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfoOfT.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfoOfT.cs @@ -88,5 +88,33 @@ private protected set HasSerialize = value != null; } } + + private protected void MapInterfaceTypesToCallbacks() + { + // Callbacks currently only supported in object kinds + // TODO: extend to collections/dictionaries + if (Kind == JsonTypeInfoKind.Object) + { + if (typeof(IJsonOnSerializing).IsAssignableFrom(typeof(T))) + { + OnSerializing = static obj => ((IJsonOnSerializing)obj).OnSerializing(); + } + + if (typeof(IJsonOnSerialized).IsAssignableFrom(typeof(T))) + { + OnSerialized = static obj => ((IJsonOnSerialized)obj).OnSerialized(); + } + + if (typeof(IJsonOnDeserializing).IsAssignableFrom(typeof(T))) + { + OnDeserializing = static obj => ((IJsonOnDeserializing)obj).OnDeserializing(); + } + + if (typeof(IJsonOnDeserialized).IsAssignableFrom(typeof(T))) + { + OnDeserialized = static obj => ((IJsonOnDeserialized)obj).OnDeserialized(); + } + } + } } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/ReflectionJsonTypeInfoOfT.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/ReflectionJsonTypeInfoOfT.cs index 9da7736b398da..2a0d33f6bfea2 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/ReflectionJsonTypeInfoOfT.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/ReflectionJsonTypeInfoOfT.cs @@ -35,6 +35,7 @@ internal ReflectionJsonTypeInfo(JsonConverter converter, JsonSerializerOptions o { NumberHandling = GetNumberHandlingForType(Type); PolymorphismOptions = JsonPolymorphismOptions.CreateFromAttributeDeclarations(Type); + MapInterfaceTypesToCallbacks(); if (PropertyInfoForTypeInfo.ConverterStrategy == ConverterStrategy.Object) { @@ -113,7 +114,7 @@ private void AddPropertiesAndParametersUsingReflection() } else { - if (JsonPropertyInfo.GetAttribute(propertyInfo) != null) + if (propertyInfo.GetCustomAttribute(inherit: false) != null) { ThrowHelper.ThrowInvalidOperationException_JsonIncludeOnNonPublicInvalid(propertyName, currentType); } @@ -131,7 +132,7 @@ private void AddPropertiesAndParametersUsingReflection() continue; } - bool hasJsonInclude = JsonPropertyInfo.GetAttribute(fieldInfo) != null; + bool hasJsonInclude = fieldInfo.GetCustomAttribute(inherit: false) != null; if (fieldInfo.IsPublic) { @@ -169,7 +170,7 @@ private void AddPropertiesAndParametersUsingReflection() if (propertyOrderSpecified) { - PropertyCache.List.Sort((p1, p2) => p1.Value!.Order.CompareTo(p2.Value!.Order)); + PropertyCache.List.StableSortByKey(static p => p.Value.Order); } } @@ -182,8 +183,8 @@ private void CacheMember( ref bool propertyOrderSpecified, ref Dictionary? ignoredMembers) { - bool hasExtensionAttribute = memberInfo.GetCustomAttribute(typeof(JsonExtensionDataAttribute)) != null; - if (hasExtensionAttribute && DataExtensionProperty != null) + bool hasExtensionAttribute = memberInfo.GetCustomAttribute(inherit: false) != null; + if (hasExtensionAttribute && ExtensionDataProperty != null) { ThrowHelper.ThrowInvalidOperationException_SerializationDuplicateTypeAttribute(Type, typeof(JsonExtensionDataAttribute)); } @@ -199,9 +200,8 @@ private void CacheMember( if (hasExtensionAttribute) { - Debug.Assert(DataExtensionProperty == null); - ValidateAndAssignDataExtensionProperty(jsonPropertyInfo); - Debug.Assert(DataExtensionProperty != null); + Debug.Assert(ExtensionDataProperty == null); + ExtensionDataProperty = jsonPropertyInfo; } else { @@ -217,7 +217,7 @@ private void CacheMember( bool isVirtual, JsonSerializerOptions options) { - JsonIgnoreCondition? ignoreCondition = JsonPropertyInfo.GetAttribute(memberInfo)?.Condition; + JsonIgnoreCondition? ignoreCondition = memberInfo.GetCustomAttribute(inherit: false)?.Condition; if (IsInvalidForSerialization(memberType)) { @@ -308,15 +308,5 @@ private static JsonParameterInfoValues[] GetParameterInfoArray(ParameterInfo[] p return jsonParameters; } - - private void ValidateAndAssignDataExtensionProperty(JsonPropertyInfo jsonPropertyInfo) - { - if (!IsValidDataExtensionProperty(jsonPropertyInfo)) - { - ThrowHelper.ThrowInvalidOperationException_SerializationDataExtensionPropertyInvalid(Type, jsonPropertyInfo); - } - - DataExtensionProperty = jsonPropertyInfo; - } } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/SourceGenJsonTypeInfoOfT.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/SourceGenJsonTypeInfoOfT.cs index 80c1ace7b2ae7..f50063c670318 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/SourceGenJsonTypeInfoOfT.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/SourceGenJsonTypeInfoOfT.cs @@ -21,12 +21,13 @@ public SourceGenJsonTypeInfo(JsonConverter converter, JsonSerializerOptions opti : base(converter, options) { PolymorphismOptions = JsonPolymorphismOptions.CreateFromAttributeDeclarations(Type); + MapInterfaceTypesToCallbacks(); } /// /// Creates serialization metadata for an object. /// - public SourceGenJsonTypeInfo(JsonSerializerOptions options, JsonObjectInfoValues objectInfo) : base(GetConverter(objectInfo), options) + public SourceGenJsonTypeInfo(JsonSerializerOptions options, JsonObjectInfoValues objectInfo) : this(GetConverter(objectInfo), options) { if (objectInfo.ObjectWithParameterizedConstructorCreator != null) { @@ -41,9 +42,7 @@ public SourceGenJsonTypeInfo(JsonSerializerOptions options, JsonObjectInfoValues PropInitFunc = objectInfo.PropertyMetadataInitializer; SerializeHandler = objectInfo.SerializeHandler; - NumberHandling = objectInfo.NumberHandling; - PolymorphismOptions = JsonPolymorphismOptions.CreateFromAttributeDeclarations(Type); } /// @@ -55,7 +54,7 @@ public SourceGenJsonTypeInfo( Func> converterCreator, object? createObjectWithArgs = null, object? addFunc = null) - : base(new JsonMetadataServicesConverter(converterCreator()), options) + : this(new JsonMetadataServicesConverter(converterCreator()), options) { if (collectionInfo is null) { @@ -70,7 +69,6 @@ public SourceGenJsonTypeInfo( CreateObjectWithArgs = createObjectWithArgs; AddMethodDelegate = addFunc; CreateObject = collectionInfo.ObjectCreator; - PolymorphismOptions = JsonPolymorphismOptions.CreateFromAttributeDeclarations(Type); } private static JsonConverter GetConverter(JsonObjectInfoValues objectInfo) @@ -167,12 +165,12 @@ internal void AddPropertiesUsingSourceGenInfo() if (jsonPropertyInfo.SrcGen_IsExtensionData) { - // Source generator compile-time type inspection has performed this validation for us. - // except JsonTypeInfo can be initialized in parallel causing this to be ocassionally re-initialized - // Debug.Assert(DataExtensionProperty == null); - Debug.Assert(IsValidDataExtensionProperty(jsonPropertyInfo)); + if (ExtensionDataProperty != null) + { + ThrowHelper.ThrowInvalidOperationException_SerializationDuplicateTypeAttribute(Type, typeof(JsonExtensionDataAttribute)); + } - DataExtensionProperty = jsonPropertyInfo; + ExtensionDataProperty = jsonPropertyInfo; continue; } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.cs b/src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.cs index 9af92adb3808d..551d034a7d300 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.cs @@ -4,6 +4,7 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; +using System.Text.Json.Serialization.Metadata; namespace System.Text.Json { @@ -663,6 +664,12 @@ public static void ThrowInvalidOperationException_ExpectedChar(JsonTokenType tok throw GetInvalidOperationException("char", tokenType); } + [DoesNotReturn] + public static void ThrowInvalidOperationException_SerializationCallbacksNotSupported(JsonTypeInfoKind typeInfoKind) + { + throw GetInvalidOperationException(SR.Format(SR.SerializationCallbacksNotSupported, typeInfoKind)); + } + [DoesNotReturn] public static void ThrowObjectDisposedException_Utf8JsonWriter() {