Skip to content

Commit 81735e6

Browse files
authored
Merge pull request #856 from prochnowc/code-generation-object
Added support for serialization and deserialization of 'object' to static code generation +semver:feature
2 parents 7dd57d4 + 8255d17 commit 81735e6

File tree

12 files changed

+271
-24
lines changed

12 files changed

+271
-24
lines changed

YamlDotNet.Analyzers.StaticGenerator/StaticContextFile.cs

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,48 @@ public override void Write(ClassSyntaxReceiver classSyntaxReceiver)
3434
{
3535
Write($"public partial class {classSyntaxReceiver.YamlStaticContextType?.Name ?? "StaticContext"} : YamlDotNet.Serialization.StaticContext");
3636
Write("{"); Indent();
37-
Write("public YamlDotNet.Serialization.ObjectFactories.StaticObjectFactory ObjectFactory { get; } = new StaticObjectFactory();");
38-
Write("public StaticTypeInspector TypeInspector { get; } = new StaticTypeInspector();");
39-
Write("public override YamlDotNet.Serialization.ObjectFactories.StaticObjectFactory GetFactory() => ObjectFactory;");
40-
Write("public override YamlDotNet.Serialization.ITypeInspector GetTypeInspector() => TypeInspector;");
37+
Write("private readonly YamlDotNet.Serialization.ObjectFactories.StaticObjectFactory _objectFactory;");
38+
Write("private readonly YamlDotNet.Serialization.ITypeResolver _typeResolver;");
39+
Write("private readonly YamlDotNet.Serialization.ITypeInspector _typeInspector;");
40+
Write($"public {classSyntaxReceiver.YamlStaticContextType?.Name ?? "StaticContext"}()");
41+
Write("{"); Indent();
42+
Write("_objectFactory = new StaticObjectFactory();");
43+
Write("_typeResolver = new StaticTypeResolver(this);");
44+
Write("_typeInspector = new StaticTypeInspector(_typeResolver);");
45+
UnIndent(); Write("}");
46+
Write("public override YamlDotNet.Serialization.ObjectFactories.StaticObjectFactory GetFactory() => _objectFactory;");
47+
Write("public override YamlDotNet.Serialization.ITypeInspector GetTypeInspector() => _typeInspector;");
48+
Write("public override YamlDotNet.Serialization.ITypeResolver GetTypeResolver() => _typeResolver;");
49+
Write("public override bool IsKnownType(Type type)");
50+
Write("{"); Indent();
51+
foreach (var o in classSyntaxReceiver.Classes)
52+
{
53+
var classObject = o.Value;
54+
if (classObject.IsArray)
55+
{
56+
Write($"if (type == typeof({classObject.ModuleSymbol.GetFullName().Replace("?", string.Empty)})) return true;");
57+
}
58+
else if (classObject.IsList)
59+
{
60+
Write($"if (type == typeof({classObject.ModuleSymbol.GetFullName().Replace("?", string.Empty)})) return true;");
61+
}
62+
else if (classObject.IsDictionary)
63+
{
64+
Write($"if (type == typeof({classObject.ModuleSymbol.GetFullName().Replace("?", string.Empty)})) return true;");
65+
}
66+
else
67+
{
68+
Write($"if (type == typeof({o.Value.ModuleSymbol.GetFullName().Replace("?", string.Empty)})) return true;");
69+
//always support a array, list and dictionary of the type
70+
Write($"if (type == typeof({classObject.ModuleSymbol.GetFullName().Replace("?", string.Empty)}[])) return true;");
71+
Write($"if (type == typeof(System.Collections.Generic.List<{classObject.ModuleSymbol.GetFullName().Replace("?", string.Empty)}>)) return true;");
72+
Write($"if (type == typeof(System.Collections.Generic.Dictionary<string, {classObject.ModuleSymbol.GetFullName().Replace("?", string.Empty)}>)) return true;");
73+
}
74+
}
75+
// always support dictionary object
76+
Write("if (type == typeof(System.Collections.Generic.Dictionary<object, object>)) return true;");
77+
Write("return false;");
78+
UnIndent(); Write("}");
4179
UnIndent(); Write("}");
4280
}
4381
}

YamlDotNet.Analyzers.StaticGenerator/StaticObjectFactoryFile.cs

Lines changed: 36 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,12 @@ public override void Write(ClassSyntaxReceiver classSyntaxReceiver)
4343
{
4444
var classObject = o.Value;
4545
Write($"if (type == typeof({classObject.ModuleSymbol.GetFullName().Replace("?", string.Empty)})) return new {classObject.ModuleSymbol.GetFullName().Replace("?", string.Empty)}();");
46+
//always support a list and dictionary of the type
4647
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)}>();");
48+
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)}>();");
4749
}
50+
// always support dictionary when deserializing object
51+
Write("if (type == typeof(System.Collections.Generic.Dictionary<object, object>)) return new System.Collections.Generic.Dictionary<object, object>();");
4852
Write($"throw new ArgumentOutOfRangeException(\"Unknown type: \" + type.ToString());");
4953
UnIndent(); Write("}");
5054

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

6872
Write("public override bool IsDictionary(Type type)");
6973
Write("{"); Indent();
70-
foreach (var o in classSyntaxReceiver.Classes.Where(c => c.Value.IsDictionary))
74+
foreach (var o in classSyntaxReceiver.Classes)
7175
{
72-
Write($"if (type == typeof({o.Value.ModuleSymbol.GetFullName().Replace("?", string.Empty)})) return true;");
76+
var classObject = o.Value;
77+
if (classObject.IsDictionary)
78+
{
79+
Write($"if (type == typeof({o.Value.ModuleSymbol.GetFullName().Replace("?", string.Empty)})) return true;");
80+
}
81+
else
82+
{
83+
//always support a dictionary of the type
84+
Write($"if (type == typeof(System.Collections.Generic.Dictionary<string, {classObject.ModuleSymbol.GetFullName().Replace("?", string.Empty)}>)) return true;");
85+
}
7386
}
87+
// always support dictionary object
88+
Write("if (type == typeof(System.Collections.Generic.Dictionary<object, object>)) return true;");
7489
Write("return false;");
7590
UnIndent(); Write("}");
7691

@@ -79,7 +94,7 @@ public override void Write(ClassSyntaxReceiver classSyntaxReceiver)
7994
foreach (var o in classSyntaxReceiver.Classes)
8095
{
8196
var classObject = o.Value;
82-
if (o.Value.IsArray)
97+
if (classObject.IsArray)
8398
{
8499
Write($"if (type == typeof({classObject.ModuleSymbol.GetFullName().Replace("?", string.Empty)})) return true;");
85100
}
@@ -115,20 +130,27 @@ public override void Write(ClassSyntaxReceiver classSyntaxReceiver)
115130
foreach (var o in classSyntaxReceiver.Classes)
116131
{
117132
var classObject = o.Value;
118-
if (!classObject.IsDictionary)
133+
if (classObject.IsDictionary)
119134
{
120-
continue;
121-
}
135+
var keyType = "object";
136+
var type = (INamedTypeSymbol)classObject.ModuleSymbol;
122137

123-
var keyType = "object";
124-
var type = (INamedTypeSymbol)classObject.ModuleSymbol;
138+
if (type.IsGenericType)
139+
{
140+
keyType = type.TypeArguments[0].GetFullName().Replace("?", string.Empty);
141+
}
125142

126-
if (type.IsGenericType)
143+
Write($"if (type == typeof({classObject.ModuleSymbol.GetFullName().Replace("?", string.Empty)})) return typeof({keyType});");
144+
}
145+
else if (!classObject.IsArray && !classObject.IsList)
127146
{
128-
keyType = type.TypeArguments[0].GetFullName().Replace("?", string.Empty);
147+
//always support a dictionary of the type
148+
Write($"if (type == typeof(System.Collections.Generic.Dictionary<string, {classObject.ModuleSymbol.GetFullName().Replace("?", string.Empty)}>)) return typeof({classObject.ModuleSymbol.GetFullName().Replace("?", string.Empty)});");
129149
}
130-
Write($"if (type == typeof({classObject.ModuleSymbol.GetFullName().Replace("?", string.Empty)})) return typeof({keyType});");
131150
}
151+
152+
// always support dictionary object
153+
Write("if (type == typeof(System.Collections.Generic.Dictionary<object, object>)) return typeof(object);");
132154
Write("throw new ArgumentOutOfRangeException(\"Unknown type: \" + type.ToString());");
133155
UnIndent(); Write("}");
134156

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

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

193+
Write("if (type == typeof(System.Collections.Generic.Dictionary<object, object>)) return typeof(object);");
170194
Write("throw new ArgumentOutOfRangeException(\"Unknown type: \" + type.ToString());");
171195
UnIndent(); Write("}");
172196
WriteExecuteMethod(classSyntaxReceiver, "ExecuteOnDeserializing", (c) => c.OnDeserializingMethods);

YamlDotNet.Analyzers.StaticGenerator/StaticPropertyDescriptorFile.cs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ public override void Write(ClassSyntaxReceiver classSyntaxReceiver)
3434
{
3535
Write("class StaticPropertyDescriptor : YamlDotNet.Serialization.IPropertyDescriptor");
3636
Write("{"); Indent();
37+
Write("private readonly YamlDotNet.Serialization.ITypeResolver _typeResolver;");
3738
Write("private YamlDotNet.Serialization.IObjectAccessor _accessor;");
3839
Write("private readonly Attribute[] _attributes;");
3940
Write("public string Name { get; }");
@@ -52,14 +53,17 @@ public override void Write(ClassSyntaxReceiver classSyntaxReceiver)
5253
UnIndent(); Write("}");
5354
Write("public YamlDotNet.Serialization.IObjectDescriptor Read(object target)");
5455
Write("{"); Indent();
55-
Write("return new YamlDotNet.Serialization.ObjectDescriptor(_accessor.Read(Name, target), Type, Type, this.ScalarStyle);");
56+
Write("var propertyValue = _accessor.Read(Name, target);");
57+
Write("var actualType = _typeResolver.Resolve(Type, propertyValue);");
58+
Write("return new YamlDotNet.Serialization.ObjectDescriptor(propertyValue, actualType, Type, this.ScalarStyle);");
5659
UnIndent(); Write("}");
5760
Write("public void Write(object target, object value)");
5861
Write("{"); Indent();
5962
Write("_accessor.Set(Name, target, value);");
6063
UnIndent(); Write("}");
61-
Write("public StaticPropertyDescriptor(YamlDotNet.Serialization.IObjectAccessor accessor, string name, bool canWrite, Type type, Attribute[] attributes)");
64+
Write("public StaticPropertyDescriptor(YamlDotNet.Serialization.ITypeResolver typeResolver, YamlDotNet.Serialization.IObjectAccessor accessor, string name, bool canWrite, Type type, Attribute[] attributes)");
6265
Write("{"); Indent();
66+
Write("this._typeResolver = typeResolver;");
6367
Write("this._accessor = accessor;");
6468
Write("this._attributes = attributes;");
6569
Write("this.Name = name;");

YamlDotNet.Analyzers.StaticGenerator/StaticTypeInspectorFile.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,12 @@ public override void Write(ClassSyntaxReceiver classSyntaxReceiver)
3838
Write("public class StaticTypeInspector : YamlDotNet.Serialization.ITypeInspector");
3939
Write("{"); Indent();
4040

41+
Write("private readonly YamlDotNet.Serialization.ITypeResolver _typeResolver;");
42+
Write("public StaticTypeInspector(YamlDotNet.Serialization.ITypeResolver typeResolver)");
43+
Write("{"); Indent();
44+
Write("_typeResolver = typeResolver;");
45+
UnIndent(); Write("}");
46+
4147
#region GetProperties
4248
Write("public IEnumerable<YamlDotNet.Serialization.IPropertyDescriptor> GetProperties(Type type, object container)");
4349
Write("{"); Indent();
@@ -96,7 +102,7 @@ public override void Write(ClassSyntaxReceiver classSyntaxReceiver)
96102

97103
private void WritePropertyDescriptor(string name, ITypeSymbol type, bool isReadonly, ImmutableArray<AttributeData> attributes, char finalChar)
98104
{
99-
Write($"new StaticPropertyDescriptor(accessor, \"{name}\", {(!isReadonly).ToString().ToLower()}, typeof({type.GetFullName().Replace("?", string.Empty)}), new Attribute[] {{");
105+
Write($"new StaticPropertyDescriptor(_typeResolver, accessor, \"{name}\", {(!isReadonly).ToString().ToLower()}, typeof({type.GetFullName().Replace("?", string.Empty)}), new Attribute[] {{");
100106
foreach (var attribute in attributes)
101107
{
102108
switch (attribute.AttributeClass?.ToDisplayString())
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
// This file is part of YamlDotNet - A .NET library for YAML.
2+
// Copyright (c) Antoine Aubry and contributors
3+
//
4+
// Permission is hereby granted, free of charge, to any person obtaining a copy of
5+
// this software and associated documentation files (the "Software"), to deal in
6+
// the Software without restriction, including without limitation the rights to
7+
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
8+
// of the Software, and to permit persons to whom the Software is furnished to do
9+
// so, subject to the following conditions:
10+
//
11+
// The above copyright notice and this permission notice shall be included in all
12+
// copies or substantial portions of the Software.
13+
//
14+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20+
// SOFTWARE.
21+
22+
using System;
23+
using Microsoft.CodeAnalysis;
24+
25+
namespace YamlDotNet.Analyzers.StaticGenerator
26+
{
27+
public class StaticTypeResolverFile : File
28+
{
29+
public StaticTypeResolverFile(Action<string, bool> write, Action indent, Action unindent, GeneratorExecutionContext context) : base(write, indent, unindent, context)
30+
{
31+
}
32+
33+
public override void Write(ClassSyntaxReceiver classSyntaxReceiver)
34+
{
35+
Write($"class StaticTypeResolver : YamlDotNet.Serialization.TypeResolvers.StaticTypeResolver");
36+
Write("{"); Indent();
37+
Write("private readonly YamlDotNet.Serialization.StaticContext _context;");
38+
Write($"public StaticTypeResolver(YamlDotNet.Serialization.StaticContext context)");
39+
Write("{"); Indent();
40+
Write("_context = context;");
41+
UnIndent(); Write("}");
42+
Write("public override Type Resolve(Type staticType, object actualValue)");
43+
Write("{"); Indent();
44+
Write("var result = base.Resolve(staticType, actualValue);");
45+
Write("if (result == staticType && actualValue != null && _context.IsKnownType(actualValue.GetType())) result = actualValue.GetType();");
46+
Write("return result;");
47+
UnIndent(); Write("}");
48+
UnIndent(); Write("}");
49+
}
50+
}
51+
}

YamlDotNet.Analyzers.StaticGenerator/TypeFactoryGenerator.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ private string GenerateSource(ClassSyntaxReceiver classSyntaxReceiver)
9696

9797
new StaticContextFile(write, indent, unindent, _context).Write(classSyntaxReceiver);
9898
new StaticObjectFactoryFile(write, indent, unindent, _context).Write(classSyntaxReceiver);
99+
new StaticTypeResolverFile(write, indent, unindent, _context).Write(classSyntaxReceiver);
99100
new StaticPropertyDescriptorFile(write, indent, unindent, _context).Write(classSyntaxReceiver);
100101
new StaticTypeInspectorFile(write, indent, unindent, _context).Write(classSyntaxReceiver);
101102
new ObjectAccessorFileGenerator(write, indent, unindent, _context).Write(classSyntaxReceiver);

YamlDotNet.Core7AoTCompileTest/Program.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,10 @@
7676
NotInherited: world
7777
External:
7878
Text: hello
79+
SomeObject: a
80+
SomeDictionary:
81+
a: 1
82+
b: 2
7983
");
8084

8185
var input = new StringReader(yaml);
@@ -211,6 +215,8 @@ public class PrimitiveTypes
211215
public List<string>? MyList { get; set; }
212216
public Inherited Inherited { get; set; }
213217
public ExternalModel External { get; set; }
218+
public object SomeObject { get; set; }
219+
public object SomeDictionary { get; set; }
214220
}
215221

216222
public class InheritedBase

YamlDotNet.Test/Analyzers/StaticGenerator/ObjectTests.cs

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
1919
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
2020
// SOFTWARE.
21+
22+
using System.Collections.Generic;
2123
using Xunit;
2224
using YamlDotNet.Serialization;
2325
using YamlDotNet.Serialization.Callbacks;
@@ -53,6 +55,15 @@ public void RegularObjectWorks()
5355
Prop2: 2
5456
Nested:
5557
NestedProp: abc
58+
DictionaryOfArrays:
59+
a:
60+
- 1
61+
b:
62+
- 2
63+
SomeValue: ""abc""
64+
SomeDictionary:
65+
a: 1
66+
b: 2
5667
";
5768
var actual = deserializer.Deserialize<RegularObjectOuter>(yaml);
5869
Assert.Equal("hello", actual.Prop1);
@@ -64,6 +75,11 @@ public void RegularObjectWorks()
6475
Assert.Equal(2, actual.Inner.Prop2);
6576
Assert.NotNull(actual.Nested);
6677
Assert.Equal("abc", actual.Nested.NestedProp);
78+
Assert.Equal("1", actual.DictionaryOfArrays["a"][0]);
79+
Assert.Equal("2", actual.DictionaryOfArrays["b"][0]);
80+
Assert.Equal("abc", actual.SomeValue);
81+
Assert.Equal("1", ((IDictionary<object, object>)actual.SomeDictionary)["a"]);
82+
Assert.Equal("2", ((IDictionary<object, object>)actual.SomeDictionary)["b"]);
6783

6884
var serializer = new StaticSerializerBuilder(new StaticContext()).Build();
6985
var actualYaml = serializer.Serialize(actual);
@@ -76,7 +92,15 @@ public void RegularObjectWorks()
7692
Prop2: 2
7793
Nested:
7894
NestedProp: abc
79-
";
95+
DictionaryOfArrays:
96+
a:
97+
- 1
98+
b:
99+
- 2
100+
SomeValue: abc
101+
SomeDictionary:
102+
a: 1
103+
b: 2";
80104
Assert.Equal(yaml.NormalizeNewLines().TrimNewLines(), actualYaml.NormalizeNewLines().TrimNewLines());
81105
}
82106

@@ -143,6 +167,12 @@ public class RegularObjectOuter
143167
public RegularObjectInner Inner { get; set; }
144168
public NestedClass Nested { get; set; }
145169

170+
public Dictionary<string, string[]> DictionaryOfArrays { get; set; }
171+
172+
public object SomeValue { get; set; }
173+
174+
public object SomeDictionary { get; set; }
175+
146176
[YamlSerializable]
147177
public class NestedClass
148178
{

0 commit comments

Comments
 (0)