Skip to content

Commit

Permalink
Merge pull request #856 from prochnowc/code-generation-object
Browse files Browse the repository at this point in the history
Added support for serialization and deserialization of 'object' to static code generation

+semver:feature
  • Loading branch information
EdwardCooke authored Oct 10, 2023
2 parents 7dd57d4 + 8255d17 commit 81735e6
Show file tree
Hide file tree
Showing 12 changed files with 271 additions and 24 deletions.
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

0 comments on commit 81735e6

Please sign in to comment.