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

Козлов Данил #185

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 5 commits
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
20 changes: 11 additions & 9 deletions ObjectPrinting/ObjectPrinting.csproj
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>
181 changes: 162 additions & 19 deletions ObjectPrinting/PrintingConfig.cs
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)

This comment was marked as resolved.

{
recursivePropertiesSerialization = () => throw exception;

return this;
}

public PrintingConfig<TOwner> PrintRecursion(Func<string> printProperty)
{
recursivePropertiesSerialization = printProperty;

return this;
}

private string SerializeObject(object obj, IImmutableList<object> previousObjects)
Copy link

Choose a reason for hiding this comment

The 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();
}
}
}
32 changes: 32 additions & 0 deletions ObjectPrinting/PropertyPrintingConfig.cs
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;
}
}
}
5 changes: 2 additions & 3 deletions ObjectPrinting/Solved/PrintingConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ public PropertyPrintingConfig<TOwner, TPropType> Printing<TPropType>()
return new PropertyPrintingConfig<TOwner, TPropType>(this);
}

public PropertyPrintingConfig<TOwner, TPropType> Printing<TPropType>(Expression<Func<TOwner, TPropType>> memberSelector)
public PropertyPrintingConfig<TOwner, TPropType> Printing<TPropType>(
Expression<Func<TOwner, TPropType>> memberSelector)
{
return new PropertyPrintingConfig<TOwner, TPropType>(this);
}
Expand Down Expand Up @@ -51,11 +52,9 @@ private string PrintToString(object obj, int nestingLevel)
var type = obj.GetType();
sb.AppendLine(type.Name);
foreach (var propertyInfo in type.GetProperties())
{
sb.Append(identation + propertyInfo.Name + " = " +
PrintToString(propertyInfo.GetValue(obj),
nestingLevel + 1));
}
return sb.ToString();
}
}
Expand Down
4 changes: 2 additions & 2 deletions ObjectPrinting/Solved/PropertyPrintingConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ public PropertyPrintingConfig(PrintingConfig<TOwner> printingConfig)
this.printingConfig = printingConfig;
}

PrintingConfig<TOwner> IPropertyPrintingConfig<TOwner, TPropType>.ParentConfig => printingConfig;

public PrintingConfig<TOwner> Using(Func<TPropType, string> print)
{
return printingConfig;
Expand All @@ -21,8 +23,6 @@ public PrintingConfig<TOwner> Using(CultureInfo culture)
{
return printingConfig;
}

PrintingConfig<TOwner> IPropertyPrintingConfig<TOwner, TPropType>.ParentConfig => printingConfig;
}

public interface IPropertyPrintingConfig<TOwner, TPropType>
Expand Down
4 changes: 2 additions & 2 deletions ObjectPrinting/Solved/PropertyPrintingConfigExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ public static string PrintToString<T>(this T obj, Func<PrintingConfig<T>, Printi
return config(ObjectPrinter.For<T>()).PrintToString(obj);
}

public static PrintingConfig<TOwner> TrimmedToLength<TOwner>(this PropertyPrintingConfig<TOwner, string> propConfig, int maxLen)
public static PrintingConfig<TOwner> TrimmedToLength<TOwner>(
this PropertyPrintingConfig<TOwner, string> propConfig, int maxLen)
{
return ((IPropertyPrintingConfig<TOwner, string>)propConfig).ParentConfig;
}

}
}
10 changes: 5 additions & 5 deletions ObjectPrinting/Solved/Tests/ObjectPrinterAcceptanceTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,13 @@ public void Demo()
//6. Исключить из сериализации конкретного свойства
.Excluding(p => p.Age);

string s1 = printer.PrintToString(person);
var s1 = printer.PrintToString(person);

//7. Синтаксический сахар в виде метода расширения, сериализующего по-умолчанию
string s2 = person.PrintToString();
var s2 = person.PrintToString();

//8. ...с конфигурированием
string s3 = person.PrintToString(s => s.Excluding(p => p.Age));
var s3 = person.PrintToString(s => s.Excluding(p => p.Age));
Console.WriteLine(s1);
Console.WriteLine(s2);
Console.WriteLine(s3);
Expand Down
16 changes: 16 additions & 0 deletions ObjectPrinting/Tests/File.cs
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; }
}
}
29 changes: 14 additions & 15 deletions ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs
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.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Разделил на классы обычные тесты и Acceptance Test

Copy link

Choose a reason for hiding this comment

The 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");
}
}
}
Loading