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

Бабинцев Григорий #217

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
21 changes: 21 additions & 0 deletions ObjectPrinting/ObjectExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ObjectPrinting
{
public static class ObjectExtensions
{
public static string PrintToString<T>(this T obj)
{
return ObjectPrinter.For<T>().PrintToString(obj);
}

public static string PrintToString<T>(this T obj, Func<PrintingConfig<T>, PrintingConfig<T>> config)
{
return config(ObjectPrinter.For<T>()).PrintToString(obj);
}
}
}
1 change: 1 addition & 0 deletions ObjectPrinting/ObjectPrinting.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="FluentAssertions" Version="7.0.0" />

Choose a reason for hiding this comment

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

Для тестов лучше иметь отдельный проект, чтобы не тащить лишние зависимости в основную сборку

<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
<PackageReference Include="NUnit" Version="4.2.2" />
<PackageReference Include="NUnit3TestAdapter" Version="4.6.0" />
Expand Down
121 changes: 105 additions & 16 deletions ObjectPrinting/PrintingConfig.cs
Original file line number Diff line number Diff line change
@@ -1,41 +1,130 @@
using System;
using System.Collections;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;

namespace ObjectPrinting
{
public class PrintingConfig<TOwner>

Choose a reason for hiding this comment

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

Старайся соблюдать порядок членов класса
Можно ориентироваться вот на это

{
public string PrintToString(TOwner obj)
private readonly SerrializeConfig serrializeConfig;

public PrintingConfig()
{
serrializeConfig = new SerrializeConfig();
}

public PrintingConfig(SerrializeConfig serrializeConfig)
{
return PrintToString(obj, 0);
this.serrializeConfig = serrializeConfig;
}

private string PrintToString(object obj, int nestingLevel)
{
//TODO apply configurations
if (obj == null)
return "null" + Environment.NewLine;

var finalTypes = new[]
{
typeof(int), typeof(double), typeof(float), typeof(string),
typeof(DateTime), typeof(TimeSpan)
};
if (finalTypes.Contains(obj.GetType()))
return obj + Environment.NewLine;
var objType = obj.GetType();

if (serrializeConfig.TypeSerrializers.TryGetValue(objType, out var serrialize))
return serrialize.DynamicInvoke(obj) + Environment.NewLine;

if (obj is ICollection collection)
return SerializeCollection(collection, nestingLevel);

Choose a reason for hiding this comment

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

Вообще на это можно посмотреть как на предопределенный сериализатор по типу. И было бы здорово уметь определять такие сбоку от этого класса, чтобы не приходилось модифицировать его при добавлении новых сериализаторов. Но это так, размышления)... делать не надо


var identation = new string('\t', nestingLevel + 1);

var sb = new StringBuilder();
var type = obj.GetType();
sb.AppendLine(type.Name);
foreach (var propertyInfo in type.GetProperties())

sb.AppendLine(objType.Name);

foreach (var propertyInfo in objType.GetProperties())
{
sb.Append(identation + propertyInfo.Name + " = " +
PrintToString(propertyInfo.GetValue(obj),
nestingLevel + 1));
var propType = propertyInfo.PropertyType;
var propValue = propertyInfo.GetValue(obj);

if (serrializeConfig.ExcludedTypes.Contains(propType) ||
serrializeConfig.ExcludedProperties.Contains(propertyInfo))
continue;

var contains = serrializeConfig.PropertySerrializers.TryGetValue(propertyInfo, out serrialize);

var objStr = contains ?
serrialize.DynamicInvoke(propValue) + Environment.NewLine :
PrintToString(propertyInfo.GetValue(obj), nestingLevel + 1);

sb.Append(identation + propertyInfo.Name + " = " + objStr);
}

return sb.ToString();
}

private string SerializeCollection(ICollection collection, int nestingLevel)
{
if (collection is IDictionary dictionary)
return SerializeDictionary(dictionary, nestingLevel);

var sb = new StringBuilder();

foreach (var item in collection)
sb.Append(PrintToString(item, nestingLevel + 1).Trim() + " ");

return $"[ {sb}]" + Environment.NewLine;
}

private string SerializeDictionary(IDictionary dictionary, int nestingLevel)
{
var sb = new StringBuilder();
var identation = new string('\t', nestingLevel);
sb.Append(identation + "{" + Environment.NewLine);
foreach (DictionaryEntry keyValuePair in dictionary)

Choose a reason for hiding this comment

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

Честно говоря, не понятно как оно работает. А ты понимаешь? Мб надежнее обходить ключи?

{
identation = new string('\t', nestingLevel + 1);
sb.Append(identation + "[" + PrintToString(keyValuePair.Key, nestingLevel + 1).Trim() + " - ");

Choose a reason for hiding this comment

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

Можно использовать интерполяцию или sb.
StringBuilder кстати тоже имеет fluentAPI и метод AppendLine там есть)

sb.Append(PrintToString(keyValuePair.Value, 0).Trim());
sb.Append("],");
sb.Append(Environment.NewLine);
}

return sb + "}" + Environment.NewLine;
}

public string PrintToString(TOwner obj) => PrintToString(obj, 0);

public PrintingConfig<TOwner> Exclude<T>()
{
var config = new PrintingConfig<TOwner>(serrializeConfig);

config.serrializeConfig.ExcludedTypes.Add(typeof(T));

return config;
}

public PrintingConfig<TOwner> Exclude<TProp>(Expression<Func<TOwner, TProp>> memberSelector)
{
var config = new PrintingConfig<TOwner>(serrializeConfig);

var memberExp = memberSelector.Body as MemberExpression;
var propInfo = memberExp.Member as PropertyInfo;

Choose a reason for hiding this comment

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

Будет null, если попытаться обработать поле (экспрешн не запрещает)


config.serrializeConfig.ExcludedProperties.Add(propInfo);

return config;
}

public PropertyPrintingConfig<TOwner, T> Print<T>()
{
return new PropertyPrintingConfig<TOwner, T>(serrializeConfig);
}

public PropertyPrintingConfig<TOwner, TProp> Print<TProp>(Expression<Func<TOwner, TProp>> memberSelector)
{
var memberExp = memberSelector.Body as MemberExpression;
var propInfo = memberExp.Member as PropertyInfo;

return new PropertyPrintingConfig<TOwner, TProp>(serrializeConfig, propInfo);
}
}
}
44 changes: 44 additions & 0 deletions ObjectPrinting/PropertyPrintingConfig.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace ObjectPrinting
{
public class PropertyPrintingConfig<TOwner, TProp>
{
private readonly SerrializeConfig serrializeConfig;
private readonly PropertyInfo propertyInfo;

public PropertyPrintingConfig(SerrializeConfig serrializeConfig, PropertyInfo propertyInfo = null)
{
this.serrializeConfig = new SerrializeConfig(serrializeConfig);
this.propertyInfo = propertyInfo;
}

public PrintingConfig<TOwner> Using(Func<TProp, string> serrializer)
{
if (propertyInfo == null)

Choose a reason for hiding this comment

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

В проекте включены nullable types, но ты не размечаешь члены классов (какие могут быть null, какие нет). Надо либо выключить либо разметить (лучше разметить)

{
var type = typeof(TProp);

if (serrializeConfig.TypeSerrializers.ContainsKey(type))

Choose a reason for hiding this comment

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

rider/resharper подсказывает, что можно вот так сократить
serrializeConfig.TypeSerrializers[type] = serrializer;

serrializeConfig.TypeSerrializers[type] = serrializer;
else
serrializeConfig.TypeSerrializers.Add(type, serrializer);
}
else
{
if (serrializeConfig.PropertySerrializers.ContainsKey(propertyInfo))
serrializeConfig.PropertySerrializers[propertyInfo] = serrializer;
else
serrializeConfig.PropertySerrializers.Add(propertyInfo, serrializer);
}


return new PrintingConfig<TOwner>(serrializeConfig);
}
}
}
32 changes: 32 additions & 0 deletions ObjectPrinting/PropertyPrintingConfigExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ObjectPrinting
{
public static class PropertyPrintingConfigExtensions
{
public static PrintingConfig<TOwner> TrimmedToLength<TOwner>(this PropertyPrintingConfig<TOwner, string> propConfig, int maxLen)
{
return propConfig.Using(property => property.Substring(0, Math.Min(maxLen, property.Length)).ToString());
}

public static PrintingConfig<TOwner> Using<TOwner>(this PropertyPrintingConfig<TOwner, DateTime> propConfig, CultureInfo culture)
{
return propConfig.Using(x => x.ToString(culture));
}

public static PrintingConfig<TOwner> Using<TOwner>(this PropertyPrintingConfig<TOwner, double> propConfig, CultureInfo culture)
{
return propConfig.Using(x => x.ToString(culture));
}

public static PrintingConfig<TOwner> Using<TOwner>(this PropertyPrintingConfig<TOwner, float> propConfig, CultureInfo culture)
{
return propConfig.Using(x => x.ToString(culture));
}
}
}
58 changes: 58 additions & 0 deletions ObjectPrinting/SerrializeConfig.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace ObjectPrinting
{
public class SerrializeConfig
{
/*
* У объекта для серриализации может быть вложенное поле, имеющшее тип, который мы сами определили,
* и для него тоже может быть возможность определить кастомный сериализатор.
* Также изначально не известно какие собственные типы есть у объекта, поэтому нужен отдельный хеш-сет,
* где хранятся все исключенные типы.
*/

public readonly HashSet<Type> ExcludedTypes;
public readonly HashSet<PropertyInfo> ExcludedProperties;

public readonly Dictionary<Type, Delegate> TypeSerrializers;
public readonly Dictionary<PropertyInfo, Delegate> PropertySerrializers;

Choose a reason for hiding this comment

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

Наверное сюда можно класть не любые делегаты? Давай строго зафиксируем тип


public SerrializeConfig()
{
ExcludedTypes = new HashSet<Type>();

ExcludedProperties = new HashSet<PropertyInfo>();

TypeSerrializers = new Dictionary<Type, Delegate>
{
{ typeof(int), DefaultSerrialize },
{ typeof(double), DefaultSerrialize },
{ typeof(float), DefaultSerrialize },
{ typeof(string), DefaultSerrialize },
{ typeof(DateTime), DefaultSerrialize },
{ typeof(TimeSpan), DefaultSerrialize },
{ typeof(Guid), DefaultSerrialize },
};

PropertySerrializers = new Dictionary<PropertyInfo, Delegate>();
}

public SerrializeConfig(SerrializeConfig old)
{
ExcludedTypes = new HashSet<Type>(old.ExcludedTypes);

ExcludedProperties = new HashSet<PropertyInfo>(old.ExcludedProperties);

TypeSerrializers = new Dictionary<Type, Delegate>(old.TypeSerrializers);

PropertySerrializers = new Dictionary<PropertyInfo, Delegate>(old.PropertySerrializers);
}

private string DefaultSerrialize(object obj) => obj.ToString();
}
}
42 changes: 31 additions & 11 deletions ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using NUnit.Framework;
using System.Globalization;
using System;

namespace ObjectPrinting.Tests
{
Expand All @@ -10,18 +12,36 @@ public void Demo()
{
var person = new Person { Name = "Alex", Age = 19 };

var printer = ObjectPrinter.For<Person>();
//1. Исключить из сериализации свойства определенного типа
//2. Указать альтернативный способ сериализации для определенного типа
//3. Для числовых типов указать культуру
//4. Настроить сериализацию конкретного свойства
//5. Настроить обрезание строковых свойств (метод должен быть виден только для строковых свойств)
//6. Исключить из сериализации конкретного свойства

string s1 = printer.PrintToString(person);

//7. Синтаксический сахар в виде метода расширения, сериализующего по-умолчанию
var printer = ObjectPrinter.For<Person>()
//1. Исключить из сериализации свойства определенного типа
.Exclude<Guid>()

//2. Указать альтернативный способ сериализации для определенного типа
.Print<int>().Using(x => $"value - {x}")

//3. Для числовых типов указать культуру
.Print<double>().Using(CultureInfo.InvariantCulture)

//4. Настроить сериализацию конкретного свойства
.Print(x => x.Name).Using(x => $"name - {x}")

//5. Настроить обрезание строковых свойств (метод должен быть виден только для строковых свойств)
.Print<string>().TrimmedToLength(10)

//6. Исключить из сериализации конкретного свойства
.Exclude(x => x.Age);

var s1 = printer.PrintToString(person);

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

//8. ...с конфигурированием
var s3 = person.PrintToString(x => x.Exclude(x => x.Name));

Console.WriteLine(s1);
Console.WriteLine(s2);
Console.WriteLine(s3);
}
}
}
Loading