From f70ab076cfe91143c800c8b0b5a8ef347cae89fb Mon Sep 17 00:00:00 2001
From: filzrev <103790468+filzrev@users.noreply.github.com>
Date: Thu, 20 Feb 2025 12:23:57 +0900
Subject: [PATCH] chore: Update YamlDotNet version to 16.3.0 (#10541)
* 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
---
Directory.Packages.props | 2 +-
.../Helpers/ReflectionExtensions.cs | 10 +-
.../Helpers/TypeConverterCache.cs | 83 ++++
.../EmitArrayNodeDeserializer.cs | 43 +-
.../EmitGenericCollectionNodeDeserializer.cs | 54 ++-
.../EmitGenericDictionaryNodeDeserializer.cs | 2 +-
.../ExtensibleObjectNodeDeserializer.cs | 15 +-
.../FullObjectGraphTraversalStrategy.cs | 393 ++++++++++++------
.../RoundtripObjectGraphTraversalStrategy.cs | 30 +-
.../ExclusiveObjectGraphVisitor.cs | 4 +-
.../TypeInspectors/EmitTypeInspector.cs | 67 +++
...ExtensibleNamingConventionTypeInspector.cs | 6 +
...dableAndWritablePropertiesTypeInspector.cs | 6 +
.../ExtensibleTypeInspectorSkeleton.cs | 13 +-
.../ExtensibleYamlAttributesTypeInspector.cs | 6 +
.../YamlDeserializer.cs | 32 +-
src/Docfx.YamlSerialization/YamlSerializer.cs | 47 ++-
17 files changed, 602 insertions(+), 211 deletions(-)
create mode 100644 src/Docfx.YamlSerialization/Helpers/TypeConverterCache.cs
diff --git a/Directory.Packages.props b/Directory.Packages.props
index acdca03c666..beba2030348 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -22,7 +22,7 @@
-
+
diff --git a/src/Docfx.YamlSerialization/Helpers/ReflectionExtensions.cs b/src/Docfx.YamlSerialization/Helpers/ReflectionExtensions.cs
index a60a1924d29..b5b3ff5d366 100644
--- a/src/Docfx.YamlSerialization/Helpers/ReflectionExtensions.cs
+++ b/src/Docfx.YamlSerialization/Helpers/ReflectionExtensions.cs
@@ -11,12 +11,18 @@ internal static class ReflectionExtensions
/// Determines whether the specified type has a default constructor.
///
/// The type.
+ /// Allow private constructor.
///
/// true if the type has a default constructor; otherwise, false.
///
- 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 GetPublicProperties(this Type type)
diff --git a/src/Docfx.YamlSerialization/Helpers/TypeConverterCache.cs b/src/Docfx.YamlSerialization/Helpers/TypeConverterCache.cs
new file mode 100644
index 00000000000..bbdcb2e2b83
--- /dev/null
+++ b/src/Docfx.YamlSerialization/Helpers/TypeConverterCache.cs
@@ -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;
+
+///
+/// A cache / map for instances.
+///
+///
+/// This class is copied from following YamlDotNet implementation.
+/// https://github.com/aaubry/YamlDotNet/blob/master/YamlDotNet/Serialization/Utilities/TypeConverterCache.cs
+///
+internal sealed class TypeConverterCache
+{
+ private readonly IYamlTypeConverter[] typeConverters;
+ private readonly ConcurrentDictionary cache = new();
+
+ public TypeConverterCache(IEnumerable? typeConverters)
+ : this(typeConverters?.ToArray() ?? [])
+ {
+ }
+
+ public TypeConverterCache(IYamlTypeConverter[] typeConverters)
+ {
+ this.typeConverters = typeConverters;
+ }
+
+ ///
+ /// Returns the first that accepts the given type.
+ ///
+ /// The to lookup.
+ /// The that accepts this type or if no converter is found.
+ /// if a type converter was found; otherwise.
+ 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;
+ }
+
+ ///
+ /// Returns the of the given type.
+ ///
+ /// The type of the converter.
+ /// The of the given type.
+ /// If no type converter of the given type is found.
+ ///
+ /// Note that this method searches on the type of the itself. If you want to find a type converter
+ /// that accepts a given , use instead.
+ ///
+ 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);
+ }
+}
diff --git a/src/Docfx.YamlSerialization/NodeDeserializers/EmitArrayNodeDeserializer.cs b/src/Docfx.YamlSerialization/NodeDeserializers/EmitArrayNodeDeserializer.cs
index 0dc273ac031..7f6fc724e95 100644
--- a/src/Docfx.YamlSerialization/NodeDeserializers/EmitArrayNodeDeserializer.cs
+++ b/src/Docfx.YamlSerialization/NodeDeserializers/EmitArrayNodeDeserializer.cs
@@ -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, object?>> _funcCache =
+
+ private static readonly ConcurrentDictionary, INamingConvention, ITypeInspector, object?>> _funcCache =
new();
- bool INodeDeserializer.Deserialize(IParser reader, Type expectedType, Func nestedObjectDeserializer, out object? value)
+ public EmitArrayNodeDeserializer(INamingConvention enumNamingConvention, ITypeInspector typeDescriptor)
+ {
+ _enumNamingConvention = enumNamingConvention;
+ _typeDescriptor = typeDescriptor;
+ }
+
+ bool INodeDeserializer.Deserialize(IParser reader, Type expectedType, Func nestedObjectDeserializer, out object? value, ObjectDeserializer rootDeserializer)
{
if (!expectedType.IsArray)
{
@@ -26,27 +36,44 @@ bool INodeDeserializer.Deserialize(IParser reader, Type expectedType, Func(IParser reader, Type expectedType, Func nestedObjectDeserializer)
+ public static TItem[] DeserializeHelper(
+ IParser reader,
+ Type expectedType,
+ Func nestedObjectDeserializer,
+ INamingConvention enumNamingConvention,
+ ITypeInspector typeDescriptor)
{
var items = new List();
- EmitGenericCollectionNodeDeserializer.DeserializeHelper(reader, expectedType, nestedObjectDeserializer, items);
+ EmitGenericCollectionNodeDeserializer.DeserializeHelper(reader, expectedType, nestedObjectDeserializer, items, enumNamingConvention, typeDescriptor);
return items.ToArray();
}
- private static Func, object?> AddItem(Type expectedType)
+ private static Func, INamingConvention, ITypeInspector, object?> AddItem(Type expectedType)
{
- var dm = new DynamicMethod(string.Empty, typeof(object), [typeof(IParser), typeof(Type), typeof(Func)]);
+ var dm = new DynamicMethod(
+ string.Empty,
+ returnType: typeof(object),
+ parameterTypes:
+ [
+ typeof(IParser), // reader
+ typeof(Type), // expectedType
+ typeof(Func), // 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, object?>)dm.CreateDelegate(typeof(Func, object?>));
+ return (Func, INamingConvention, ITypeInspector, object?>)dm.CreateDelegate(typeof(Func, INamingConvention, ITypeInspector, object?>));
}
}
diff --git a/src/Docfx.YamlSerialization/NodeDeserializers/EmitGenericCollectionNodeDeserializer.cs b/src/Docfx.YamlSerialization/NodeDeserializers/EmitGenericCollectionNodeDeserializer.cs
index 58f5dd74ff2..632ffdaf6c1 100644
--- a/src/Docfx.YamlSerialization/NodeDeserializers/EmitGenericCollectionNodeDeserializer.cs
+++ b/src/Docfx.YamlSerialization/NodeDeserializers/EmitGenericCollectionNodeDeserializer.cs
@@ -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;
@@ -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 _gpCache = [];
- private readonly Dictionary, object?>> _actionCache = [];
+ private readonly INamingConvention _enumNamingConvention;
+ private readonly ITypeInspector _typeDescriptor;
+ private readonly Dictionary _gpCache =
+ new();
+ private readonly Dictionary, 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 nestedObjectDeserializer, out object? value)
+ bool INodeDeserializer.Deserialize(IParser reader, Type expectedType, Func nestedObjectDeserializer, out object? value, ObjectDeserializer rootDeserializer)
{
if (!_gpCache.TryGetValue(expectedType, out var gp))
{
@@ -55,25 +60,44 @@ bool INodeDeserializer.Deserialize(IParser reader, Type expectedType, Func), typeof(object)]);
+ var dm = new DynamicMethod(
+ string.Empty,
+ returnType: typeof(void),
+ [
+ typeof(IParser),
+ typeof(Type),
+ typeof(Func),
+ 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, object?>)dm.CreateDelegate(typeof(Action, object?>));
+ action = (Action, object?, INamingConvention, ITypeInspector>)dm.CreateDelegate(typeof(Action, 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(IParser reader, Type expectedType, Func nestedObjectDeserializer, ICollection result)
+ public static void DeserializeHelper(
+ IParser reader,
+ Type expectedType,
+ Func nestedObjectDeserializer,
+ ICollection result,
+ INamingConvention enumNamingConvention,
+ ITypeInspector typeDescriptor)
{
reader.Consume();
while (!reader.Accept(out _))
@@ -81,13 +105,13 @@ public static void DeserializeHelper(IParser reader, Type expectedType, F
var value = nestedObjectDeserializer(reader, typeof(TItem));
if (value is not IValuePromise promise)
{
- result.Add(TypeConverter.ChangeType(value, NullNamingConvention.Instance));
+ result.Add(TypeConverter.ChangeType(value, enumNamingConvention, typeDescriptor));
}
else if (result is IList list)
{
var index = list.Count;
result.Add(default!);
- promise.ValueAvailable += v => list[index] = TypeConverter.ChangeType(v, NullNamingConvention.Instance);
+ promise.ValueAvailable += v => list[index] = TypeConverter.ChangeType(v, enumNamingConvention, typeDescriptor);
}
else
{
diff --git a/src/Docfx.YamlSerialization/NodeDeserializers/EmitGenericDictionaryNodeDeserializer.cs b/src/Docfx.YamlSerialization/NodeDeserializers/EmitGenericDictionaryNodeDeserializer.cs
index 8294a3dd44d..04fe761597c 100644
--- a/src/Docfx.YamlSerialization/NodeDeserializers/EmitGenericDictionaryNodeDeserializer.cs
+++ b/src/Docfx.YamlSerialization/NodeDeserializers/EmitGenericDictionaryNodeDeserializer.cs
@@ -24,7 +24,7 @@ public EmitGenericDictionaryNodeDeserializer(IObjectFactory objectFactory)
_objectFactory = objectFactory;
}
- bool INodeDeserializer.Deserialize(IParser reader, Type expectedType, Func nestedObjectDeserializer, out object? value)
+ bool INodeDeserializer.Deserialize(IParser reader, Type expectedType, Func nestedObjectDeserializer, out object? value, ObjectDeserializer rootDeserializer)
{
if (!_gpCache.TryGetValue(expectedType, out var gp))
{
diff --git a/src/Docfx.YamlSerialization/NodeDeserializers/ExtensibleObjectNodeDeserializer.cs b/src/Docfx.YamlSerialization/NodeDeserializers/ExtensibleObjectNodeDeserializer.cs
index 24980f3120d..c883079bcb4 100644
--- a/src/Docfx.YamlSerialization/NodeDeserializers/ExtensibleObjectNodeDeserializer.cs
+++ b/src/Docfx.YamlSerialization/NodeDeserializers/ExtensibleObjectNodeDeserializer.cs
@@ -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;
@@ -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 nestedObjectDeserializer, out object? value)
+ bool INodeDeserializer.Deserialize(IParser reader, Type expectedType, Func nestedObjectDeserializer, out object? value, ObjectDeserializer rootDeserializer)
{
if (!reader.TryConsume(out _))
{
@@ -34,7 +37,7 @@ bool INodeDeserializer.Deserialize(IParser reader, Type expectedType, Func(out _))
{
var propertyName = reader.Consume();
- var property = _typeDescriptor.GetProperty(expectedType, value, propertyName.Value, _ignoreUnmatched);
+ var property = _typeDescriptor.GetProperty(expectedType, value, propertyName.Value, _ignoreUnmatched, _caseInsensitivePropertyMatching);
if (property == null)
{
reader.SkipThisAndNestedEvents();
@@ -44,7 +47,7 @@ bool INodeDeserializer.Deserialize(IParser reader, Type expectedType, Func
{
- var convertedValue = TypeConverter.ChangeType(v, property.Type, NullNamingConvention.Instance);
+ var convertedValue = TypeConverter.ChangeType(v, property.Type, _enumNamingConvention, _typeDescriptor);
property.Write(valueRef, convertedValue);
};
}
diff --git a/src/Docfx.YamlSerialization/ObjectGraphTraversalStrategies/FullObjectGraphTraversalStrategy.cs b/src/Docfx.YamlSerialization/ObjectGraphTraversalStrategies/FullObjectGraphTraversalStrategy.cs
index 29dfdfab69a..0c0d2e2c583 100644
--- a/src/Docfx.YamlSerialization/ObjectGraphTraversalStrategies/FullObjectGraphTraversalStrategy.cs
+++ b/src/Docfx.YamlSerialization/ObjectGraphTraversalStrategies/FullObjectGraphTraversalStrategy.cs
@@ -3,11 +3,12 @@
using System.Collections;
using System.ComponentModel;
-using System.Globalization;
+using System.Diagnostics;
using System.Reflection;
using System.Reflection.Emit;
-using Docfx.YamlSerialization.Helpers;
+using System.Text;
using Docfx.YamlSerialization.ObjectDescriptors;
+using YamlDotNet.Core;
using YamlDotNet.Serialization;
using YamlDotNet.Serialization.NamingConventions;
using IObjectGraphVisitor = System.Object;
@@ -23,154 +24,223 @@ public class FullObjectGraphTraversalStrategy : IObjectGraphTraversalStrategy
{
private static MethodInfo TraverseGenericDictionaryHelperMethod { get; } =
typeof(FullObjectGraphTraversalStrategy).GetMethod(nameof(TraverseGenericDictionaryHelper))!;
- protected YamlSerializer Serializer { get; }
+
private readonly int _maxRecursion;
private readonly ITypeInspector _typeDescriptor;
private readonly ITypeResolver _typeResolver;
private readonly INamingConvention _namingConvention;
- private readonly Dictionary, Action> _behaviorCache = [];
- private readonly Dictionary, Action> _traverseGenericDictionaryCache = [];
+ private readonly IObjectFactory _objectFactory;
- public FullObjectGraphTraversalStrategy(YamlSerializer serializer, ITypeInspector typeDescriptor, ITypeResolver typeResolver, int maxRecursion, INamingConvention? namingConvention)
- {
- if (maxRecursion <= 0)
- {
- throw new ArgumentOutOfRangeException(nameof(maxRecursion), maxRecursion, "maxRecursion must be greater than 1");
- }
+ // private readonly Dictionary, Action, ObjectSerializer>> _behaviorCache = new();
+ private readonly Dictionary, Action, ObjectSerializer>> _traverseGenericDictionaryCache = new();
+
+ protected YamlSerializer Serializer { get; }
- ArgumentNullException.ThrowIfNull(typeDescriptor);
- ArgumentNullException.ThrowIfNull(typeResolver);
+ public FullObjectGraphTraversalStrategy(YamlSerializer serializer, ITypeInspector typeDescriptor, ITypeResolver typeResolver, int maxRecursion, INamingConvention namingConvention, IObjectFactory objectFactory)
+ {
+ ArgumentOutOfRangeException.ThrowIfNegativeOrZero(maxRecursion);
Serializer = serializer;
+
_typeDescriptor = typeDescriptor;
_typeResolver = typeResolver;
+
_maxRecursion = maxRecursion;
- _namingConvention = namingConvention ?? NullNamingConvention.Instance;
+ _namingConvention = namingConvention;
+ _objectFactory = objectFactory;
}
- void IObjectGraphTraversalStrategy.Traverse(IObjectDescriptor graph, IObjectGraphVisitor visitor, TContext context)
+ void IObjectGraphTraversalStrategy.Traverse(IObjectDescriptor graph, IObjectGraphVisitor visitor, TContext context, ObjectSerializer serializer)
{
- Traverse(graph, visitor, 0, context);
+ Traverse(null, "", graph, visitor, context, new Stack(_maxRecursion), serializer);
}
- protected virtual void Traverse(IObjectDescriptor value, IObjectGraphVisitor visitor, int currentDepth, TContext context)
+ protected virtual void Traverse(IPropertyDescriptor? propertyDescriptor, object name, IObjectDescriptor value, IObjectGraphVisitor visitor, TContext context, Stack path, ObjectSerializer serializer)
{
- if (++currentDepth > _maxRecursion)
+ if (path.Count >= _maxRecursion)
{
- throw new InvalidOperationException("Too much recursion when traversing the object graph");
+ var message = new StringBuilder();
+ message.AppendLine("Too much recursion when traversing the object graph.");
+ message.AppendLine("The path to reach this recursion was:");
+
+ var lines = new Stack>(path.Count);
+ var maxNameLength = 0;
+ foreach (var segment in path)
+ {
+ var segmentName = segment.Name?.ToString() ?? string.Empty;
+ maxNameLength = Math.Max(maxNameLength, segmentName.Length);
+ lines.Push(new KeyValuePair(segmentName, segment.Value.Type.FullName!));
+ }
+
+ foreach (var line in lines)
+ {
+ message
+ .Append(" -> ")
+ .Append(line.Key.PadRight(maxNameLength))
+ .Append(" [")
+ .Append(line.Value)
+ .AppendLine("]");
+ }
+
+ throw new MaximumRecursionLevelReachedException(message.ToString());
}
- if (!visitor.Enter(value, context))
+ if (!visitor.Enter(propertyDescriptor, value, context, serializer))
{
return;
}
- var typeCode = Type.GetTypeCode(value.Type);
- switch (typeCode)
+ path.Push(new ObjectPathSegment(name, value));
+
+ try
{
- case TypeCode.Boolean:
- case TypeCode.Byte:
- case TypeCode.Int16:
- case TypeCode.Int32:
- case TypeCode.Int64:
- case TypeCode.SByte:
- case TypeCode.UInt16:
- case TypeCode.UInt32:
- case TypeCode.UInt64:
- case TypeCode.Single:
- case TypeCode.Double:
- case TypeCode.Decimal:
- case TypeCode.String:
- case TypeCode.Char:
- case TypeCode.DateTime:
- visitor.VisitScalar(value, context);
- break;
- case TypeCode.DBNull:
- visitor.VisitScalar(new BetterObjectDescriptor(null, typeof(object), typeof(object)), context);
- break;
- case TypeCode.Empty:
- throw new NotSupportedException(string.Format(CultureInfo.InvariantCulture, "TypeCode.{0} is not supported.", typeCode));
- default:
- if (value.Value == null || value.Type == typeof(TimeSpan))
- {
- visitor.VisitScalar(value, context);
+ var typeCode = Type.GetTypeCode(value.Type);
+ switch (typeCode)
+ {
+ case TypeCode.Boolean:
+ case TypeCode.Byte:
+ case TypeCode.Int16:
+ case TypeCode.Int32:
+ case TypeCode.Int64:
+ case TypeCode.SByte:
+ case TypeCode.UInt16:
+ case TypeCode.UInt32:
+ case TypeCode.UInt64:
+ case TypeCode.Single:
+ case TypeCode.Double:
+ case TypeCode.Decimal:
+ case TypeCode.String:
+ case TypeCode.Char:
+ case TypeCode.DateTime:
+ visitor.VisitScalar(value, context, serializer);
+ break;
+
+ case TypeCode.DBNull:
+ visitor.VisitScalar(new BetterObjectDescriptor(null, typeof(object), typeof(object)), context, serializer);
+ break;
+
+ case TypeCode.Empty:
+ throw new NotSupportedException($"TypeCode.{typeCode} is not supported.");
+
+ case TypeCode.Object:
+ default:
+ if (value.Value == null || value.Type == typeof(TimeSpan))
+ {
+ visitor.VisitScalar(value, context, serializer);
+ break;
+ }
+
+ var underlyingType = Nullable.GetUnderlyingType(value.Type);
+ if (underlyingType != null)
+ {
+ // This is a nullable type, recursively handle it with its underlying type.
+ // Note that if it contains null, the condition above already took care of it
+ Traverse(
+ propertyDescriptor,
+ "Value",
+ new BetterObjectDescriptor(value.Value, underlyingType, value.Type, value.ScalarStyle),
+ visitor,
+ context,
+ path,
+ serializer
+ );
+ }
+ else
+ {
+ TraverseObject(propertyDescriptor, value, visitor, context, path, serializer);
+ }
break;
- }
-
- var underlyingType = Nullable.GetUnderlyingType(value.Type);
- if (underlyingType != null)
- {
- // This is a nullable type, recursively handle it with its underlying type.
- // Note that if it contains null, the condition above already took care of it
- Traverse(new BetterObjectDescriptor(value.Value, underlyingType, value.Type, value.ScalarStyle), visitor, currentDepth, context);
- }
- else
- {
- TraverseObject(value, visitor, currentDepth, context);
- }
- break;
+ }
+ }
+ finally
+ {
+ path.Pop();
}
}
- protected virtual void TraverseObject(IObjectDescriptor value, IObjectGraphVisitor visitor, int currentDepth, TContext context)
+ protected virtual void TraverseObject(
+ IPropertyDescriptor? propertyDescriptor,
+ IObjectDescriptor value,
+ IObjectGraphVisitor visitor,
+ TContext context,
+ Stack path,
+ ObjectSerializer serializer)
{
+ Debug.Assert(context != null);
+
var key = Tuple.Create(value.Type, typeof(TContext));
- if (!_behaviorCache.TryGetValue(key, out var action))
+
+ if (typeof(IDictionary).IsAssignableFrom(value.Type))
{
- if (typeof(IDictionary).IsAssignableFrom(value.Type))
- {
- action = TraverseDictionary;
- }
- else
- {
- var dictionaryType = ReflectionUtility.GetImplementedGenericInterface(value.Type, typeof(IDictionary<,>));
- if (dictionaryType != null)
- {
- action = (v, vi, d, c) => TraverseGenericDictionary(v, dictionaryType, vi, d, c);
- }
- else if (typeof(IEnumerable).IsAssignableFrom(value.Type))
- {
- action = TraverseList;
- }
- else
- {
- action = TraverseProperties;
- }
- }
- _behaviorCache[key] = action;
+ TraverseDictionary(propertyDescriptor, value, visitor, typeof(object), typeof(object), context, path, serializer);
+ return;
+ }
+
+ if (_objectFactory.GetDictionary(value, out var adaptedDictionary, out var genericArguments))
+ {
+ Debug.Assert(genericArguments != null);
+
+ var objectDescriptor = new ObjectDescriptor(adaptedDictionary, value.Type, value.StaticType, value.ScalarStyle);
+ var dictionaryKeyType = genericArguments[0];
+ var dictionaryValueType = genericArguments[1];
+
+ TraverseDictionary(propertyDescriptor, objectDescriptor, visitor, dictionaryKeyType, dictionaryValueType, context, path, serializer);
+ return;
+ }
+
+ if (typeof(IEnumerable).IsAssignableFrom(value.Type))
+ {
+ TraverseList(propertyDescriptor, value, visitor, context, path, serializer);
+ return;
}
- action(value, visitor, currentDepth, context!);
+
+ TraverseProperties(value, visitor, context, path, serializer);
}
- protected virtual void TraverseDictionary(IObjectDescriptor dictionary, object visitor, int currentDepth, object context)
+ protected virtual void TraverseDictionary(
+ IPropertyDescriptor? propertyDescriptor,
+ IObjectDescriptor dictionary,
+ IObjectGraphVisitor visitor,
+ Type keyType,
+ Type valueType,
+ TContext context,
+ Stack path,
+ ObjectSerializer serializer)
{
- var v = (IObjectGraphVisitor)visitor;
- var c = (TContext)context;
- v.VisitMappingStart(dictionary, typeof(object), typeof(object), c);
+ visitor.VisitMappingStart(dictionary, keyType, valueType, context, serializer);
- foreach (DictionaryEntry entry in (IDictionary)dictionary.NonNullValue())
+ var isDynamic = dictionary.Type.FullName!.Equals("System.Dynamic.ExpandoObject");
+ foreach (DictionaryEntry? entry in (IDictionary)dictionary.NonNullValue())
{
- var key = GetObjectDescriptor(entry.Key, typeof(object));
- var value = GetObjectDescriptor(entry.Value, typeof(object));
+ var entryValue = entry!.Value;
+ var keyValue = isDynamic ? _namingConvention.Apply(entryValue.Key.ToString()!) : entryValue.Key;
+ var key = GetObjectDescriptor(keyValue, keyType);
+ var value = GetObjectDescriptor(entryValue.Value, valueType);
- if (v.EnterMapping(key, value, c))
+ if (visitor.EnterMapping(key, value, context, serializer))
{
- Traverse(key, v, currentDepth, c);
- Traverse(value, v, currentDepth, c);
+ Traverse(propertyDescriptor, keyValue, key, visitor, context, path, serializer);
+ Traverse(propertyDescriptor, keyValue, value, visitor, context, path, serializer);
}
}
- v.VisitMappingEnd(dictionary, c);
+ visitor.VisitMappingEnd(dictionary, context, serializer);
}
- private void TraverseGenericDictionary(IObjectDescriptor dictionary, Type dictionaryType, IObjectGraphVisitor visitor, int currentDepth, IObjectGraphVisitorContext context)
+ private void TraverseGenericDictionary(
+ IObjectDescriptor dictionary,
+ Type dictionaryType,
+ IObjectGraphVisitor visitor,
+ TContext context,
+ Stack path,
+ ObjectSerializer serializer)
{
- var v = (IObjectGraphVisitor)visitor;
- var c = (TContext)context;
+ Debug.Assert(dictionary.Value != null);
+
var entryTypes = dictionaryType.GetGenericArguments();
// dictionaryType is IDictionary
- v.VisitMappingStart(dictionary, entryTypes[0], entryTypes[1], c);
+ visitor.VisitMappingStart(dictionary, entryTypes[0], entryTypes[1], context, serializer);
var key = Tuple.Create(entryTypes[0], entryTypes[1], typeof(TContext));
if (!_traverseGenericDictionaryCache.TryGetValue(key, out var action))
@@ -178,14 +248,26 @@ private void TraverseGenericDictionary(IObjectDescriptor dictionary, T
action = GetTraverseGenericDictionaryHelper(entryTypes[0], entryTypes[1], typeof(TContext));
_traverseGenericDictionaryCache[key] = action;
}
- action(this, dictionary.Value, v, currentDepth, _namingConvention, c);
+ action(this, dictionary.Value, visitor, _namingConvention ?? NullNamingConvention.Instance, context!, path, serializer);
- v.VisitMappingEnd(dictionary, c);
+ visitor.VisitMappingEnd(dictionary, context, serializer);
}
- private static Action GetTraverseGenericDictionaryHelper(Type tkey, Type tvalue, Type tcontext)
+ private static Action, ObjectSerializer> GetTraverseGenericDictionaryHelper(Type tkey, Type tvalue, Type tcontext)
{
- var dm = new DynamicMethod(string.Empty, typeof(void), [typeof(FullObjectGraphTraversalStrategy), typeof(object), typeof(IObjectGraphVisitor), typeof(int), typeof(INamingConvention), typeof(IObjectGraphVisitorContext)]);
+ var dm = new DynamicMethod(
+ string.Empty,
+ returnType: typeof(void),
+ parameterTypes:
+ [
+ typeof(FullObjectGraphTraversalStrategy),
+ typeof(object),
+ typeof(IObjectGraphVisitor),
+ typeof(INamingConvention),
+ typeof(IObjectGraphVisitorContext),
+ typeof(Stack),
+ typeof(ObjectSerializer),
+ ]);
var il = dm.GetILGenerator();
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldarg_1);
@@ -196,77 +278,116 @@ private void TraverseGenericDictionary(IObjectDescriptor dictionary, T
il.Emit(OpCodes.Ldarg_S, (byte)5);
il.Emit(OpCodes.Call, TraverseGenericDictionaryHelperMethod.MakeGenericMethod(tkey, tvalue, tcontext));
il.Emit(OpCodes.Ret);
- return (Action)dm.CreateDelegate(typeof(Action));
+ return (Action, ObjectSerializer>)dm.CreateDelegate(typeof(Action, ObjectSerializer>));
}
[EditorBrowsable(EditorBrowsableState.Never)]
- public static void TraverseGenericDictionaryHelper(
+ private static void TraverseGenericDictionaryHelper(
FullObjectGraphTraversalStrategy self,
+ IPropertyDescriptor propertyDescriptor,
IDictionary dictionary,
- IObjectGraphVisitor visitor,
- int currentDepth,
+ IObjectGraphVisitor visitor,
INamingConvention namingConvention,
- IObjectGraphVisitorContext context)
+ TContext context,
+ Stack paths,
+ ObjectSerializer serializer)
{
- var v = (IObjectGraphVisitor)visitor;
- var c = (TContext)context;
+
var isDynamic = dictionary.GetType().FullName!.Equals("System.Dynamic.ExpandoObject");
+
foreach (var entry in dictionary)
{
- var keyString = isDynamic ? namingConvention.Apply(entry.Key!.ToString()!) : entry.Key!.ToString();
+ Debug.Assert(entry.Key != null);
+ Debug.Assert(entry.Value != null);
+
+ var keyString = isDynamic
+ ? namingConvention.Apply(entry.Key!.ToString()!)!
+ : entry.Key.ToString()!;
var key = self.GetObjectDescriptor(keyString, typeof(TKey));
var value = self.GetObjectDescriptor(entry.Value, typeof(TValue));
- if (v.EnterMapping(key, value, c))
+ if (visitor.EnterMapping(key, value, context, serializer))
{
- self.Traverse(key, v, currentDepth, c);
- self.Traverse(value, v, currentDepth, c);
+ self.Traverse(propertyDescriptor, propertyDescriptor.Name, key, visitor, context, paths, serializer);
+ self.Traverse(propertyDescriptor, propertyDescriptor.Name, key, visitor, context, paths, serializer);
}
}
}
- private void TraverseList(IObjectDescriptor value, IObjectGraphVisitor visitor, int currentDepth, IObjectGraphVisitorContext context)
+ private void TraverseList(
+ IPropertyDescriptor? propertyDescriptor,
+ IObjectDescriptor value,
+ IObjectGraphVisitor visitor,
+ TContext context,
+ Stack path,
+ ObjectSerializer serializer)
{
- var v = (IObjectGraphVisitor)visitor;
- var c = (TContext)context;
- var enumerableType = ReflectionUtility.GetImplementedGenericInterface(value.Type, typeof(IEnumerable<>));
- var itemType = enumerableType != null ?
- enumerableType.GetGenericArguments()[0]
- : typeof(object);
+ var itemType = _objectFactory.GetValueType(value.Type);
- v.VisitSequenceStart(value, itemType, c);
+ visitor.VisitSequenceStart(value, itemType, context, serializer);
+
+ var index = 0;
foreach (var item in (IEnumerable)value.NonNullValue())
{
- Traverse(GetObjectDescriptor(item, itemType), v, currentDepth, c);
+ Traverse(propertyDescriptor, index, GetObjectDescriptor(item, itemType), visitor, context, path, serializer);
+ ++index;
}
- v.VisitSequenceEnd(value, c);
+ visitor.VisitSequenceEnd(value, context, serializer);
}
- protected virtual void TraverseProperties(IObjectDescriptor value, IObjectGraphVisitor visitor, int currentDepth, IObjectGraphVisitorContext context)
+ protected virtual void TraverseProperties(
+ IObjectDescriptor value,
+ IObjectGraphVisitor visitor,
+ TContext context,
+ Stack path,
+ ObjectSerializer serializer)
{
- var v = (IObjectGraphVisitor)visitor;
- var c = (TContext)context;
- v.VisitMappingStart(value, typeof(string), typeof(object), c);
+ Debug.Assert(visitor != null);
+ Debug.Assert(context != null);
+ Debug.Assert(value.Value != null);
+
+ if (context.GetType() != typeof(Nothing))
+ {
+ _objectFactory.ExecuteOnSerializing(value.Value);
+ }
+
+ visitor.VisitMappingStart(value, typeof(string), typeof(object), context, serializer);
var source = value.NonNullValue();
- foreach (var propertyDescriptor in _typeDescriptor.GetProperties(value.Type, value.Value))
+ foreach (var propertyDescriptor in _typeDescriptor.GetProperties(value.Type, source))
{
var propertyValue = propertyDescriptor.Read(source);
-
- if (v.EnterMapping(propertyDescriptor, propertyValue, c))
+ if (visitor.EnterMapping(propertyDescriptor, propertyValue, context, serializer))
{
- Traverse(new BetterObjectDescriptor(propertyDescriptor.Name, typeof(string), typeof(string)), v, currentDepth, c);
- Traverse(propertyValue, v, currentDepth, c);
+ Traverse(null, propertyDescriptor.Name, new BetterObjectDescriptor(propertyDescriptor.Name, typeof(string), typeof(string), ScalarStyle.Plain), visitor, context, path, serializer);
+ Traverse(propertyDescriptor, propertyDescriptor.Name, propertyValue, visitor, context, path, serializer);
}
}
- v.VisitMappingEnd(value, c);
+ visitor.VisitMappingEnd(value, context, serializer);
+
+ if (context.GetType() != typeof(Nothing))
+ {
+ _objectFactory.ExecuteOnSerialized(value.Value);
+ }
}
private IObjectDescriptor GetObjectDescriptor(object? value, Type staticType)
{
return new BetterObjectDescriptor(value, _typeResolver.Resolve(staticType, value), staticType);
}
+
+ protected internal readonly struct ObjectPathSegment
+ {
+ public readonly object Name;
+ public readonly IObjectDescriptor Value;
+
+ public ObjectPathSegment(object name, IObjectDescriptor value)
+ {
+ this.Name = name;
+ this.Value = value;
+ }
+ }
}
diff --git a/src/Docfx.YamlSerialization/ObjectGraphTraversalStrategies/RoundtripObjectGraphTraversalStrategy.cs b/src/Docfx.YamlSerialization/ObjectGraphTraversalStrategies/RoundtripObjectGraphTraversalStrategy.cs
index 3e53a587526..53b0a95facc 100644
--- a/src/Docfx.YamlSerialization/ObjectGraphTraversalStrategies/RoundtripObjectGraphTraversalStrategy.cs
+++ b/src/Docfx.YamlSerialization/ObjectGraphTraversalStrategies/RoundtripObjectGraphTraversalStrategy.cs
@@ -1,11 +1,8 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-using System.Globalization;
using Docfx.YamlSerialization.Helpers;
using YamlDotNet.Serialization;
-using IObjectGraphVisitor = System.Object;
-using IObjectGraphVisitorContext = System.Object;
namespace Docfx.YamlSerialization.ObjectGraphTraversalStrategies;
@@ -16,18 +13,33 @@ namespace Docfx.YamlSerialization.ObjectGraphTraversalStrategies;
///
public class RoundtripObjectGraphTraversalStrategy : FullObjectGraphTraversalStrategy
{
- public RoundtripObjectGraphTraversalStrategy(YamlSerializer serializer, ITypeInspector typeDescriptor, ITypeResolver typeResolver, int maxRecursion)
- : base(serializer, typeDescriptor, typeResolver, maxRecursion, null)
+ private readonly TypeConverterCache _converters;
+ private readonly Settings _settings;
+
+ public RoundtripObjectGraphTraversalStrategy(IEnumerable converters, YamlSerializer serializer, ITypeInspector typeDescriptor, ITypeResolver typeResolver, int maxRecursion, INamingConvention namingConvention, Settings settings, IObjectFactory objectFactory)
+ : base(serializer, typeDescriptor, typeResolver, maxRecursion, namingConvention, objectFactory)
{
+ _converters = new TypeConverterCache(converters);
+ _settings = settings;
}
- protected override void TraverseProperties(IObjectDescriptor value, IObjectGraphVisitor visitor, int currentDepth, IObjectGraphVisitorContext context)
+ ////protected override void TraverseProperties(IObjectDescriptor value, IObjectGraphVisitor visitor, int currentDepth, IObjectGraphVisitorContext context)
+ ////{
+ //// if (!value.Type.HasDefaultConstructor() && !Serializer.Converters.Any(c => c.Accepts(value.Type)))
+ //// {
+ //// throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, "Type '{0}' cannot be deserialized because it does not have a default constructor or a type converter.", value.Type));
+ //// }
+
+ //// base.TraverseProperties(value, visitor, currentDepth, context);
+ ////}
+
+ protected override void TraverseProperties(IObjectDescriptor value, IObjectGraphVisitor visitor, TContext context, Stack path, ObjectSerializer serializer)
{
- if (!value.Type.HasDefaultConstructor() && !Serializer.Converters.Any(c => c.Accepts(value.Type)))
+ if (!value.Type.HasDefaultConstructor(_settings.AllowPrivateConstructors) && !_converters.TryGetConverterForType(value.Type, out _))
{
- throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, "Type '{0}' cannot be deserialized because it does not have a default constructor or a type converter.", value.Type));
+ throw new InvalidOperationException($"Type '{value.Type}' cannot be deserialized because it does not have a default constructor or a type converter.");
}
- base.TraverseProperties(value, visitor, currentDepth, context);
+ base.TraverseProperties(value, visitor, context, path, serializer);
}
}
diff --git a/src/Docfx.YamlSerialization/ObjectGraphVisitors/ExclusiveObjectGraphVisitor.cs b/src/Docfx.YamlSerialization/ObjectGraphVisitors/ExclusiveObjectGraphVisitor.cs
index 0afe1efc7ee..a26e4e67304 100644
--- a/src/Docfx.YamlSerialization/ObjectGraphVisitors/ExclusiveObjectGraphVisitor.cs
+++ b/src/Docfx.YamlSerialization/ObjectGraphVisitors/ExclusiveObjectGraphVisitor.cs
@@ -23,13 +23,13 @@ public ExclusiveObjectGraphVisitor(IObjectGraphVisitor nextVisitor)
return type.IsValueType ? Activator.CreateInstance(type) : null;
}
- public override bool EnterMapping(IPropertyDescriptor key, IObjectDescriptor value, IEmitter context)
+ public override bool EnterMapping(IPropertyDescriptor key, IObjectDescriptor value, IEmitter context, ObjectSerializer serializer)
{
var defaultValueAttribute = key.GetCustomAttribute();
object? defaultValue = defaultValueAttribute != null
? defaultValueAttribute.Value
: GetDefault(key.Type);
- return !Equals(value.Value, defaultValue) && base.EnterMapping(key, value, context);
+ return !Equals(value.Value, defaultValue) && base.EnterMapping(key, value, context, serializer);
}
}
diff --git a/src/Docfx.YamlSerialization/TypeInspectors/EmitTypeInspector.cs b/src/Docfx.YamlSerialization/TypeInspectors/EmitTypeInspector.cs
index bd893e48aed..1dbbc65e60d 100644
--- a/src/Docfx.YamlSerialization/TypeInspectors/EmitTypeInspector.cs
+++ b/src/Docfx.YamlSerialization/TypeInspectors/EmitTypeInspector.cs
@@ -4,6 +4,7 @@
using System.Collections.Concurrent;
using System.Reflection;
using System.Reflection.Emit;
+using System.Runtime.Serialization;
using Docfx.YamlSerialization.Helpers;
using Docfx.YamlSerialization.ObjectDescriptors;
using YamlDotNet.Core;
@@ -15,6 +16,8 @@ public class EmitTypeInspector : ExtensibleTypeInspectorSkeleton
{
private static readonly ConcurrentDictionary _cache = new();
private static readonly ConcurrentDictionary> _propertyDescriptorCache = new();
+ private static readonly ConcurrentDictionary<(Type, string), string> _enumNameCache = new();
+ private static readonly ConcurrentDictionary<(Type, string), string> _enumValueCache = new();
private readonly ITypeResolver _resolver;
public EmitTypeInspector(ITypeResolver resolver)
@@ -62,6 +65,58 @@ where name.StartsWith(ep.Prefix, StringComparison.Ordinal)
select new ExtensiblePropertyDescriptor(ep, name, _resolver)).FirstOrDefault();
}
+ // This code is based on ReflectionTypeInspector implementation(See: https://github.com/aaubry/YamlDotNet/blob/master/YamlDotNet/Serialization/TypeInspectors/ReflectionTypeInspector.cs)
+ public override string GetEnumName(Type enumType, string name)
+ {
+ var key = (enumType, name);
+ if (_enumNameCache.TryGetValue(key, out var result))
+ return result;
+
+ // Try to gets enum name from EnumMemberAttribute and resolve enum name.
+ foreach (var enumMember in enumType.GetMembers())
+ {
+ var attribute = enumMember.GetCustomAttribute(inherit: false);
+ if (attribute != null && attribute.Value == name)
+ {
+ name = enumMember.Name;
+ break;
+ }
+ }
+
+ // Add resolved name to cache
+ _enumNameCache.TryAdd(key, name);
+ return name;
+ }
+
+ public override string GetEnumValue(object enumValue)
+ {
+ var enumType = enumValue.GetType();
+ var valueText = enumValue.ToString()!;
+ var key = (enumType, valueText);
+
+ if (_enumValueCache.TryGetValue(key, out var result))
+ return result;
+
+ // Try to gets enum value from EnumMemberAttribute and resolve enum value.
+ if (enumType.GetCustomAttribute() != null)
+ {
+ var enumMember = enumType.GetMember(valueText).FirstOrDefault();
+ if (enumMember != null)
+ {
+ var attribute = enumMember.GetCustomAttribute(inherit: false);
+ if (attribute?.Value != null)
+ {
+ valueText = attribute.Value;
+ }
+ }
+ }
+
+ // Add resolved text to cache.
+ _enumValueCache.TryAdd(key, valueText);
+
+ return valueText;
+ }
+
private sealed class CachingItem
{
private CachingItem() { }
@@ -401,6 +456,12 @@ public EmitPropertyDescriptor(EmitPropertyDescriptorSkeleton skeleton, ITypeReso
public Type? TypeOverride { get; set; }
+ public bool AllowNulls { get; set; }
+
+ public bool Required { get; set; }
+
+ public Type? ConverterType { get; set; }
+
public T GetCustomAttribute() where T : Attribute => (T)_skeleton.GetCustomAttribute(typeof(T));
public IObjectDescriptor Read(object target)
@@ -461,6 +522,12 @@ public ExtensiblePropertyDescriptor(
public Type? TypeOverride { get; set; }
+ public bool AllowNulls { get; set; }
+
+ public bool Required { get; set; }
+
+ public Type? ConverterType { get; set; }
+
public T? GetCustomAttribute() where T : Attribute => null;
public IObjectDescriptor Read(object target)
diff --git a/src/Docfx.YamlSerialization/TypeInspectors/ExtensibleNamingConventionTypeInspector.cs b/src/Docfx.YamlSerialization/TypeInspectors/ExtensibleNamingConventionTypeInspector.cs
index aeb619b4593..f7fa3fd3eaa 100644
--- a/src/Docfx.YamlSerialization/TypeInspectors/ExtensibleNamingConventionTypeInspector.cs
+++ b/src/Docfx.YamlSerialization/TypeInspectors/ExtensibleNamingConventionTypeInspector.cs
@@ -25,4 +25,10 @@ from p in innerTypeDescriptor.GetProperties(type, container)
public override IPropertyDescriptor? GetProperty(Type type, object? container, string name) =>
innerTypeDescriptor.GetProperty(type, container, name);
+
+ public override string GetEnumName(Type enumType, string name) =>
+ innerTypeDescriptor.GetEnumName(enumType, name);
+
+ public override string GetEnumValue(object enumValue) =>
+ innerTypeDescriptor.GetEnumValue(enumValue);
}
diff --git a/src/Docfx.YamlSerialization/TypeInspectors/ExtensibleReadableAndWritablePropertiesTypeInspector.cs b/src/Docfx.YamlSerialization/TypeInspectors/ExtensibleReadableAndWritablePropertiesTypeInspector.cs
index 1407f50a52a..01ad2ab9f38 100644
--- a/src/Docfx.YamlSerialization/TypeInspectors/ExtensibleReadableAndWritablePropertiesTypeInspector.cs
+++ b/src/Docfx.YamlSerialization/TypeInspectors/ExtensibleReadableAndWritablePropertiesTypeInspector.cs
@@ -21,4 +21,10 @@ where p.CanWrite
public override IPropertyDescriptor? GetProperty(Type type, object? container, string name) =>
_innerTypeDescriptor.GetProperty(type, container, name);
+
+ public override string GetEnumName(Type enumType, string name) =>
+ _innerTypeDescriptor.GetEnumName(enumType, name);
+
+ public override string GetEnumValue(object enumValue) =>
+ _innerTypeDescriptor.GetEnumValue(enumValue);
}
diff --git a/src/Docfx.YamlSerialization/TypeInspectors/ExtensibleTypeInspectorSkeleton.cs b/src/Docfx.YamlSerialization/TypeInspectors/ExtensibleTypeInspectorSkeleton.cs
index 235855130aa..715bec3a57f 100644
--- a/src/Docfx.YamlSerialization/TypeInspectors/ExtensibleTypeInspectorSkeleton.cs
+++ b/src/Docfx.YamlSerialization/TypeInspectors/ExtensibleTypeInspectorSkeleton.cs
@@ -11,12 +11,11 @@ public abstract class ExtensibleTypeInspectorSkeleton : ITypeInspector, IExtensi
{
public abstract IEnumerable GetProperties(Type type, object? container);
- public IPropertyDescriptor GetProperty(Type type, object? container, string name, bool ignoreUnmatched)
+ public IPropertyDescriptor GetProperty(Type type, object? container, string name, bool ignoreUnmatched, bool caseInsensitivePropertyMatching)
{
- var candidates =
- from p in GetProperties(type, container)
- where p.Name == name
- select p;
+ var candidates = caseInsensitivePropertyMatching
+ ? GetProperties(type, container).Where(p => p.Name.Equals(name, StringComparison.OrdinalIgnoreCase))
+ : GetProperties(type, container).Where(p => p.Name == name);
using var enumerator = candidates.GetEnumerator();
if (!enumerator.MoveNext())
@@ -61,4 +60,8 @@ from p in GetProperties(type, container)
}
public virtual IPropertyDescriptor? GetProperty(Type type, object? container, string name) => null;
+
+ public abstract string GetEnumName(Type enumType, string name);
+
+ public abstract string GetEnumValue(object enumValue);
}
diff --git a/src/Docfx.YamlSerialization/TypeInspectors/ExtensibleYamlAttributesTypeInspector.cs b/src/Docfx.YamlSerialization/TypeInspectors/ExtensibleYamlAttributesTypeInspector.cs
index 21878ccdaa2..0fb61db306f 100644
--- a/src/Docfx.YamlSerialization/TypeInspectors/ExtensibleYamlAttributesTypeInspector.cs
+++ b/src/Docfx.YamlSerialization/TypeInspectors/ExtensibleYamlAttributesTypeInspector.cs
@@ -48,4 +48,10 @@ public override IEnumerable GetProperties(Type type, object
public override IPropertyDescriptor? GetProperty(Type type, object? container, string name) =>
innerTypeDescriptor.GetProperty(type, container, name);
+
+ public override string GetEnumName(Type enumType, string name) =>
+ innerTypeDescriptor.GetEnumName(enumType, name);
+
+ public override string GetEnumValue(object enumValue) =>
+ innerTypeDescriptor.GetEnumValue(enumValue);
}
diff --git a/src/Docfx.YamlSerialization/YamlDeserializer.cs b/src/Docfx.YamlSerialization/YamlDeserializer.cs
index 221e8b70d56..71d6fe8f571 100644
--- a/src/Docfx.YamlSerialization/YamlDeserializer.cs
+++ b/src/Docfx.YamlSerialization/YamlDeserializer.cs
@@ -52,20 +52,32 @@ public IEnumerable GetProperties(Type type, object? contain
return TypeDescriptor.GetProperties(type, container);
}
- public IPropertyDescriptor GetProperty(Type type, object? container, string name, bool ignoreUnmatched)
+ public IPropertyDescriptor GetProperty(Type type, object? container, string name, bool ignoreUnmatched, bool caseInsensitivePropertyMatching)
{
- return TypeDescriptor.GetProperty(type, container, name, ignoreUnmatched);
+ return TypeDescriptor.GetProperty(type, container, name, ignoreUnmatched, caseInsensitivePropertyMatching);
+ }
+
+ public string GetEnumName(Type enumType, string name)
+ {
+ return TypeDescriptor.GetEnumName(enumType, name);
+ }
+ public string GetEnumValue(object enumValue)
+ {
+ return TypeDescriptor.GetEnumValue(enumValue);
}
}
public YamlDeserializer(
IObjectFactory? objectFactory = null,
INamingConvention? namingConvention = null,
+ INamingConvention? enumNamingConvention = null,
bool ignoreUnmatched = false,
- bool ignoreNotFoundAnchor = true)
+ bool ignoreNotFoundAnchor = true,
+ bool caseInsensitivePropertyMatching = false)
{
objectFactory ??= new DefaultEmitObjectFactory();
namingConvention ??= NullNamingConvention.Instance;
+ enumNamingConvention ??= NullNamingConvention.Instance;
_typeDescriptor.TypeDescriptor =
new ExtensibleYamlAttributesTypeInspector(
@@ -89,24 +101,24 @@ public YamlDeserializer(
[
new TypeConverterNodeDeserializer(_converters),
new NullNodeDeserializer(),
- new ScalarNodeDeserializer(attemptUnknownTypeDeserialization: false, _reflectionTypeConverter, YamlFormatter.Default, NullNamingConvention.Instance),
- new EmitArrayNodeDeserializer(),
+ new ScalarNodeDeserializer(attemptUnknownTypeDeserialization: false, _reflectionTypeConverter, _typeDescriptor, YamlFormatter.Default, enumNamingConvention),
+ new EmitArrayNodeDeserializer(enumNamingConvention,_typeDescriptor),
new EmitGenericDictionaryNodeDeserializer(objectFactory),
new DictionaryNodeDeserializer(objectFactory, duplicateKeyChecking: true),
- new EmitGenericCollectionNodeDeserializer(objectFactory),
- new CollectionNodeDeserializer(objectFactory, NullNamingConvention.Instance),
+ new EmitGenericCollectionNodeDeserializer(objectFactory, enumNamingConvention,_typeDescriptor),
+ new CollectionNodeDeserializer(objectFactory, enumNamingConvention, _typeDescriptor),
new EnumerableNodeDeserializer(),
- new ExtensibleObjectNodeDeserializer(objectFactory, _typeDescriptor, ignoreUnmatched)
+ new ExtensibleObjectNodeDeserializer(objectFactory, _typeDescriptor, enumNamingConvention, ignoreUnmatched, caseInsensitivePropertyMatching),
];
_tagMappings = new Dictionary(PredefinedTagMappings);
TypeResolvers =
[
new TagNodeTypeResolver(_tagMappings),
new DefaultContainersNodeTypeResolver(),
- new ScalarYamlNodeTypeResolver()
+ new ScalarYamlNodeTypeResolver(),
];
- NodeValueDeserializer nodeValueDeserializer = new(NodeDeserializers, TypeResolvers, _reflectionTypeConverter, NullNamingConvention.Instance);
+ NodeValueDeserializer nodeValueDeserializer = new(NodeDeserializers, TypeResolvers, _reflectionTypeConverter, enumNamingConvention, _typeDescriptor);
if (ignoreNotFoundAnchor)
{
_valueDeserializer = new LooseAliasValueDeserializer(nodeValueDeserializer);
diff --git a/src/Docfx.YamlSerialization/YamlSerializer.cs b/src/Docfx.YamlSerialization/YamlSerializer.cs
index 4c2c6170481..22bc0001268 100644
--- a/src/Docfx.YamlSerialization/YamlSerializer.cs
+++ b/src/Docfx.YamlSerialization/YamlSerializer.cs
@@ -11,6 +11,7 @@
using YamlDotNet.Serialization;
using YamlDotNet.Serialization.EventEmitters;
using YamlDotNet.Serialization.NamingConventions;
+using YamlDotNet.Serialization.ObjectFactories;
using YamlDotNet.Serialization.ObjectGraphVisitors;
using YamlDotNet.Serialization.TypeInspectors;
using YamlDotNet.Serialization.TypeResolvers;
@@ -23,11 +24,15 @@ public class YamlSerializer
private readonly SerializationOptions _options;
private readonly INamingConvention _namingConvention;
private readonly ITypeResolver _typeResolver;
+ private readonly ITypeInspector _typeDescriptor;
+ private readonly IObjectFactory _objectFactory;
+ private readonly Settings _settings;
- public YamlSerializer(SerializationOptions options = SerializationOptions.None, INamingConvention? namingConvention = null)
+ public YamlSerializer(SerializationOptions options = SerializationOptions.None, INamingConvention? namingConvention = null, Settings? settings = null)
{
_options = options;
_namingConvention = namingConvention ?? NullNamingConvention.Instance;
+ _settings = settings ?? new Settings();
Converters = new List();
foreach (IYamlTypeConverter yamlTypeConverter in YamlTypeConverters.BuiltInConverters)
@@ -38,6 +43,9 @@ public YamlSerializer(SerializationOptions options = SerializationOptions.None,
_typeResolver = IsOptionSet(SerializationOptions.DefaultToStaticType)
? new StaticTypeResolver()
: new DynamicTypeResolver();
+
+ _typeDescriptor = CreateTypeInspector();
+ _objectFactory = new DefaultObjectFactory();
}
private bool IsOptionSet(SerializationOptions option)
@@ -66,7 +74,7 @@ private void EmitDocument(IEmitter emitter, IObjectDescriptor graph)
emitter.Emit(new StreamStart());
emitter.Emit(new DocumentStart());
- traversalStrategy.Traverse(graph, emittingVisitor, emitter);
+ traversalStrategy.Traverse(graph, emittingVisitor, emitter, GetObjectSerializer(emitter));
emitter.Emit(new DocumentEnd(true));
emitter.Emit(new StreamEnd());
@@ -76,6 +84,8 @@ private IObjectGraphVisitor CreateEmittingVisitor(IEmitter emitter, IO
{
IObjectGraphVisitor emittingVisitor = new EmittingObjectGraphVisitor(eventEmitter);
+ emittingVisitor = new CustomSerializationObjectGraphVisitor(emittingVisitor, Converters, GetObjectSerializer(emitter));
+
void nestedObjectSerializer(object? v, Type? t = null) => SerializeValue(emitter, v, t);
emittingVisitor = new CustomSerializationObjectGraphVisitor(emittingVisitor, Converters, nestedObjectSerializer);
@@ -83,7 +93,8 @@ private IObjectGraphVisitor CreateEmittingVisitor(IEmitter emitter, IO
if (!IsOptionSet(SerializationOptions.DisableAliases))
{
var anchorAssigner = new AnchorAssigner(Converters);
- traversalStrategy.Traverse(graph, anchorAssigner, default);
+
+ traversalStrategy.Traverse(graph, anchorAssigner, default, GetObjectSerializer(emitter));
emittingVisitor = new AnchorAssigningObjectGraphVisitor(emittingVisitor, eventEmitter, anchorAssigner);
}
@@ -110,7 +121,12 @@ public void SerializeValue(IEmitter emitter, object? value, Type? type)
graph
);
- traversalStrategy.Traverse(graph, emittingVisitor, emitter);
+ traversalStrategy.Traverse(graph, emittingVisitor, emitter, GetObjectSerializer(emitter));
+ }
+
+ private ObjectSerializer GetObjectSerializer(IEmitter emitter)
+ {
+ return (object? v, Type? t = null) => SerializeValue(emitter, v, t);
}
private IEventEmitter CreateEventEmitter()
@@ -119,7 +135,7 @@ private IEventEmitter CreateEventEmitter()
if (IsOptionSet(SerializationOptions.JsonCompatible))
{
- return new JsonEventEmitter(writer, YamlFormatter.Default, NullNamingConvention.Instance);
+ return new JsonEventEmitter(writer, YamlFormatter.Default, NullNamingConvention.Instance, _typeDescriptor);
}
else
{
@@ -130,29 +146,28 @@ private IEventEmitter CreateEventEmitter()
quoteYaml1_1Strings: false,
defaultScalarStyle: ScalarStyle.Any,
formatter: YamlFormatter.Default,
- enumNamingConvention: NullNamingConvention.Instance);
+ enumNamingConvention: NullNamingConvention.Instance,
+ typeInspector: _typeDescriptor);
}
}
private IObjectGraphTraversalStrategy CreateTraversalStrategy()
+ {
+ return IsOptionSet(SerializationOptions.Roundtrip)
+ ? new RoundtripObjectGraphTraversalStrategy(Converters, this, _typeDescriptor, _typeResolver, 50, _namingConvention, _settings, _objectFactory)
+ : new FullObjectGraphTraversalStrategy(this, _typeDescriptor, _typeResolver, 50, _namingConvention, _objectFactory);
+ }
+
+ private ITypeInspector CreateTypeInspector()
{
ITypeInspector typeDescriptor = new EmitTypeInspector(_typeResolver);
if (IsOptionSet(SerializationOptions.Roundtrip))
- {
typeDescriptor = new ReadableAndWritablePropertiesTypeInspector(typeDescriptor);
- }
typeDescriptor = new NamingConventionTypeInspector(typeDescriptor, _namingConvention);
typeDescriptor = new YamlAttributesTypeInspector(typeDescriptor);
- if (IsOptionSet(SerializationOptions.Roundtrip))
- {
- return new RoundtripObjectGraphTraversalStrategy(this, typeDescriptor, _typeResolver, 50);
- }
- else
- {
- return new FullObjectGraphTraversalStrategy(this, typeDescriptor, _typeResolver, 50, _namingConvention);
- }
+ return typeDescriptor;
}
}