Skip to content

Commit

Permalink
chore: Update YamlDotNet version to 16.3.0 (#10541)
Browse files Browse the repository at this point in the history
* chore: update yamldotnet version

* chore: update ITypeInspector's derived method

* chore: update IPropertyDescriptor derived methods

* chore: update YamlDeserializer

* chore: update YamlSerializer

* chore: update IObjectGraphTraversalStrategy derived methods

* chore: update INodeDeserializer derived methods

* chore: update IObjectGraphVisitor derived methods
  • Loading branch information
filzrev authored Feb 20, 2025
1 parent 7d095d6 commit f70ab07
Show file tree
Hide file tree
Showing 17 changed files with 602 additions and 211 deletions.
2 changes: 1 addition & 1 deletion Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
<PackageVersion Include="System.Composition" Version="9.0.2" />
<PackageVersion Include="System.Formats.Asn1" Version="9.0.2" />
<PackageVersion Include="System.Text.Json" Version="9.0.2" />
<PackageVersion Include="YamlDotNet" Version="15.3.0" />
<PackageVersion Include="YamlDotNet" Version="16.3.0" />
</ItemGroup>

<!-- .slnx solution format is supported Microsoft.Build 17.13.9 or later. -->
Expand Down
10 changes: 8 additions & 2 deletions src/Docfx.YamlSerialization/Helpers/ReflectionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,18 @@ internal static class ReflectionExtensions
/// Determines whether the specified type has a default constructor.
/// </summary>
/// <param name="type">The type.</param>
/// <param name="allowPrivateConstructors">Allow private constructor.</param>
/// <returns>
/// <c>true</c> if the type has a default constructor; otherwise, <c>false</c>.
/// </returns>
public static bool HasDefaultConstructor(this Type type)
public static bool HasDefaultConstructor(this Type type, bool allowPrivateConstructors)
{
return type.IsValueType || type.GetConstructor(BindingFlags.Public | BindingFlags.Instance, null, Type.EmptyTypes, null) != null;
var bindingFlags = BindingFlags.Public | BindingFlags.Instance;
if (allowPrivateConstructors)
{
bindingFlags |= BindingFlags.NonPublic;
}
return type.IsValueType || type.GetConstructor(bindingFlags, null, Type.EmptyTypes, null) != null;
}

public static IEnumerable<PropertyInfo> GetPublicProperties(this Type type)
Expand Down
83 changes: 83 additions & 0 deletions src/Docfx.YamlSerialization/Helpers/TypeConverterCache.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Concurrent;
using System.Diagnostics.CodeAnalysis;
using YamlDotNet.Serialization;

namespace Docfx.YamlSerialization.Helpers;

/// <summary>
/// A cache / map for <see cref="IYamlTypeConverter"/> instances.
/// </summary>
/// <remarks>
/// This class is copied from following YamlDotNet implementation.
/// https://github.com/aaubry/YamlDotNet/blob/master/YamlDotNet/Serialization/Utilities/TypeConverterCache.cs
/// </remarks>
internal sealed class TypeConverterCache
{
private readonly IYamlTypeConverter[] typeConverters;
private readonly ConcurrentDictionary<Type, (bool HasMatch, IYamlTypeConverter? TypeConverter)> cache = new();

public TypeConverterCache(IEnumerable<IYamlTypeConverter>? typeConverters)
: this(typeConverters?.ToArray() ?? [])
{
}

public TypeConverterCache(IYamlTypeConverter[] typeConverters)
{
this.typeConverters = typeConverters;
}

/// <summary>
/// Returns the first <see cref="IYamlTypeConverter"/> that accepts the given type.
/// </summary>
/// <param name="type">The <see cref="Type"/> to lookup.</param>
/// <param name="typeConverter">The <see cref="IYamlTypeConverter" /> that accepts this type or <see langword="false" /> if no converter is found.</param>
/// <returns><see langword="true"/> if a type converter was found; <see langword="false"/> otherwise.</returns>
public bool TryGetConverterForType(Type type, [NotNullWhen(true)] out IYamlTypeConverter? typeConverter)
{
var result = cache.GetOrAdd(type, static (t, tc) => LookupTypeConverter(t, tc), typeConverters);

typeConverter = result.TypeConverter;
return result.HasMatch;
}

/// <summary>
/// Returns the <see cref="IYamlTypeConverter"/> of the given type.
/// </summary>
/// <param name="converter">The type of the converter.</param>
/// <returns>The <see cref="IYamlTypeConverter"/> of the given type.</returns>
/// <exception cref="ArgumentException">If no type converter of the given type is found.</exception>
/// <remarks>
/// Note that this method searches on the type of the <see cref="IYamlTypeConverter"/> itself. If you want to find a type converter
/// that accepts a given <see cref="Type"/>, use <see cref="TryGetConverterForType(Type, out IYamlTypeConverter?)"/> instead.
/// </remarks>
public IYamlTypeConverter GetConverterByType(Type converter)
{
// Intentially avoids LINQ as this is on a hot path
foreach (var typeConverter in typeConverters)
{
if (typeConverter.GetType() == converter)
{
return typeConverter;
}
}

throw new ArgumentException($"{nameof(IYamlTypeConverter)} of type {converter.FullName} not found", nameof(converter));
}

private static (bool HasMatch, IYamlTypeConverter? TypeConverter) LookupTypeConverter(Type type, IYamlTypeConverter[] typeConverters)
{
// Intentially avoids LINQ as this is on a hot path
foreach (var typeConverter in typeConverters)
{
if (typeConverter.Accepts(type))
{
return (true, typeConverter);
}
}

return (false, null);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,22 @@ namespace Docfx.YamlSerialization.NodeDeserializers;

public class EmitArrayNodeDeserializer : INodeDeserializer
{
private readonly INamingConvention _enumNamingConvention;
private readonly ITypeInspector _typeDescriptor;

private static readonly MethodInfo DeserializeHelperMethod =
typeof(EmitArrayNodeDeserializer).GetMethod(nameof(DeserializeHelper))!;
private static readonly ConcurrentDictionary<Type, Func<IParser, Type, Func<IParser, Type, object?>, object?>> _funcCache =

private static readonly ConcurrentDictionary<Type, Func<IParser, Type, Func<IParser, Type, object?>, INamingConvention, ITypeInspector, object?>> _funcCache =
new();

bool INodeDeserializer.Deserialize(IParser reader, Type expectedType, Func<IParser, Type, object?> nestedObjectDeserializer, out object? value)
public EmitArrayNodeDeserializer(INamingConvention enumNamingConvention, ITypeInspector typeDescriptor)
{
_enumNamingConvention = enumNamingConvention;
_typeDescriptor = typeDescriptor;
}

bool INodeDeserializer.Deserialize(IParser reader, Type expectedType, Func<IParser, Type, object?> nestedObjectDeserializer, out object? value, ObjectDeserializer rootDeserializer)
{
if (!expectedType.IsArray)
{
Expand All @@ -26,27 +36,44 @@ bool INodeDeserializer.Deserialize(IParser reader, Type expectedType, Func<IPars
}

var func = _funcCache.GetOrAdd(expectedType, AddItem);
value = func(reader, expectedType, nestedObjectDeserializer);
value = func(reader, expectedType, nestedObjectDeserializer, _enumNamingConvention, _typeDescriptor);
return true;
}

[EditorBrowsable(EditorBrowsableState.Never)]
public static TItem[] DeserializeHelper<TItem>(IParser reader, Type expectedType, Func<IParser, Type, object?> nestedObjectDeserializer)
public static TItem[] DeserializeHelper<TItem>(
IParser reader,
Type expectedType,
Func<IParser, Type, object?> nestedObjectDeserializer,
INamingConvention enumNamingConvention,
ITypeInspector typeDescriptor)
{
var items = new List<TItem>();
EmitGenericCollectionNodeDeserializer.DeserializeHelper(reader, expectedType, nestedObjectDeserializer, items);
EmitGenericCollectionNodeDeserializer.DeserializeHelper(reader, expectedType, nestedObjectDeserializer, items, enumNamingConvention, typeDescriptor);
return items.ToArray();
}

private static Func<IParser, Type, Func<IParser, Type, object?>, object?> AddItem(Type expectedType)
private static Func<IParser, Type, Func<IParser, Type, object?>, INamingConvention, ITypeInspector, object?> AddItem(Type expectedType)
{
var dm = new DynamicMethod(string.Empty, typeof(object), [typeof(IParser), typeof(Type), typeof(Func<IParser, Type, object>)]);
var dm = new DynamicMethod(
string.Empty,
returnType: typeof(object),
parameterTypes:
[
typeof(IParser), // reader
typeof(Type), // expectedType
typeof(Func<IParser, Type, object?>), // nestedObjectDeserializer
typeof(INamingConvention), // enumNamingConvention
typeof(ITypeInspector), // typeDescriptor
]);
var il = dm.GetILGenerator();
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Ldarg_2);
il.Emit(OpCodes.Ldarg_3);
il.Emit(OpCodes.Ldarg_S, (byte)4);
il.Emit(OpCodes.Call, DeserializeHelperMethod.MakeGenericMethod(expectedType.GetElementType()!));
il.Emit(OpCodes.Ret);
return (Func<IParser, Type, Func<IParser, Type, object?>, object?>)dm.CreateDelegate(typeof(Func<IParser, Type, Func<IParser, Type, object?>, object?>));
return (Func<IParser, Type, Func<IParser, Type, object?>, INamingConvention, ITypeInspector, object?>)dm.CreateDelegate(typeof(Func<IParser, Type, Func<IParser, Type, object?>, INamingConvention, ITypeInspector, object?>));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
using YamlDotNet.Core;
using YamlDotNet.Core.Events;
using YamlDotNet.Serialization;
using YamlDotNet.Serialization.NamingConventions;
using YamlDotNet.Serialization.Utilities;
using EditorBrowsable = System.ComponentModel.EditorBrowsableAttribute;
using EditorBrowsableState = System.ComponentModel.EditorBrowsableState;
Expand All @@ -19,15 +18,21 @@ public class EmitGenericCollectionNodeDeserializer : INodeDeserializer
private static readonly MethodInfo DeserializeHelperMethod =
typeof(EmitGenericCollectionNodeDeserializer).GetMethod(nameof(DeserializeHelper))!;
private readonly IObjectFactory _objectFactory;
private readonly Dictionary<Type, Type?> _gpCache = [];
private readonly Dictionary<Type, Action<IParser, Type, Func<IParser, Type, object?>, object?>> _actionCache = [];
private readonly INamingConvention _enumNamingConvention;
private readonly ITypeInspector _typeDescriptor;
private readonly Dictionary<Type, Type?> _gpCache =
new();
private readonly Dictionary<Type, Action<IParser, Type, Func<IParser, Type, object?>, object?, INamingConvention, ITypeInspector>> _actionCache =
new();

public EmitGenericCollectionNodeDeserializer(IObjectFactory objectFactory)
public EmitGenericCollectionNodeDeserializer(IObjectFactory objectFactory, INamingConvention enumNamingConvention, ITypeInspector typeDescriptor)
{
_objectFactory = objectFactory;
_enumNamingConvention = enumNamingConvention;
_typeDescriptor = typeDescriptor;
}

bool INodeDeserializer.Deserialize(IParser reader, Type expectedType, Func<IParser, Type, object?> nestedObjectDeserializer, out object? value)
bool INodeDeserializer.Deserialize(IParser reader, Type expectedType, Func<IParser, Type, object?> nestedObjectDeserializer, out object? value, ObjectDeserializer rootDeserializer)
{
if (!_gpCache.TryGetValue(expectedType, out var gp))
{
Expand Down Expand Up @@ -55,39 +60,58 @@ bool INodeDeserializer.Deserialize(IParser reader, Type expectedType, Func<IPars
value = _objectFactory.Create(expectedType);
if (!_actionCache.TryGetValue(gp, out var action))
{
var dm = new DynamicMethod(string.Empty, typeof(void), [typeof(IParser), typeof(Type), typeof(Func<IParser, Type, object>), typeof(object)]);
var dm = new DynamicMethod(
string.Empty,
returnType: typeof(void),
[
typeof(IParser),
typeof(Type),
typeof(Func<IParser, Type, object?>),
typeof(object),
typeof(INamingConvention),
typeof(ITypeInspector)
]);

var il = dm.GetILGenerator();
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Ldarg_2);
il.Emit(OpCodes.Ldarg_3);
il.Emit(OpCodes.Ldarg_0); // reader
il.Emit(OpCodes.Ldarg_1); // expectedType
il.Emit(OpCodes.Ldarg_2); // nestedObjectDeserializer
il.Emit(OpCodes.Ldarg_3); // result
il.Emit(OpCodes.Castclass, typeof(ICollection<>).MakeGenericType(gp));
il.Emit(OpCodes.Ldarg_S, (byte)4); // enumNamingConvention
il.Emit(OpCodes.Ldarg_S, (byte)5); // typeDescriptor
il.Emit(OpCodes.Call, DeserializeHelperMethod.MakeGenericMethod(gp));
il.Emit(OpCodes.Ret);
action = (Action<IParser, Type, Func<IParser, Type, object?>, object?>)dm.CreateDelegate(typeof(Action<IParser, Type, Func<IParser, Type, object?>, object?>));
action = (Action<IParser, Type, Func<IParser, Type, object?>, object?, INamingConvention, ITypeInspector>)dm.CreateDelegate(typeof(Action<IParser, Type, Func<IParser, Type, object?>, object?, INamingConvention, ITypeInspector>));
_actionCache[gp] = action;
}

action(reader, expectedType, nestedObjectDeserializer, value);
action(reader, expectedType, nestedObjectDeserializer, value, _enumNamingConvention, _typeDescriptor);
return true;
}

[EditorBrowsable(EditorBrowsableState.Never)]
public static void DeserializeHelper<TItem>(IParser reader, Type expectedType, Func<IParser, Type, object?> nestedObjectDeserializer, ICollection<TItem> result)
public static void DeserializeHelper<TItem>(
IParser reader,
Type expectedType,
Func<IParser, Type, object?> nestedObjectDeserializer,
ICollection<TItem> result,
INamingConvention enumNamingConvention,
ITypeInspector typeDescriptor)
{
reader.Consume<SequenceStart>();
while (!reader.Accept<SequenceEnd>(out _))
{
var value = nestedObjectDeserializer(reader, typeof(TItem));
if (value is not IValuePromise promise)
{
result.Add(TypeConverter.ChangeType<TItem>(value, NullNamingConvention.Instance));
result.Add(TypeConverter.ChangeType<TItem>(value, enumNamingConvention, typeDescriptor));
}
else if (result is IList<TItem> list)
{
var index = list.Count;
result.Add(default!);
promise.ValueAvailable += v => list[index] = TypeConverter.ChangeType<TItem>(v, NullNamingConvention.Instance);
promise.ValueAvailable += v => list[index] = TypeConverter.ChangeType<TItem>(v, enumNamingConvention, typeDescriptor);
}
else
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public EmitGenericDictionaryNodeDeserializer(IObjectFactory objectFactory)
_objectFactory = objectFactory;
}

bool INodeDeserializer.Deserialize(IParser reader, Type expectedType, Func<IParser, Type, object?> nestedObjectDeserializer, out object? value)
bool INodeDeserializer.Deserialize(IParser reader, Type expectedType, Func<IParser, Type, object?> nestedObjectDeserializer, out object? value, ObjectDeserializer rootDeserializer)
{
if (!_gpCache.TryGetValue(expectedType, out var gp))
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
using YamlDotNet.Core;
using YamlDotNet.Core.Events;
using YamlDotNet.Serialization;
using YamlDotNet.Serialization.NamingConventions;
using YamlDotNet.Serialization.Utilities;

namespace Docfx.YamlSerialization.NodeDeserializers;
Expand All @@ -14,15 +13,19 @@ public sealed class ExtensibleObjectNodeDeserializer : INodeDeserializer
private readonly IObjectFactory _objectFactory;
private readonly ITypeInspector _typeDescriptor;
private readonly bool _ignoreUnmatched;
private readonly bool _caseInsensitivePropertyMatching;
private readonly INamingConvention _enumNamingConvention;

public ExtensibleObjectNodeDeserializer(IObjectFactory objectFactory, ITypeInspector typeDescriptor, bool ignoreUnmatched)
public ExtensibleObjectNodeDeserializer(IObjectFactory objectFactory, ITypeInspector typeDescriptor, INamingConvention enumNamingConvention, bool ignoreUnmatched, bool caseInsensitivePropertyMatching)
{
_objectFactory = objectFactory;
_typeDescriptor = typeDescriptor;
_enumNamingConvention = enumNamingConvention;
_ignoreUnmatched = ignoreUnmatched;
_caseInsensitivePropertyMatching = caseInsensitivePropertyMatching;
}

bool INodeDeserializer.Deserialize(IParser reader, Type expectedType, Func<IParser, Type, object?> nestedObjectDeserializer, out object? value)
bool INodeDeserializer.Deserialize(IParser reader, Type expectedType, Func<IParser, Type, object?> nestedObjectDeserializer, out object? value, ObjectDeserializer rootDeserializer)
{
if (!reader.TryConsume<MappingStart>(out _))
{
Expand All @@ -34,7 +37,7 @@ bool INodeDeserializer.Deserialize(IParser reader, Type expectedType, Func<IPars
while (!reader.Accept<MappingEnd>(out _))
{
var propertyName = reader.Consume<Scalar>();
var property = _typeDescriptor.GetProperty(expectedType, value, propertyName.Value, _ignoreUnmatched);
var property = _typeDescriptor.GetProperty(expectedType, value, propertyName.Value, _ignoreUnmatched, _caseInsensitivePropertyMatching);
if (property == null)
{
reader.SkipThisAndNestedEvents();
Expand All @@ -44,15 +47,15 @@ bool INodeDeserializer.Deserialize(IParser reader, Type expectedType, Func<IPars
var propertyValue = nestedObjectDeserializer(reader, property.Type);
if (propertyValue is not IValuePromise propertyValuePromise)
{
var convertedValue = TypeConverter.ChangeType(propertyValue, property.Type, NullNamingConvention.Instance);
var convertedValue = TypeConverter.ChangeType(propertyValue, property.Type, _enumNamingConvention, _typeDescriptor);
property.Write(value, convertedValue);
}
else
{
var valueRef = value;
propertyValuePromise.ValueAvailable += v =>
{
var convertedValue = TypeConverter.ChangeType(v, property.Type, NullNamingConvention.Instance);
var convertedValue = TypeConverter.ChangeType(v, property.Type, _enumNamingConvention, _typeDescriptor);
property.Write(valueRef, convertedValue);
};
}
Expand Down
Loading

0 comments on commit f70ab07

Please sign in to comment.