From 1e4bf403160ddde004912894c4bc27cc1d8d0dd2 Mon Sep 17 00:00:00 2001 From: Edward Cooke Date: Sat, 2 Sep 2023 00:20:11 -0600 Subject: [PATCH] Added on onserialized/onserializing and ondeserialized/ondeserializing --- .../ClassObject.cs | 18 ++++-- .../ClassSyntaxReceiver.cs | 23 +++++++- .../StaticObjectFactoryFile.cs | 30 ++++++++++ YamlDotNet.Core7AoTCompileTest/Program.cs | 32 +++++++++-- .../Analyzers/StaticGenerator/ObjectTests.cs | 41 ++++++++++++++ .../Serialization/DeserializerTest.cs | 28 +++++++++- .../Serialization/SerializationTests.cs | 26 +++++++++ .../Callbacks/OnDeserializedAttribute.cs | 30 ++++++++++ .../Callbacks/OnDeserializingAttribute.cs | 30 ++++++++++ .../Callbacks/OnSerializedAttribute.cs | 30 ++++++++++ .../Callbacks/OnSerializingAttribute.cs | 30 ++++++++++ YamlDotNet/Serialization/IObjectFactory.cs | 24 ++++++++ .../ObjectNodeDeserializer.cs | 3 + .../ObjectFactories/DefaultObjectFactory.cs | 55 +++++++++++++++++++ .../ObjectFactories/ObjectFactoryBase.cs | 20 +++++++ .../ObjectFactories/StaticObjectFactory.cs | 8 +++ .../FullObjectGraphTraversalStrategy.cs | 11 ++++ 17 files changed, 427 insertions(+), 12 deletions(-) create mode 100644 YamlDotNet/Serialization/Callbacks/OnDeserializedAttribute.cs create mode 100644 YamlDotNet/Serialization/Callbacks/OnDeserializingAttribute.cs create mode 100644 YamlDotNet/Serialization/Callbacks/OnSerializedAttribute.cs create mode 100644 YamlDotNet/Serialization/Callbacks/OnSerializingAttribute.cs diff --git a/YamlDotNet.Analyzers.StaticGenerator/ClassObject.cs b/YamlDotNet.Analyzers.StaticGenerator/ClassObject.cs index 2cb04a67c..adaa04d41 100644 --- a/YamlDotNet.Analyzers.StaticGenerator/ClassObject.cs +++ b/YamlDotNet.Analyzers.StaticGenerator/ClassObject.cs @@ -30,12 +30,16 @@ public class ClassObject public List FieldSymbols { get; } public string FullName { get; } public string GuidSuffix { get; } - public ITypeSymbol ModuleSymbol { get; } - public List PropertySymbols { get; } - public string SanitizedClassName { get; } public bool IsArray { get; } public bool IsDictionary { get; } public bool IsList { get; } + public ITypeSymbol ModuleSymbol { get; } + public List OnDeserializedMethods { get; } + public List OnDeserializingMethods { get; } + public List OnSerializedMethods { get; } + public List OnSerializingMethods { get; } + public List PropertySymbols { get; } + public string SanitizedClassName { get; } public ClassObject(string sanitizedClassName, ITypeSymbol moduleSymbol, bool isDictionary = false, bool isList = false, bool isArray = false) { @@ -43,11 +47,15 @@ public ClassObject(string sanitizedClassName, ITypeSymbol moduleSymbol, bool isD PropertySymbols = new List(); FullName = moduleSymbol.GetFullName() ?? string.Empty; GuidSuffix = Guid.NewGuid().ToString("N"); - ModuleSymbol = moduleSymbol; - SanitizedClassName = sanitizedClassName; IsDictionary = isDictionary; IsList = isList; IsArray = isArray; + ModuleSymbol = moduleSymbol; + OnDeserializedMethods = new List(); + OnDeserializingMethods = new List(); + OnSerializedMethods = new List(); + OnSerializingMethods = new List(); + SanitizedClassName = sanitizedClassName; } } } diff --git a/YamlDotNet.Analyzers.StaticGenerator/ClassSyntaxReceiver.cs b/YamlDotNet.Analyzers.StaticGenerator/ClassSyntaxReceiver.cs index 589f9a790..fe654dd01 100644 --- a/YamlDotNet.Analyzers.StaticGenerator/ClassSyntaxReceiver.cs +++ b/YamlDotNet.Analyzers.StaticGenerator/ClassSyntaxReceiver.cs @@ -65,7 +65,8 @@ public void OnVisitSyntaxNode(GeneratorSyntaxContext context) foreach (var member in members) { if (member.IsStatic || - member.DeclaredAccessibility != Accessibility.Public || + (member.DeclaredAccessibility != Accessibility.Public && + member.DeclaredAccessibility != Accessibility.Internal) || member.GetAttributes().Any(x => x.AttributeClass!.ToDisplayString() == "YamlDotNet.Serialization.YamlIgnoreAttribute")) { continue; @@ -81,6 +82,26 @@ public void OnVisitSyntaxNode(GeneratorSyntaxContext context) classObject.FieldSymbols.Add(fieldSymbol); CheckForSupportedGeneric(fieldSymbol.Type); } + else if (member is IMethodSymbol methodSymbol) + { + var methodAttributes = methodSymbol.GetAttributes(); + if (methodAttributes.Any(x => x.AttributeClass!.ToDisplayString() == "YamlDotNet.Serialization.Callbacks.OnDeserializedAttribute")) + { + classObject.OnDeserializedMethods.Add(methodSymbol); + } + if (methodAttributes.Any(x => x.AttributeClass!.ToDisplayString() == "YamlDotNet.Serialization.Callbacks.OnDeserializingAttribute")) + { + classObject.OnDeserializingMethods.Add(methodSymbol); + } + if (methodAttributes.Any(x => x.AttributeClass!.ToDisplayString() == "YamlDotNet.Serialization.Callbacks.OnSerializedAttribute")) + { + classObject.OnSerializedMethods.Add(methodSymbol); + } + if (methodAttributes.Any(x => x.AttributeClass!.ToDisplayString() == "YamlDotNet.Serialization.Callbacks.OnSerializingAttribute")) + { + classObject.OnSerializingMethods.Add(methodSymbol); + } + } } classSymbol = classSymbol.BaseType; } diff --git a/YamlDotNet.Analyzers.StaticGenerator/StaticObjectFactoryFile.cs b/YamlDotNet.Analyzers.StaticGenerator/StaticObjectFactoryFile.cs index 44c6bb65c..60c5c5297 100644 --- a/YamlDotNet.Analyzers.StaticGenerator/StaticObjectFactoryFile.cs +++ b/YamlDotNet.Analyzers.StaticGenerator/StaticObjectFactoryFile.cs @@ -20,6 +20,7 @@ // SOFTWARE. using System; +using System.Collections.Generic; using System.Linq; using Microsoft.CodeAnalysis; @@ -168,7 +169,36 @@ public override void Write(ClassSyntaxReceiver classSyntaxReceiver) Write("throw new ArgumentOutOfRangeException(\"Unknown type: \" + type.ToString());"); UnIndent(); Write("}"); + WriteExecuteMethod(classSyntaxReceiver, "ExecuteOnDeserializing", (c) => c.OnDeserializingMethods); + WriteExecuteMethod(classSyntaxReceiver, "ExecuteOnDeserialized", (c) => c.OnDeserializedMethods); + WriteExecuteMethod(classSyntaxReceiver, "ExecuteOnSerializing", (c) => c.OnSerializingMethods); + WriteExecuteMethod(classSyntaxReceiver, "ExecuteOnSerialized", (c) => c.OnSerializedMethods); + UnIndent(); Write("}"); + } + private void WriteExecuteMethod(ClassSyntaxReceiver classSyntaxReceiver, string methodName, Func> selector) + { + Write($"public override void {methodName}(object value)"); + Write("{"); Indent(); + Write("if (value == null) return;"); + Write("var type = value.GetType();"); + foreach (var o in classSyntaxReceiver.Classes) + { + var classObject = o.Value; + var methods = selector(classObject); + if (methods.Any()) + { + var className = classObject.ModuleSymbol.GetFullName().Replace("?", string.Empty); + Write($"if (type == typeof({className}))"); + Write("{"); Indent(); + foreach (var m in methods) + { + Write($"(({className})value).{m.Name}();"); + } + Write("return;"); + UnIndent(); Write("}"); + } + } UnIndent(); Write("}"); } } diff --git a/YamlDotNet.Core7AoTCompileTest/Program.cs b/YamlDotNet.Core7AoTCompileTest/Program.cs index 6b07db503..87d5de1da 100644 --- a/YamlDotNet.Core7AoTCompileTest/Program.cs +++ b/YamlDotNet.Core7AoTCompileTest/Program.cs @@ -23,15 +23,11 @@ #pragma warning disable CS8602 // Possible null reference argument. using System; -using System.Collections; using System.Collections.Generic; using System.IO; -using System.Reflection; -using System.Security.Cryptography.X509Certificates; using YamlDotNet.Core; -using YamlDotNet.Core.Events; using YamlDotNet.Serialization; -using YamlDotNet.Serialization.NodeDeserializers; +using YamlDotNet.Serialization.Callbacks; string yaml = $@"MyBool: true hi: 1 @@ -203,6 +199,32 @@ public class InheritedBase public class Inherited : InheritedBase { public string NotInherited { get; set; } + + + [OnSerializing] + public void Serializing() + { + Console.WriteLine("Serializing"); + } + + [OnSerialized] + public void Serialized() + { + Console.WriteLine("Serialized"); + } + + [OnDeserialized] + public void Deserialized() + { + Console.WriteLine("Deserialized"); + } + + [OnDeserializing] + public void Deserializing() + { + Console.WriteLine("Deserializing"); + } + } public enum MyTestEnum diff --git a/YamlDotNet.Test/Analyzers/StaticGenerator/ObjectTests.cs b/YamlDotNet.Test/Analyzers/StaticGenerator/ObjectTests.cs index 41d59c20e..d2796c7c8 100644 --- a/YamlDotNet.Test/Analyzers/StaticGenerator/ObjectTests.cs +++ b/YamlDotNet.Test/Analyzers/StaticGenerator/ObjectTests.cs @@ -20,6 +20,7 @@ // SOFTWARE. using Xunit; using YamlDotNet.Serialization; +using YamlDotNet.Serialization.Callbacks; namespace YamlDotNet.Test.Analyzers.StaticGenerator { @@ -78,6 +79,46 @@ public void RegularObjectWorks() "; Assert.Equal(yaml.NormalizeNewLines().TrimNewLines(), actualYaml.NormalizeNewLines().TrimNewLines()); } + + [Fact] + public void CallbacksAreExecuted() + { + var yaml = "Test: Hi"; + var deserializer = new StaticDeserializerBuilder(new StaticContext()).Build(); + var test = deserializer.Deserialize(yaml); + + Assert.Equal(1, test.OnDeserializedCallCount); + Assert.Equal(1, test.OnDeserializingCallCount); + + var serializer = new StaticSerializerBuilder(new StaticContext()).Build(); + yaml = serializer.Serialize(test); + Assert.Equal(1, test.OnSerializedCallCount); + Assert.Equal(1, test.OnSerializingCallCount); + } + + [YamlSerializable] + public class TestState + { + public int OnDeserializedCallCount { get; set; } + public int OnDeserializingCallCount { get; set; } + public int OnSerializedCallCount { get; set; } + public int OnSerializingCallCount { get; set; } + + public string Test { get; set; } = string.Empty; + + [OnDeserialized] + public void Deserialized() => OnDeserializedCallCount++; + + [OnDeserializing] + public void Deserializing() => OnDeserializingCallCount++; + + [OnSerialized] + public void Serialized() => OnSerializedCallCount++; + + [OnSerializing] + public void Serializing() => OnSerializingCallCount++; + } + } public class InheritedClass { diff --git a/YamlDotNet.Test/Serialization/DeserializerTest.cs b/YamlDotNet.Test/Serialization/DeserializerTest.cs index b62af24ae..d8d54aeb4 100644 --- a/YamlDotNet.Test/Serialization/DeserializerTest.cs +++ b/YamlDotNet.Test/Serialization/DeserializerTest.cs @@ -26,6 +26,7 @@ using Xunit; using YamlDotNet.Core; using YamlDotNet.Serialization; +using YamlDotNet.Serialization.Callbacks; using YamlDotNet.Serialization.NamingConventions; namespace YamlDotNet.Test.Serialization @@ -331,7 +332,32 @@ public void DeserializeWithoutDuplicateKeyChecking_YamlWithDuplicateKeys_DoesNot act = () => sut.Deserialize>>(parser); act.ShouldNotThrow("Because duplicate key checking is not enabled"); } - + + [Fact] + public void SerializeStateMethodsGetCalledOnce() + { + var yaml = "Test: Hi"; + var deserializer = new DeserializerBuilder().Build(); + var test = deserializer.Deserialize(yaml); + + Assert.Equal(1, test.OnDeserializedCallCount); + Assert.Equal(1, test.OnDeserializingCallCount); + } + + public class TestState + { + public int OnDeserializedCallCount { get; set; } + public int OnDeserializingCallCount { get; set; } + + public string Test { get; set; } = string.Empty; + + [OnDeserialized] + public void Deserialized() => OnDeserializedCallCount++; + + [OnDeserializing] + public void Deserializing() => OnDeserializingCallCount++; + } + public class Test { public string Value { get; set; } diff --git a/YamlDotNet.Test/Serialization/SerializationTests.cs b/YamlDotNet.Test/Serialization/SerializationTests.cs index 85f9460cc..28a502f95 100644 --- a/YamlDotNet.Test/Serialization/SerializationTests.cs +++ b/YamlDotNet.Test/Serialization/SerializationTests.cs @@ -37,6 +37,7 @@ using YamlDotNet.Core; using YamlDotNet.Core.Events; using YamlDotNet.Serialization; +using YamlDotNet.Serialization.Callbacks; using YamlDotNet.Serialization.NamingConventions; using YamlDotNet.Serialization.ObjectFactories; @@ -2395,6 +2396,31 @@ public void KeysOnConcreteClassDontGetQuoted_TypeBoolDoesNotGetQuoted() result.Should().Be($"True: {Environment.NewLine}False: hello{Environment.NewLine}Null: true{Environment.NewLine}"); } + [Fact] + public void SerializeStateMethodsGetCalledOnce() + { + var serializer = new SerializerBuilder().Build(); + var test = new TestState(); + serializer.Serialize(test); + + Assert.Equal(1, test.OnSerializedCallCount); + Assert.Equal(1, test.OnSerializingCallCount); + } + + public class TestState + { + public int OnSerializedCallCount { get; set; } + public int OnSerializingCallCount { get; set; } + + public string Test { get; set; } = string.Empty; + + [OnSerialized] + public void Serialized() => OnSerializedCallCount++; + + [OnSerializing] + public void Serializing() => OnSerializingCallCount++; + } + public class ReservedWordsTestClass { public string True { get; set; } diff --git a/YamlDotNet/Serialization/Callbacks/OnDeserializedAttribute.cs b/YamlDotNet/Serialization/Callbacks/OnDeserializedAttribute.cs new file mode 100644 index 000000000..d33b3deb7 --- /dev/null +++ b/YamlDotNet/Serialization/Callbacks/OnDeserializedAttribute.cs @@ -0,0 +1,30 @@ +// This file is part of YamlDotNet - A .NET library for YAML. +// Copyright (c) Antoine Aubry and contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +// of the Software, and to permit persons to whom the Software is furnished to do +// so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +using System; + +namespace YamlDotNet.Serialization.Callbacks +{ + [AttributeUsage(AttributeTargets.Method)] + public sealed class OnDeserializedAttribute : Attribute + { + } +} diff --git a/YamlDotNet/Serialization/Callbacks/OnDeserializingAttribute.cs b/YamlDotNet/Serialization/Callbacks/OnDeserializingAttribute.cs new file mode 100644 index 000000000..27bdd31a7 --- /dev/null +++ b/YamlDotNet/Serialization/Callbacks/OnDeserializingAttribute.cs @@ -0,0 +1,30 @@ +// This file is part of YamlDotNet - A .NET library for YAML. +// Copyright (c) Antoine Aubry and contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +// of the Software, and to permit persons to whom the Software is furnished to do +// so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +using System; + +namespace YamlDotNet.Serialization.Callbacks +{ + [AttributeUsage(AttributeTargets.Method)] + public sealed class OnDeserializingAttribute : Attribute + { + } +} diff --git a/YamlDotNet/Serialization/Callbacks/OnSerializedAttribute.cs b/YamlDotNet/Serialization/Callbacks/OnSerializedAttribute.cs new file mode 100644 index 000000000..ad5c28330 --- /dev/null +++ b/YamlDotNet/Serialization/Callbacks/OnSerializedAttribute.cs @@ -0,0 +1,30 @@ +// This file is part of YamlDotNet - A .NET library for YAML. +// Copyright (c) Antoine Aubry and contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +// of the Software, and to permit persons to whom the Software is furnished to do +// so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +using System; + +namespace YamlDotNet.Serialization.Callbacks +{ + [AttributeUsage(AttributeTargets.Method)] + public sealed class OnSerializedAttribute : Attribute + { + } +} diff --git a/YamlDotNet/Serialization/Callbacks/OnSerializingAttribute.cs b/YamlDotNet/Serialization/Callbacks/OnSerializingAttribute.cs new file mode 100644 index 000000000..f30d05b16 --- /dev/null +++ b/YamlDotNet/Serialization/Callbacks/OnSerializingAttribute.cs @@ -0,0 +1,30 @@ +// This file is part of YamlDotNet - A .NET library for YAML. +// Copyright (c) Antoine Aubry and contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +// of the Software, and to permit persons to whom the Software is furnished to do +// so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +using System; + +namespace YamlDotNet.Serialization.Callbacks +{ + [AttributeUsage(AttributeTargets.Method)] + public sealed class OnSerializingAttribute : Attribute + { + } +} diff --git a/YamlDotNet/Serialization/IObjectFactory.cs b/YamlDotNet/Serialization/IObjectFactory.cs index c1f2f4d5b..39d8e7c7f 100644 --- a/YamlDotNet/Serialization/IObjectFactory.cs +++ b/YamlDotNet/Serialization/IObjectFactory.cs @@ -60,5 +60,29 @@ public interface IObjectFactory /// /// Type GetValueType(Type type); + + /// + /// Executes the methods on the object that has the attribute + /// + /// + void ExecuteOnDeserializing(object value); + + /// + /// Executes the methods on the object that has the attribute + /// + /// + void ExecuteOnDeserialized(object value); + + /// + /// Executes the methods on the object that has the attribute + /// + /// + void ExecuteOnSerializing(object value); + + /// + /// Executes the methods on the object that has the attribute + /// + /// + void ExecuteOnSerialized(object value); } } diff --git a/YamlDotNet/Serialization/NodeDeserializers/ObjectNodeDeserializer.cs b/YamlDotNet/Serialization/NodeDeserializers/ObjectNodeDeserializer.cs index 2a920d735..ea582981a 100644 --- a/YamlDotNet/Serialization/NodeDeserializers/ObjectNodeDeserializer.cs +++ b/YamlDotNet/Serialization/NodeDeserializers/ObjectNodeDeserializer.cs @@ -57,6 +57,8 @@ public bool Deserialize(IParser parser, Type expectedType, Func(StringComparer.Ordinal); while (!parser.TryConsume(out var _)) { @@ -104,6 +106,7 @@ public bool Deserialize(IParser parser, Type expectedType, Func public sealed class DefaultObjectFactory : ObjectFactoryBase { + private readonly Dictionary> _stateMethods = new Dictionary> + { + { typeof(OnDeserializedAttribute), new Dictionary() }, + { typeof(OnDeserializingAttribute), new Dictionary() }, + { typeof(OnSerializedAttribute), new Dictionary() }, + { typeof(OnSerializingAttribute), new Dictionary() }, + }; + private readonly Dictionary DefaultGenericInterfaceImplementations = new Dictionary { { typeof(IEnumerable<>), typeof(List<>) }, @@ -104,5 +115,49 @@ public override object Create(Type type) } } + public override void ExecuteOnDeserialized(object value) => + ExecuteState(typeof(OnDeserializedAttribute), value); + + public override void ExecuteOnDeserializing(object value) => + ExecuteState(typeof(OnDeserializingAttribute), value); + + public override void ExecuteOnSerialized(object value) => + ExecuteState(typeof(OnSerializedAttribute), value); + + public override void ExecuteOnSerializing(object value) => + ExecuteState(typeof(OnSerializingAttribute), value); + + private void ExecuteState(Type attributeType, object value) + { + if (value == null) + { + return; + } + + var type = value.GetType(); + var methodsToExecute = GetStateMethods(attributeType, type); + + foreach (var method in methodsToExecute) + { + method.Invoke(value, null); + } + } + + private MethodInfo[] GetStateMethods(Type attributeType, Type valueType) + { + var stateDictionary = _stateMethods[attributeType]; + + if (stateDictionary.TryGetValue(valueType, out var methods)) + { + return methods; + } + + methods = valueType.GetMethods(BindingFlags.Public | + BindingFlags.Instance | + BindingFlags.NonPublic); + methods = methods.Where(x => x.GetCustomAttributes(attributeType, true).Any()).ToArray(); + stateDictionary[valueType] = methods; + return methods; + } } } diff --git a/YamlDotNet/Serialization/ObjectFactories/ObjectFactoryBase.cs b/YamlDotNet/Serialization/ObjectFactories/ObjectFactoryBase.cs index 712cd144d..2309dd905 100644 --- a/YamlDotNet/Serialization/ObjectFactories/ObjectFactoryBase.cs +++ b/YamlDotNet/Serialization/ObjectFactories/ObjectFactoryBase.cs @@ -33,6 +33,26 @@ public abstract class ObjectFactoryBase : IObjectFactory public virtual object? CreatePrimitive(Type type) => type.IsValueType() ? Activator.CreateInstance(type) : null; + /// + public virtual void ExecuteOnDeserialized(object value) + { + } + + /// + public virtual void ExecuteOnDeserializing(object value) + { + } + + /// + public virtual void ExecuteOnSerialized(object value) + { + } + + /// + public virtual void ExecuteOnSerializing(object value) + { + } + public virtual bool GetDictionary(IObjectDescriptor descriptor, out IDictionary? dictionary, out Type[]? genericArguments) { var genericDictionaryType = ReflectionUtility.GetImplementedGenericInterface(descriptor.Type, typeof(IDictionary<,>)); diff --git a/YamlDotNet/Serialization/ObjectFactories/StaticObjectFactory.cs b/YamlDotNet/Serialization/ObjectFactories/StaticObjectFactory.cs index caf99c916..93a656462 100644 --- a/YamlDotNet/Serialization/ObjectFactories/StaticObjectFactory.cs +++ b/YamlDotNet/Serialization/ObjectFactories/StaticObjectFactory.cs @@ -121,5 +121,13 @@ public bool GetDictionary(IObjectDescriptor descriptor, out IDictionary? diction genericArguments = null; return false; } + + public abstract void ExecuteOnDeserializing(object value); + + public abstract void ExecuteOnDeserialized(object value); + + public abstract void ExecuteOnSerializing(object value); + + public abstract void ExecuteOnSerialized(object value); } } diff --git a/YamlDotNet/Serialization/ObjectGraphTraversalStrategies/FullObjectGraphTraversalStrategy.cs b/YamlDotNet/Serialization/ObjectGraphTraversalStrategies/FullObjectGraphTraversalStrategy.cs index 52ea83a34..e5581e9ce 100644 --- a/YamlDotNet/Serialization/ObjectGraphTraversalStrategies/FullObjectGraphTraversalStrategy.cs +++ b/YamlDotNet/Serialization/ObjectGraphTraversalStrategies/FullObjectGraphTraversalStrategy.cs @@ -104,6 +104,7 @@ protected virtual void Traverse(object name, IObjectDescriptor value, throw new MaximumRecursionLevelReachedException(message.ToString()); } + if (!visitor.Enter(value, context)) { return; @@ -232,6 +233,11 @@ private void TraverseList(IObjectDescriptor value, IObjectGraphVisitor protected virtual void TraverseProperties(IObjectDescriptor value, IObjectGraphVisitor visitor, TContext context, Stack path) { + if (context.GetType() != typeof(Nothing)) + { + objectFactory.ExecuteOnSerializing(value.Value); + } + visitor.VisitMappingStart(value, typeof(string), typeof(object), context); var source = value.NonNullValue(); @@ -246,6 +252,11 @@ protected virtual void TraverseProperties(IObjectDescriptor value, IOb } visitor.VisitMappingEnd(value, context); + + if (context.GetType() != typeof(Nothing)) + { + objectFactory.ExecuteOnSerialized(value.Value); + } } private IObjectDescriptor GetObjectDescriptor(object? value, Type staticType)