-
Notifications
You must be signed in to change notification settings - Fork 214
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
Козлов Данил #185
base: master
Are you sure you want to change the base?
Козлов Данил #185
Changes from 5 commits
cdfbfce
593adff
56c0639
4b029ce
2a7369f
a68eb7c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,14 +1,16 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
|
||
<PropertyGroup> | ||
<LangVersion>8</LangVersion> | ||
<TargetFramework>netcoreapp3.1</TargetFramework> | ||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath> | ||
</PropertyGroup> | ||
<PropertyGroup> | ||
<LangVersion>9.0</LangVersion> | ||
<TargetFramework>net8.0</TargetFramework> | ||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.1" /> | ||
<PackageReference Include="NUnit" Version="3.12.0" /> | ||
</ItemGroup> | ||
<ItemGroup> | ||
<PackageReference Include="FluentAssertions" Version="6.12.0"/> | ||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.1"/> | ||
<PackageReference Include="NUnit" Version="3.12.0"/> | ||
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0"/> | ||
</ItemGroup> | ||
|
||
</Project> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,41 +1,184 @@ | ||
using System; | ||
using System.Collections; | ||
using System.Collections.Generic; | ||
using System.Collections.Immutable; | ||
using System.Globalization; | ||
using System.Linq; | ||
using System.Linq.Expressions; | ||
using System.Reflection; | ||
using System.Text; | ||
|
||
namespace ObjectPrinting | ||
{ | ||
public class PrintingConfig<TOwner> | ||
{ | ||
private static readonly HashSet<Type> _finalTypes = new() | ||
{ | ||
typeof(int), typeof(uint), typeof(double), typeof(float), typeof(decimal), | ||
typeof(long), typeof(ulong), typeof(short), typeof(ushort), | ||
typeof(string), typeof(bool), | ||
typeof(DateTime), typeof(TimeSpan), typeof(Guid) | ||
}; | ||
|
||
private readonly HashSet<MemberInfo> excludedProperties = new(); | ||
private readonly HashSet<Type> excludedTypes = new(); | ||
protected readonly Dictionary<MemberInfo, int> propertiesMaxLength = new(); | ||
protected readonly Dictionary<MemberInfo, Func<object, string>> propertiesSerialization = new(); | ||
protected readonly Dictionary<Type, CultureInfo> typesCulture = new(); | ||
protected readonly Dictionary<Type, Func<object, string>> typesSerialization = new(); | ||
private Func<dynamic> recursivePropertiesSerialization; | ||
|
||
public PrintingConfig() | ||
{ | ||
} | ||
|
||
protected PrintingConfig(PrintingConfig<TOwner> config) | ||
{ | ||
typesCulture = config.typesCulture; | ||
propertiesMaxLength = config.propertiesMaxLength; | ||
propertiesSerialization = config.propertiesSerialization; | ||
typesSerialization = config.typesSerialization; | ||
} | ||
|
||
public string PrintToString(TOwner obj) | ||
{ | ||
return PrintToString(obj, 0); | ||
return SerializeObject(obj, ImmutableList<object>.Empty); | ||
} | ||
|
||
public PrintingConfig<TOwner> Excluding<TPropType>() | ||
{ | ||
excludedTypes.Add(typeof(TPropType)); | ||
|
||
return this; | ||
} | ||
|
||
public PrintingConfig<TOwner> Excluding<TPropType>(Expression<Func<TOwner, TPropType>> property) | ||
{ | ||
var memberBody = (MemberExpression)property.Body; | ||
var propertyInfo = memberBody.Member; | ||
|
||
excludedProperties.Add(propertyInfo); | ||
|
||
return this; | ||
} | ||
|
||
public TypePrintingConfig<TOwner, TPropType> Printing<TPropType>() | ||
{ | ||
return new TypePrintingConfig<TOwner, TPropType>(this); | ||
} | ||
|
||
private string PrintToString(object obj, int nestingLevel) | ||
public PropertyPrintingConfig<TOwner, TPropType> Printing<TPropType>( | ||
Expression<Func<TOwner, TPropType>> printProperty) | ||
{ | ||
var memberBody = (MemberExpression)printProperty.Body; | ||
var propertyInfo = memberBody.Member; | ||
|
||
return new PropertyPrintingConfig<TOwner, TPropType>(this, propertyInfo); | ||
} | ||
|
||
public PrintingConfig<TOwner> PrintRecursion(Exception exception) | ||
{ | ||
recursivePropertiesSerialization = () => throw exception; | ||
|
||
return this; | ||
} | ||
|
||
public PrintingConfig<TOwner> PrintRecursion(Func<string> printProperty) | ||
{ | ||
recursivePropertiesSerialization = printProperty; | ||
|
||
return this; | ||
} | ||
|
||
private string SerializeObject(object obj, IImmutableList<object> previousObjects) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Немного пересложнил, здесь явно нужна некоторая декомпозиция, мб вынос в отдельную абстракцию сериалайзера |
||
{ | ||
//TODO apply configurations | ||
if (obj == null) | ||
return "null" + Environment.NewLine; | ||
return "null"; | ||
|
||
var finalTypes = new[] | ||
var type = obj.GetType(); | ||
if (previousObjects.Any(prev => prev == obj)) | ||
{ | ||
typeof(int), typeof(double), typeof(float), typeof(string), | ||
typeof(DateTime), typeof(TimeSpan) | ||
}; | ||
if (finalTypes.Contains(obj.GetType())) | ||
return obj + Environment.NewLine; | ||
|
||
var identation = new string('\t', nestingLevel + 1); | ||
var sb = new StringBuilder(); | ||
if (recursivePropertiesSerialization != null) | ||
return recursivePropertiesSerialization(); | ||
return "Recursive property"; | ||
} | ||
|
||
|
||
if (obj is IEnumerable && obj is not string) | ||
return SerializeEnumerable(obj, previousObjects); | ||
|
||
if (_finalTypes.Contains(type)) | ||
return SerializeFinalType(obj); | ||
|
||
var identation = new string('\t', previousObjects.Count + 1); | ||
previousObjects = previousObjects.Add(obj); | ||
var objectSerialization = new StringBuilder(); | ||
objectSerialization.AppendLine(type.Name); | ||
var members = Array.Empty<MemberInfo>().Concat(type.GetProperties()).Concat(type.GetFields()); | ||
foreach (var memberInfo in members) | ||
{ | ||
var memberType = memberInfo.MemberType == MemberTypes.Property | ||
? (memberInfo as PropertyInfo).PropertyType | ||
: (memberInfo as FieldInfo).FieldType; | ||
if (excludedProperties.Contains(memberInfo) | ||
|| excludedTypes.Contains(memberType)) | ||
continue; | ||
|
||
objectSerialization.Append(identation); | ||
objectSerialization.Append(memberInfo.Name); | ||
objectSerialization.Append(" = "); | ||
objectSerialization.Append(SerializeMember(memberInfo, obj, previousObjects)); | ||
objectSerialization.Append(Environment.NewLine); | ||
} | ||
|
||
return objectSerialization.ToString(); | ||
} | ||
|
||
private string SerializeFinalType(object obj) | ||
{ | ||
var type = obj.GetType(); | ||
sb.AppendLine(type.Name); | ||
foreach (var propertyInfo in type.GetProperties()) | ||
if (typesSerialization.TryGetValue(type, out var serialization)) | ||
return serialization.Invoke(obj); | ||
|
||
var result = obj.ToString(); | ||
if (typesCulture.ContainsKey(obj.GetType())) | ||
result = ((IFormattable)obj).ToString(null, typesCulture[type]); | ||
|
||
return result; | ||
} | ||
|
||
private string SerializeMember(MemberInfo memberInfo, object obj, IImmutableList<object> previousObjects) | ||
{ | ||
var value = memberInfo.MemberType == MemberTypes.Property | ||
? (memberInfo as PropertyInfo).GetValue(obj) | ||
: (memberInfo as FieldInfo).GetValue(obj); | ||
var serializedProperty = propertiesSerialization.TryGetValue(memberInfo, out var serialization) | ||
? serialization.Invoke(value) | ||
: SerializeObject(value, previousObjects).TrimEnd(); | ||
|
||
var propertyMaxLength = propertiesMaxLength.TryGetValue(memberInfo, out var length) | ||
? length | ||
: serializedProperty.Length; | ||
|
||
serializedProperty = serializedProperty[..propertyMaxLength]; | ||
|
||
return serializedProperty; | ||
} | ||
|
||
private string SerializeEnumerable(object obj, IImmutableList<object> previousObjects) | ||
{ | ||
var enumerable = (IEnumerable)obj; | ||
var serializedEnumerable = new StringBuilder(); | ||
var nestingLevel = previousObjects.Count == 0 ? 0 : previousObjects.Count + 1; | ||
var identation = nestingLevel == 0 ? "" : Environment.NewLine + new string('\t', nestingLevel); | ||
|
||
foreach (var item in enumerable) | ||
{ | ||
sb.Append(identation + propertyInfo.Name + " = " + | ||
PrintToString(propertyInfo.GetValue(obj), | ||
nestingLevel + 1)); | ||
serializedEnumerable.Append(identation); | ||
serializedEnumerable.Append(SerializeObject(item, previousObjects)); | ||
} | ||
return sb.ToString(); | ||
|
||
return serializedEnumerable.ToString(); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
using System; | ||
using System.Reflection; | ||
|
||
namespace ObjectPrinting | ||
{ | ||
public class PropertyPrintingConfig<TOwner, TPropType> : PrintingConfig<TOwner> | ||
{ | ||
private readonly MemberInfo propertyInfo; | ||
|
||
public PropertyPrintingConfig(PrintingConfig<TOwner> config, MemberInfo propertyInfo) : base(config) | ||
{ | ||
this.propertyInfo = propertyInfo; | ||
} | ||
|
||
public PropertyPrintingConfig<TOwner, TPropType> Using(Func<TPropType, string> printProperty) | ||
{ | ||
propertiesSerialization.TryAdd(propertyInfo, type => printProperty((TPropType)type)); | ||
|
||
return this; | ||
} | ||
|
||
public PropertyPrintingConfig<TOwner, TPropType> TrimToLength(int length) | ||
{ | ||
if (length < 0) | ||
throw new ArgumentException("length can not be negative"); | ||
|
||
propertiesMaxLength.Add(propertyInfo, length); | ||
|
||
return this; | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
using System.Collections.Generic; | ||
|
||
namespace ObjectPrinting.Tests | ||
{ | ||
public class File | ||
{ | ||
public string field; | ||
public string Name { get; set; } | ||
|
||
public Dictionary<string, string> Attributes { get; set; } | ||
|
||
public List<string> SimilarNames { get; set; } | ||
|
||
public string[] Copies { get; set; } | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,27 +1,26 @@ | ||
using NUnit.Framework; | ||
using System.Globalization; | ||
using NUnit.Framework; | ||
|
||
namespace ObjectPrinting.Tests | ||
{ | ||
[TestFixture] | ||
public class ObjectPrinterAcceptanceTests | ||
This comment was marked as resolved.
Sorry, something went wrong. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Разделил на классы обычные тесты и Acceptance Test There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Тестики лучше было вынести в отдельный проект, это не должно тащиться в либу |
||
{ | ||
[Test] | ||
public void Demo() | ||
public void Acceptance() | ||
{ | ||
var person = new Person { Name = "Alex", Age = 19 }; | ||
var person = new Person { Name = "Alex", Age = 19, Height = 1.2 }; | ||
|
||
var printer = ObjectPrinter.For<Person>(); | ||
//1. Исключить из сериализации свойства определенного типа | ||
//2. Указать альтернативный способ сериализации для определенного типа | ||
//3. Для числовых типов указать культуру | ||
//4. Настроить сериализацию конкретного свойства | ||
//5. Настроить обрезание строковых свойств (метод должен быть виден только для строковых свойств) | ||
//6. Исключить из сериализации конкретного свойства | ||
|
||
string s1 = printer.PrintToString(person); | ||
|
||
//7. Синтаксический сахар в виде метода расширения, сериализующего по-умолчанию | ||
//8. ...с конфигурированием | ||
var printer = ObjectPrinter.For<Person>() | ||
.Excluding<int>() | ||
.Printing<double>() | ||
.WithCulture<double>(CultureInfo.CurrentCulture) | ||
.Using(x => (x * 2).ToString()) | ||
.Printing(person => person.Name) | ||
.Using(name => $"{name}ing") | ||
.TrimToLength(2) | ||
.Excluding(person => person.Age) | ||
.PrintRecursion(() => "Recursion"); | ||
} | ||
} | ||
} |
This comment was marked as resolved.
Sorry, something went wrong.