Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added support for serialization and deserialization of 'object' to static code generation #856

Merged
merged 2 commits into from
Oct 10, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 42 additions & 4 deletions YamlDotNet.Analyzers.StaticGenerator/StaticContextFile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,48 @@ public override void Write(ClassSyntaxReceiver classSyntaxReceiver)
{
Write($"public partial class {classSyntaxReceiver.YamlStaticContextType?.Name ?? "StaticContext"} : YamlDotNet.Serialization.StaticContext");
Write("{"); Indent();
Write("public YamlDotNet.Serialization.ObjectFactories.StaticObjectFactory ObjectFactory { get; } = new StaticObjectFactory();");
Write("public StaticTypeInspector TypeInspector { get; } = new StaticTypeInspector();");
Write("public override YamlDotNet.Serialization.ObjectFactories.StaticObjectFactory GetFactory() => ObjectFactory;");
Write("public override YamlDotNet.Serialization.ITypeInspector GetTypeInspector() => TypeInspector;");
Write("private readonly YamlDotNet.Serialization.ObjectFactories.StaticObjectFactory _objectFactory;");
Write("private readonly YamlDotNet.Serialization.ITypeResolver _typeResolver;");
Write("private readonly YamlDotNet.Serialization.ITypeInspector _typeInspector;");
Write($"public {classSyntaxReceiver.YamlStaticContextType?.Name ?? "StaticContext"}()");
Write("{"); Indent();
Write("_objectFactory = new StaticObjectFactory();");
Write("_typeResolver = new StaticTypeResolver(this);");
Write("_typeInspector = new StaticTypeInspector(_typeResolver);");
UnIndent(); Write("}");
Write("public override YamlDotNet.Serialization.ObjectFactories.StaticObjectFactory GetFactory() => _objectFactory;");
Write("public override YamlDotNet.Serialization.ITypeInspector GetTypeInspector() => _typeInspector;");
Write("public override YamlDotNet.Serialization.ITypeResolver GetTypeResolver() => _typeResolver;");
Write("public override bool IsKnownType(Type type)");
Write("{"); Indent();
foreach (var o in classSyntaxReceiver.Classes)
{
var classObject = o.Value;
if (classObject.IsArray)
{
Write($"if (type == typeof({classObject.ModuleSymbol.GetFullName().Replace("?", string.Empty)})) return true;");
}
else if (classObject.IsList)
{
Write($"if (type == typeof({classObject.ModuleSymbol.GetFullName().Replace("?", string.Empty)})) return true;");
}
else if (classObject.IsDictionary)
{
Write($"if (type == typeof({classObject.ModuleSymbol.GetFullName().Replace("?", string.Empty)})) return true;");
}
else
{
Write($"if (type == typeof({o.Value.ModuleSymbol.GetFullName().Replace("?", string.Empty)})) return true;");
//always support a array, list and dictionary of the type
Write($"if (type == typeof({classObject.ModuleSymbol.GetFullName().Replace("?", string.Empty)}[])) return true;");
Write($"if (type == typeof(System.Collections.Generic.List<{classObject.ModuleSymbol.GetFullName().Replace("?", string.Empty)}>)) return true;");
Write($"if (type == typeof(System.Collections.Generic.Dictionary<string, {classObject.ModuleSymbol.GetFullName().Replace("?", string.Empty)}>)) return true;");
}
}
// always support dictionary object
Write("if (type == typeof(System.Collections.Generic.Dictionary<object, object>)) return true;");
Write("return false;");
UnIndent(); Write("}");
UnIndent(); Write("}");
}
}
Expand Down
48 changes: 36 additions & 12 deletions YamlDotNet.Analyzers.StaticGenerator/StaticObjectFactoryFile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,12 @@ public override void Write(ClassSyntaxReceiver classSyntaxReceiver)
{
var classObject = o.Value;
Write($"if (type == typeof({classObject.ModuleSymbol.GetFullName().Replace("?", string.Empty)})) return new {classObject.ModuleSymbol.GetFullName().Replace("?", string.Empty)}();");
//always support a list and dictionary of the type
Write($"if (type == typeof(System.Collections.Generic.List<{classObject.ModuleSymbol.GetFullName().Replace("?", string.Empty)}>)) return new System.Collections.Generic.List<{classObject.ModuleSymbol.GetFullName().Replace("?", string.Empty)}>();");
Write($"if (type == typeof(System.Collections.Generic.Dictionary<string, {classObject.ModuleSymbol.GetFullName().Replace("?", string.Empty)}>)) return new System.Collections.Generic.Dictionary<string, {classObject.ModuleSymbol.GetFullName().Replace("?", string.Empty)}>();");
}
// always support dictionary when deserializing object
Write("if (type == typeof(System.Collections.Generic.Dictionary<object, object>)) return new System.Collections.Generic.Dictionary<object, object>();");
Write($"throw new ArgumentOutOfRangeException(\"Unknown type: \" + type.ToString());");
UnIndent(); Write("}");

Expand All @@ -67,10 +71,21 @@ public override void Write(ClassSyntaxReceiver classSyntaxReceiver)

Write("public override bool IsDictionary(Type type)");
Write("{"); Indent();
foreach (var o in classSyntaxReceiver.Classes.Where(c => c.Value.IsDictionary))
foreach (var o in classSyntaxReceiver.Classes)
{
Write($"if (type == typeof({o.Value.ModuleSymbol.GetFullName().Replace("?", string.Empty)})) return true;");
var classObject = o.Value;
if (classObject.IsDictionary)
{
Write($"if (type == typeof({o.Value.ModuleSymbol.GetFullName().Replace("?", string.Empty)})) return true;");
}
else
{
//always support a dictionary of the type
Write($"if (type == typeof(System.Collections.Generic.Dictionary<string, {classObject.ModuleSymbol.GetFullName().Replace("?", string.Empty)}>)) return true;");
}
}
// always support dictionary object
Write("if (type == typeof(System.Collections.Generic.Dictionary<object, object>)) return true;");
Write("return false;");
UnIndent(); Write("}");

Expand All @@ -79,7 +94,7 @@ public override void Write(ClassSyntaxReceiver classSyntaxReceiver)
foreach (var o in classSyntaxReceiver.Classes)
{
var classObject = o.Value;
if (o.Value.IsArray)
if (classObject.IsArray)
{
Write($"if (type == typeof({classObject.ModuleSymbol.GetFullName().Replace("?", string.Empty)})) return true;");
}
Expand Down Expand Up @@ -115,20 +130,27 @@ public override void Write(ClassSyntaxReceiver classSyntaxReceiver)
foreach (var o in classSyntaxReceiver.Classes)
{
var classObject = o.Value;
if (!classObject.IsDictionary)
if (classObject.IsDictionary)
{
continue;
}
var keyType = "object";
var type = (INamedTypeSymbol)classObject.ModuleSymbol;

var keyType = "object";
var type = (INamedTypeSymbol)classObject.ModuleSymbol;
if (type.IsGenericType)
{
keyType = type.TypeArguments[0].GetFullName().Replace("?", string.Empty);
}

if (type.IsGenericType)
Write($"if (type == typeof({classObject.ModuleSymbol.GetFullName().Replace("?", string.Empty)})) return typeof({keyType});");
}
else if (!classObject.IsArray && !classObject.IsList)
{
keyType = type.TypeArguments[0].GetFullName().Replace("?", string.Empty);
//always support a dictionary of the type
Write($"if (type == typeof(System.Collections.Generic.Dictionary<string, {classObject.ModuleSymbol.GetFullName().Replace("?", string.Empty)}>)) return typeof({classObject.ModuleSymbol.GetFullName().Replace("?", string.Empty)});");
}
Write($"if (type == typeof({classObject.ModuleSymbol.GetFullName().Replace("?", string.Empty)})) return typeof({keyType});");
}

// always support dictionary object
Write("if (type == typeof(System.Collections.Generic.Dictionary<object, object>)) return typeof(object);");
Write("throw new ArgumentOutOfRangeException(\"Unknown type: \" + type.ToString());");
UnIndent(); Write("}");

Expand Down Expand Up @@ -159,14 +181,16 @@ public override void Write(ClassSyntaxReceiver classSyntaxReceiver)
Write($"if (type == typeof({classObject.ModuleSymbol.GetFullName().Replace("?", string.Empty)})) return typeof({valueType});");
}

//always support array and list of all types
//always support array, list and dictionary of all types
foreach (var o in classSyntaxReceiver.Classes)
{
var classObject = o.Value;
Write($"if (type == typeof({classObject.ModuleSymbol.GetFullName().Replace("?", string.Empty)}[])) return typeof({classObject.ModuleSymbol.GetFullName().Replace("?", string.Empty)});");
Write($"if (type == typeof(System.Collections.Generic.List<{classObject.ModuleSymbol.GetFullName().Replace("?", string.Empty)}>)) return typeof({classObject.ModuleSymbol.GetFullName().Replace("?", string.Empty)});");
Write($"if (type == typeof(System.Collections.Generic.Dictionary<string, {classObject.ModuleSymbol.GetFullName().Replace("?", string.Empty)}>)) return typeof({classObject.ModuleSymbol.GetFullName().Replace("?", string.Empty)});");
}

Write("if (type == typeof(System.Collections.Generic.Dictionary<object, object>)) return typeof(object);");
Write("throw new ArgumentOutOfRangeException(\"Unknown type: \" + type.ToString());");
UnIndent(); Write("}");
WriteExecuteMethod(classSyntaxReceiver, "ExecuteOnDeserializing", (c) => c.OnDeserializingMethods);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ public override void Write(ClassSyntaxReceiver classSyntaxReceiver)
{
Write("class StaticPropertyDescriptor : YamlDotNet.Serialization.IPropertyDescriptor");
Write("{"); Indent();
Write("private readonly YamlDotNet.Serialization.ITypeResolver _typeResolver;");
Write("private YamlDotNet.Serialization.IObjectAccessor _accessor;");
Write("private readonly Attribute[] _attributes;");
Write("public string Name { get; }");
Expand All @@ -52,14 +53,17 @@ public override void Write(ClassSyntaxReceiver classSyntaxReceiver)
UnIndent(); Write("}");
Write("public YamlDotNet.Serialization.IObjectDescriptor Read(object target)");
Write("{"); Indent();
Write("return new YamlDotNet.Serialization.ObjectDescriptor(_accessor.Read(Name, target), Type, Type, this.ScalarStyle);");
Write("var propertyValue = _accessor.Read(Name, target);");
Write("var actualType = _typeResolver.Resolve(Type, propertyValue);");
Write("return new YamlDotNet.Serialization.ObjectDescriptor(propertyValue, actualType, Type, this.ScalarStyle);");
UnIndent(); Write("}");
Write("public void Write(object target, object value)");
Write("{"); Indent();
Write("_accessor.Set(Name, target, value);");
UnIndent(); Write("}");
Write("public StaticPropertyDescriptor(YamlDotNet.Serialization.IObjectAccessor accessor, string name, bool canWrite, Type type, Attribute[] attributes)");
Write("public StaticPropertyDescriptor(YamlDotNet.Serialization.ITypeResolver typeResolver, YamlDotNet.Serialization.IObjectAccessor accessor, string name, bool canWrite, Type type, Attribute[] attributes)");
Write("{"); Indent();
Write("this._typeResolver = typeResolver;");
Write("this._accessor = accessor;");
Write("this._attributes = attributes;");
Write("this.Name = name;");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@ public override void Write(ClassSyntaxReceiver classSyntaxReceiver)
Write("public class StaticTypeInspector : YamlDotNet.Serialization.ITypeInspector");
Write("{"); Indent();

Write("private readonly YamlDotNet.Serialization.ITypeResolver _typeResolver;");
Write("public StaticTypeInspector(YamlDotNet.Serialization.ITypeResolver typeResolver)");
Write("{"); Indent();
Write("_typeResolver = typeResolver;");
UnIndent(); Write("}");

#region GetProperties
Write("public IEnumerable<YamlDotNet.Serialization.IPropertyDescriptor> GetProperties(Type type, object container)");
Write("{"); Indent();
Expand Down Expand Up @@ -96,7 +102,7 @@ public override void Write(ClassSyntaxReceiver classSyntaxReceiver)

private void WritePropertyDescriptor(string name, ITypeSymbol type, bool isReadonly, ImmutableArray<AttributeData> attributes, char finalChar)
{
Write($"new StaticPropertyDescriptor(accessor, \"{name}\", {(!isReadonly).ToString().ToLower()}, typeof({type.GetFullName().Replace("?", string.Empty)}), new Attribute[] {{");
Write($"new StaticPropertyDescriptor(_typeResolver, accessor, \"{name}\", {(!isReadonly).ToString().ToLower()}, typeof({type.GetFullName().Replace("?", string.Empty)}), new Attribute[] {{");
foreach (var attribute in attributes)
{
switch (attribute.AttributeClass?.ToDisplayString())
Expand Down
51 changes: 51 additions & 0 deletions YamlDotNet.Analyzers.StaticGenerator/StaticTypeResolverFile.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// 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;
using Microsoft.CodeAnalysis;

namespace YamlDotNet.Analyzers.StaticGenerator
{
public class StaticTypeResolverFile : File
{
public StaticTypeResolverFile(Action<string, bool> write, Action indent, Action unindent, GeneratorExecutionContext context) : base(write, indent, unindent, context)
{
}

public override void Write(ClassSyntaxReceiver classSyntaxReceiver)
{
Write($"class StaticTypeResolver : YamlDotNet.Serialization.TypeResolvers.StaticTypeResolver");
Write("{"); Indent();
Write("private readonly YamlDotNet.Serialization.StaticContext _context;");
Write($"public StaticTypeResolver(YamlDotNet.Serialization.StaticContext context)");
Write("{"); Indent();
Write("_context = context;");
UnIndent(); Write("}");
Write("public override Type Resolve(Type staticType, object actualValue)");
Write("{"); Indent();
Write("var result = base.Resolve(staticType, actualValue);");
Write("if (result == staticType && actualValue != null && _context.IsKnownType(actualValue.GetType())) result = actualValue.GetType();");
Write("return result;");
UnIndent(); Write("}");
UnIndent(); Write("}");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ private string GenerateSource(ClassSyntaxReceiver classSyntaxReceiver)

new StaticContextFile(write, indent, unindent, _context).Write(classSyntaxReceiver);
new StaticObjectFactoryFile(write, indent, unindent, _context).Write(classSyntaxReceiver);
new StaticTypeResolverFile(write, indent, unindent, _context).Write(classSyntaxReceiver);
new StaticPropertyDescriptorFile(write, indent, unindent, _context).Write(classSyntaxReceiver);
new StaticTypeInspectorFile(write, indent, unindent, _context).Write(classSyntaxReceiver);
new ObjectAccessorFileGenerator(write, indent, unindent, _context).Write(classSyntaxReceiver);
Expand Down
6 changes: 6 additions & 0 deletions YamlDotNet.Core7AoTCompileTest/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,10 @@
NotInherited: world
External:
Text: hello
SomeObject: a
SomeDictionary:
a: 1
b: 2
");

var input = new StringReader(yaml);
Expand Down Expand Up @@ -211,6 +215,8 @@ public class PrimitiveTypes
public List<string>? MyList { get; set; }
public Inherited Inherited { get; set; }
public ExternalModel External { get; set; }
public object SomeObject { get; set; }
public object SomeDictionary { get; set; }
}

public class InheritedBase
Expand Down
32 changes: 31 additions & 1 deletion YamlDotNet.Test/Analyzers/StaticGenerator/ObjectTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
// 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.Collections.Generic;
using Xunit;
using YamlDotNet.Serialization;
using YamlDotNet.Serialization.Callbacks;
Expand Down Expand Up @@ -53,6 +55,15 @@ public void RegularObjectWorks()
Prop2: 2
Nested:
NestedProp: abc
DictionaryOfArrays:
a:
- 1
b:
- 2
SomeValue: ""abc""
SomeDictionary:
a: 1
b: 2
";
var actual = deserializer.Deserialize<RegularObjectOuter>(yaml);
Assert.Equal("hello", actual.Prop1);
Expand All @@ -64,6 +75,11 @@ public void RegularObjectWorks()
Assert.Equal(2, actual.Inner.Prop2);
Assert.NotNull(actual.Nested);
Assert.Equal("abc", actual.Nested.NestedProp);
Assert.Equal("1", actual.DictionaryOfArrays["a"][0]);
Assert.Equal("2", actual.DictionaryOfArrays["b"][0]);
Assert.Equal("abc", actual.SomeValue);
Assert.Equal("1", ((IDictionary<object, object>)actual.SomeDictionary)["a"]);
Assert.Equal("2", ((IDictionary<object, object>)actual.SomeDictionary)["b"]);

var serializer = new StaticSerializerBuilder(new StaticContext()).Build();
var actualYaml = serializer.Serialize(actual);
Expand All @@ -76,7 +92,15 @@ public void RegularObjectWorks()
Prop2: 2
Nested:
NestedProp: abc
";
DictionaryOfArrays:
a:
- 1
b:
- 2
SomeValue: abc
SomeDictionary:
a: 1
b: 2";
Assert.Equal(yaml.NormalizeNewLines().TrimNewLines(), actualYaml.NormalizeNewLines().TrimNewLines());
}

Expand Down Expand Up @@ -143,6 +167,12 @@ public class RegularObjectOuter
public RegularObjectInner Inner { get; set; }
public NestedClass Nested { get; set; }

public Dictionary<string, string[]> DictionaryOfArrays { get; set; }

public object SomeValue { get; set; }

public object SomeDictionary { get; set; }

[YamlSerializable]
public class NestedClass
{
Expand Down
Loading