Skip to content

Commit 16408d2

Browse files
committed
Add support for hidden properties
Update to Roslyn V2.8.0
1 parent a24ec9e commit 16408d2

File tree

6 files changed

+246
-41
lines changed

6 files changed

+246
-41
lines changed

CSharpScriptSerializer.sln.DotSettings

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@
1212
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/CONSTRUCTOR_OR_DESTRUCTOR_BODY/@EntryValue">ExpressionBody</s:String>
1313
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/LOCAL_FUNCTION_BODY/@EntryValue">ExpressionBody</s:String>
1414
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/METHOD_OR_OPERATOR_BODY/@EntryValue">ExpressionBody</s:String>
15+
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_ACCESSORHOLDER_ATTRIBUTE_ON_SAME_LINE_EX/@EntryValue">NEVER</s:String>
1516
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_FIELD_ATTRIBUTE_ON_SAME_LINE/@EntryValue">False</s:Boolean>
17+
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_FIELD_ATTRIBUTE_ON_SAME_LINE_EX/@EntryValue">NEVER</s:String>
1618
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SPACE_AFTER_TYPECAST_PARENTHESES/@EntryValue">False</s:Boolean>
1719
<s:String x:Key="/Default/CodeStyle/FileHeader/FileHeaderText/@EntryValue"></s:String>
1820
<s:String x:Key="/Default/CodeStyle/Naming/CppNaming/UserRules/=DECLSPEC_005FPROPERTY/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /&gt;</s:String>
@@ -79,6 +81,11 @@
7981
<s:String x:Key="/Default/CodeStyle/Naming/XamlNaming/UserRules/=XAML_005FFIELD/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /&gt;</s:String>
8082
<s:String x:Key="/Default/CodeStyle/Naming/XamlNaming/UserRules/=XAML_005FRESOURCE/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /&gt;</s:String>
8183
<s:String x:Key="/Default/Environment/Hierarchy/PsiConfigurationSettingsKey/CustomLocation/@EntryValue">C:\Users\Andriy\AppData\Local\JetBrains\Transient\ReSharperPlatformVs15\v09_9df6e669\SolutionCaches</s:String>
84+
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpAttributeForSingleLineMethodUpgrade/@EntryIndexedValue">True</s:Boolean>
85+
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpKeepExistingMigration/@EntryIndexedValue">True</s:Boolean>
86+
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpPlaceEmbeddedOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>
87+
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpRenamePlacementToArrangementMigration/@EntryIndexedValue">True</s:Boolean>
8288
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EAddAccessorOwnerDeclarationBracesMigration/@EntryIndexedValue">True</s:Boolean>
89+
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002ECSharpPlaceAttributeOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>
8390
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean>
8491
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateThisQualifierSettings/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

src/CSharpScriptSerializer/CSScriptSerializer.cs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -341,12 +341,15 @@ protected static bool IsConstructable(Type type)
341341
protected static bool IsInitializable(Type type) => type.GetRuntimeProperties().Where(IsCandidateProperty).Any();
342342

343343
protected static bool IsCandidateProperty(PropertyInfo property)
344-
=> !(property.GetMethod ?? property.SetMethod).IsStatic
345-
&& property.GetIndexParameters().Length == 0
344+
=> IsUsableProperty(property)
345+
&& !(property.GetMethod ?? property.SetMethod).IsStatic
346346
&& property.CanRead
347-
&& property.CanWrite
348347
&& property.GetMethod != null
349-
&& property.GetMethod.IsPublic
348+
&& property.GetMethod.IsPublic;
349+
350+
protected static bool IsUsableProperty(PropertyInfo property)
351+
=> property.GetIndexParameters().Length == 0
352+
&& property.CanWrite
350353
&& property.SetMethod != null
351354
&& property.SetMethod.IsPublic;
352355

src/CSharpScriptSerializer/CSharpScriptSerializer.csproj

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<PackageId>CSharpScriptSerializer</PackageId>
66
<AssemblyTitle>CSharpScriptSerializer</AssemblyTitle>
77
<Title>CSharpScriptSerializer</Title>
8-
<VersionPrefix>1.4.0</VersionPrefix>
8+
<VersionPrefix>1.5.0</VersionPrefix>
99
<TargetFrameworks>netstandard1.5;net46</TargetFrameworks>
1010
<NetStandardImplicitPackageVersion Condition=" '$(TargetFramework)' == 'netstandard1.5' ">1.6.1</NetStandardImplicitPackageVersion>
1111
<TreatWarningsAsErrors>True</TreatWarningsAsErrors>
@@ -15,6 +15,9 @@
1515
<PackageTags>Roslyn;CSharp;C#;CSX;Script;Serialization</PackageTags>
1616
<PackageReleaseNotes>
1717
<![CDATA[
18+
Version 1.5.0
19+
* Add support for hidden properties
20+
* Update to Roslyn V2.8.0
1821
Version 1.4.0
1922
* Add support for ValueTuple, Type and plain Object
2023
Version 1.3.0
@@ -26,8 +29,6 @@ Version 1.1.2
2629
Version 1.1.1
2730
* Use verbatim literals for multi-line strings
2831
* Remove redundant flags enum values
29-
Version 1.1.0
30-
* Enable customizing the property serialization condition in PropertyCSScriptSerializer
3132
]]>
3233
</PackageReleaseNotes>
3334
<PackageProjectUrl>https://github.com/AndriySvyryd/CSharpScriptSerializer</PackageProjectUrl>
@@ -41,8 +42,8 @@ Version 1.1.0
4142
</PropertyGroup>
4243

4344
<ItemGroup>
44-
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="2.3.1" />
45-
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="2.3.1" />
45+
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="2.8.2" />
46+
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="2.8.2" />
4647
</ItemGroup>
4748

4849
<ItemGroup Condition=" '$(TargetFramework)' == 'net46' ">

src/CSharpScriptSerializer/PropertyCSScriptSerializer.cs

Lines changed: 120 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,17 @@
33
using System.Linq;
44
using System.Linq.Expressions;
55
using System.Reflection;
6+
using Microsoft.CodeAnalysis;
67
using Microsoft.CodeAnalysis.CSharp;
78
using Microsoft.CodeAnalysis.CSharp.Syntax;
9+
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
810

911
namespace CSharpScriptSerialization
1012
{
1113
public class PropertyCSScriptSerializer<T> : ConstructorCSScriptSerializer<T>
1214
{
1315
private readonly IReadOnlyCollection<PropertyData> _propertyData;
16+
private readonly IReadOnlyCollection<PropertyData> _hiddenPropertyData;
1417

1518
public PropertyCSScriptSerializer()
1619
: this((Func<T, object>[])null)
@@ -36,63 +39,154 @@ public PropertyCSScriptSerializer(IReadOnlyDictionary<string, Func<T, object, bo
3639
public PropertyCSScriptSerializer(IReadOnlyDictionary<string, Func<T, object, bool>> propertyConditions,
3740
IReadOnlyCollection<Func<T, object>> constructorParameterGetters,
3841
IReadOnlyDictionary<string, Func<T, object>> propertyValueGetters)
42+
: this(propertyConditions, constructorParameterGetters, propertyValueGetters, null, null)
43+
{
44+
}
45+
46+
public PropertyCSScriptSerializer(IReadOnlyDictionary<string, Func<T, object, bool>> propertyConditions,
47+
IReadOnlyCollection<Func<T, object>> constructorParameterGetters,
48+
IReadOnlyDictionary<string, Func<T, object>> propertyValueGetters,
49+
IReadOnlyDictionary<string, Func<T, object, bool>> hiddenPropertyConditions,
50+
IReadOnlyDictionary<string, Func<T, object>> hiddenPropertyValueGetters)
3951
: base(constructorParameterGetters)
4052
{
53+
var typeInfo = typeof(T).GetTypeInfo();
54+
var allUsableProperties = new Dictionary<string, PropertyInfo>();
55+
var allHiddenProperties = new Dictionary<string, PropertyInfo>();
56+
while (!typeInfo.Equals(typeof(object)))
57+
{
58+
foreach (var property in typeInfo
59+
.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly))
60+
{
61+
if (!IsUsableProperty(property)
62+
|| !Equals(property.SetMethod.GetBaseDefinition(), property.SetMethod))
63+
{
64+
continue;
65+
}
66+
67+
if (!allUsableProperties.ContainsKey(property.Name))
68+
{
69+
allUsableProperties[property.Name] = property;
70+
}
71+
else
72+
{
73+
allHiddenProperties[property.DeclaringType.Name + "." + property.Name] = property;
74+
}
75+
}
76+
77+
typeInfo = typeInfo.BaseType.GetTypeInfo();
78+
}
79+
4180
propertyConditions = propertyConditions ?? new Dictionary<string, Func<T, object, bool>>();
4281
propertyValueGetters = propertyValueGetters ?? new Dictionary<string, Func<T, object>>();
43-
var referencedPropertyNames = propertyConditions.Keys.Concat(propertyValueGetters.Keys).Distinct();
44-
var allUsableProperties = typeof(T).GetTypeInfo()
45-
.GetProperties(BindingFlags.Instance | BindingFlags.Public).Where(property =>
46-
property.GetIndexParameters().Length == 0
47-
&& property.CanWrite
48-
&& property.SetMethod != null
49-
&& property.SetMethod.IsPublic)
50-
.ToDictionary(p => p.Name);
51-
52-
_propertyData = GetProperties(referencedPropertyNames, allUsableProperties)
53-
.Concat(allUsableProperties.Values.Where(IsCandidateProperty)).Distinct()
54-
.Select(
55-
p => new PropertyData(
82+
83+
_propertyData =
84+
GetProperties(propertyConditions.Keys.Concat(propertyValueGetters.Keys).Distinct(),
85+
allUsableProperties, hidden: false)
86+
.Concat(allUsableProperties.Values.Where(IsCandidateProperty)).Distinct()
87+
.Select(p => new PropertyData(
5688
p.Name,
5789
p.PropertyType,
58-
propertyValueGetters.GetValueOrDefault(p.Name, CreatePropertyInitializer(p)),
90+
p.DeclaringType,
91+
propertyValueGetters.GetValueOrDefault(p.Name,
92+
CreatePropertyValueGetter(p)),
5993
propertyConditions.GetValueOrDefault(p.Name,
6094
(o, v) => !Equals(v, GetDefault(p.PropertyType)))))
61-
.ToArray();
95+
.ToArray();
96+
97+
hiddenPropertyConditions = hiddenPropertyConditions ?? new Dictionary<string, Func<T, object, bool>>();
98+
hiddenPropertyValueGetters = hiddenPropertyValueGetters ?? new Dictionary<string, Func<T, object>>();
99+
100+
_hiddenPropertyData =
101+
GetProperties(hiddenPropertyConditions.Keys.Concat(hiddenPropertyValueGetters.Keys).Distinct(),
102+
allHiddenProperties, hidden: true)
103+
.Concat(allHiddenProperties.Values.Where(IsCandidateProperty)).Distinct()
104+
.Select(p => new PropertyData(
105+
p.Name,
106+
p.PropertyType,
107+
p.DeclaringType,
108+
hiddenPropertyValueGetters.GetValueOrDefault(p.DeclaringType.Name + "." + p.Name,
109+
CreatePropertyValueGetter(p)),
110+
hiddenPropertyConditions.GetValueOrDefault(p.DeclaringType.Name + "." + p.Name,
111+
(o, v) => !Equals(v, GetDefault(p.PropertyType)))))
112+
.ToArray();
62113
}
63114

64115
protected override bool GenerateEmptyArgumentList => false;
65116

66-
private IEnumerable<PropertyInfo> GetProperties(IEnumerable<string> propertyNames,
67-
Dictionary<string, PropertyInfo> allProperties)
117+
private IEnumerable<PropertyInfo> GetProperties(
118+
IEnumerable<string> propertyNames,
119+
Dictionary<string, PropertyInfo> allProperties,
120+
bool hidden)
68121
{
69122
foreach (var propertyName in propertyNames)
70123
{
71124
if (!allProperties.TryGetValue(propertyName, out var property))
72125
{
126+
if (hidden)
127+
{
128+
throw new InvalidOperationException(
129+
$"The type {typeof(T)} does not have a hidden public nonstatic writable property {propertyName}");
130+
}
131+
73132
throw new InvalidOperationException(
74133
$"The type {typeof(T)} does not have a public nonstatic writable property {propertyName}");
75134
}
76135
yield return property;
77136
}
78137
}
79138

80-
public override ExpressionSyntax GetCreation(object obj) => GetObjectCreationExpression((T)obj);
139+
public override ExpressionSyntax GetCreation(object obj)
140+
{
141+
var typedObject = (T)obj;
142+
var objectParameter = IdentifierName("o");
143+
var hiddenPropertyInitializers = _hiddenPropertyData
144+
.Where(p => p.PropertyCondition(typedObject, p.PropertyValueGetter(typedObject)))
145+
.Select(p => (StatementSyntax)ExpressionStatement(AssignmentExpression(
146+
SyntaxKind.SimpleAssignmentExpression,
147+
MemberAccessExpression(
148+
SyntaxKind.SimpleMemberAccessExpression,
149+
ParenthesizedExpression(CastExpression(GetTypeSyntax(p.DeclaringType), objectParameter)),
150+
IdentifierName(p.PropertyName)),
151+
GetCreationExpression(p.PropertyValueGetter(typedObject)))))
152+
.ToList();
153+
154+
var objectCreationExpression = GetObjectCreationExpression(typedObject);
155+
if (hiddenPropertyInitializers.Count == 0)
156+
{
157+
return objectCreationExpression;
158+
}
159+
160+
hiddenPropertyInitializers.Add(ReturnStatement(objectParameter));
161+
return InvocationExpression(
162+
ParenthesizedExpression(
163+
CastExpression(
164+
GenericName(Identifier("Func")).WithTypeArgumentList(
165+
TypeArgumentList(
166+
SeparatedList<TypeSyntax>(new SyntaxNodeOrToken[]
167+
{
168+
GetTypeSyntax(Type), Token(SyntaxKind.CommaToken),
169+
GetTypeSyntax(Type)
170+
}))),
171+
ParenthesizedExpression(SimpleLambdaExpression(Parameter(Identifier("o")),
172+
Block(hiddenPropertyInitializers))))))
173+
.WithArgumentList(ArgumentList(SingletonSeparatedList(Argument(objectCreationExpression))));
174+
}
81175

82176
protected override ObjectCreationExpressionSyntax GetObjectCreationExpression(T obj)
83177
=> base.GetObjectCreationExpression(obj)
84178
.WithInitializer(AddNewLine(
85-
SyntaxFactory.InitializerExpression(
179+
InitializerExpression(
86180
SyntaxKind.ObjectInitializerExpression,
87-
SyntaxFactory.SeparatedList<ExpressionSyntax>(
181+
SeparatedList<ExpressionSyntax>(
88182
ToCommaSeparatedList(_propertyData
89183
.Where(p => p.PropertyCondition(obj, p.PropertyValueGetter(obj)))
90-
.Select(p => SyntaxFactory.AssignmentExpression(
184+
.Select(p => AssignmentExpression(
91185
SyntaxKind.SimpleAssignmentExpression,
92-
SyntaxFactory.IdentifierName(p.PropertyName),
186+
IdentifierName(p.PropertyName),
93187
GetCreationExpression(p.PropertyValueGetter(obj)))))))));
94188

95-
protected static Func<T, object> CreatePropertyInitializer(PropertyInfo property)
189+
protected static Func<T, object> CreatePropertyValueGetter(PropertyInfo property)
96190
{
97191
var objectParameter = Expression.Parameter(typeof(T), name: "o");
98192
return Expression.Lambda<Func<T, object>>(
@@ -108,17 +202,20 @@ protected class PropertyData
108202
public PropertyData(
109203
string propertyName,
110204
Type propertyType,
205+
Type declaringType,
111206
Func<T, object> propertyValueGetter,
112207
Func<T, object, bool> propertyCondition)
113208
{
114209
PropertyName = propertyName;
115210
PropertyType = propertyType;
211+
DeclaringType = declaringType;
116212
PropertyValueGetter = propertyValueGetter;
117213
PropertyCondition = propertyCondition;
118214
}
119215

120216
public string PropertyName { get; }
121217
public Type PropertyType { get; }
218+
public Type DeclaringType { get; }
122219
public Func<T, object> PropertyValueGetter { get; }
123220
public Func<T, object, bool> PropertyCondition { get; }
124221
}

test/CSharpScriptSerializer.Tests/CSharpScriptSerializer.Tests.csproj

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,8 @@
33
<PropertyGroup>
44
<AssemblyName>CSharpScriptSerializer.Tests</AssemblyName>
55
<PackageId>CSharpScriptSerializer.Tests</PackageId>
6-
<VersionPrefix>1.4.0</VersionPrefix>
7-
<TargetFrameworks>netcoreapp1.1;net46</TargetFrameworks>
8-
<RuntimeFrameworkVersion Condition=" '$(TargetFramework)' == 'netcoreapp1.1' ">1.1.1</RuntimeFrameworkVersion>
6+
<VersionPrefix>1.5.0</VersionPrefix>
7+
<TargetFrameworks>netcoreapp2.1;net46</TargetFrameworks>
98
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
109
<GenerateRuntimeConfigurationFiles>true</GenerateRuntimeConfigurationFiles>
1110
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
@@ -17,12 +16,12 @@
1716

1817
<ItemGroup>
1918
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.0.0" />
20-
<PackageReference Include="xunit.runner.visualstudio" Version="2.2.0" />
21-
<PackageReference Include="xunit" Version="2.2.0" />
19+
<PackageReference Include="xunit.runner.visualstudio" Version="2.3.1" />
20+
<PackageReference Include="xunit" Version="2.3.1" />
2221
</ItemGroup>
2322

24-
<ItemGroup Condition=" '$(TargetFramework)' == 'netcoreapp1.1' ">
25-
<PackageReference Include="Microsoft.CSharp" Version="4.3.0" />
23+
<ItemGroup Condition=" '$(TargetFramework)' == 'netcoreapp2.1' ">
24+
<PackageReference Include="Microsoft.CSharp" Version="4.5.0" />
2625
</ItemGroup>
2726

2827
<ItemGroup Condition=" '$(TargetFramework)' == 'net46' ">

0 commit comments

Comments
 (0)