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

Рыбин Леонид #229

Open
wants to merge 9 commits 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
16 changes: 16 additions & 0 deletions ObjectPrinting/Extensions/CultureExtension.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using System;
using System.Globalization;

namespace ObjectPrinting.Extensions;

public static class CultureExtension
{
public static PrintingConfig<TOwner> UseCulture<TOwner, TPropType>(
this PropertyPrintingConfiguration<TOwner, TPropType> propertyStringConfiguration,
CultureInfo culture) where TPropType : IFormattable
{
propertyStringConfiguration.ParentConfig.AddTypeSerializer<TPropType>(t => ((TPropType)t).ToString(null, culture));

return propertyStringConfiguration.ParentConfig;
}
}
12 changes: 12 additions & 0 deletions ObjectPrinting/Extensions/ObjectPrinterExtension.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using System;

namespace ObjectPrinting.Extensions;

public static class ObjectPrinterExtension
{
public static string PrintToString<T>(this T obj) =>
ObjectPrinter.For<T>().PrintToString(obj);

public static string PrintToString<T>(this T obj, Func<PrintingConfig<T>, PrintingConfig<T>> config) =>
config(ObjectPrinter.For<T>()).PrintToString(obj);
}
13 changes: 13 additions & 0 deletions ObjectPrinting/Extensions/StringPrintingConfigurationExtension.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
namespace ObjectPrinting.Extensions;

public static class StringPrintingConfigurationExtension
{
public static PrintingConfig<TOwner> TrimmedToLength<TOwner>(
this PropertyPrintingConfiguration<TOwner, string> propertyStringConfiguration, int length)
{
propertyStringConfiguration.ParentConfig
.AddStringPropertyTrim(propertyStringConfiguration.PropertyMemberInfo.Name, length);

return propertyStringConfiguration.ParentConfig;
}
}
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" />
<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
163 changes: 151 additions & 12 deletions ObjectPrinting/PrintingConfig.cs
Original file line number Diff line number Diff line change
@@ -1,41 +1,180 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;

namespace ObjectPrinting
{
public class PrintingConfig<TOwner>
{
private readonly HashSet<Type> excludedTypes = new();
private readonly HashSet<MemberInfo> excludedProperties = new();
private readonly Dictionary<Type, Func<Object, string>> typeSerializers = new();
private readonly Dictionary<string, Func<Object, string>> propertySerializers = new();
private readonly Dictionary<string, int> propertyTrim = new();
private int maxNestingLevel = 5;

public int MaxNestingLevel
{
get => maxNestingLevel;
private set
{
if (value <= 0)
{
throw new ArgumentOutOfRangeException(
nameof(value), value, "The maxNestingDepth value must be positive.");
}
maxNestingLevel = value;
}
}

private readonly HashSet<Type> finalTypes =
[
typeof(bool), typeof(sbyte), typeof(byte), typeof(short), typeof(ushort),
typeof(int), typeof(uint), typeof(long), typeof(ulong), typeof(float),
typeof(double), typeof(decimal), typeof(string), typeof(DateTime), typeof(TimeSpan), typeof(Guid)
];

public PrintingConfig<TOwner> Exclude<TPropType>()
{
excludedTypes.Add(typeof(TPropType));
return this;
}

public PropertyPrintingConfiguration<TOwner, TPropType> Printing<TPropType>(
Expression<Func<TOwner, TPropType>> propertySelector)
{
var memberInfo = GetPropertyName(propertySelector);
return new PropertyPrintingConfiguration<TOwner, TPropType>(this, memberInfo);
}

public PropertyPrintingConfiguration<TOwner, TPropType> Printing<TPropType>()
{
return new PropertyPrintingConfiguration<TOwner, TPropType>(this, null);
}

public PrintingConfig<TOwner> Exclude<TPropType>(Expression<Func<TOwner, TPropType>> propertySelector)
{
var propertyName = GetPropertyName(propertySelector);
excludedProperties.Add(propertyName);
return this;
}

public string PrintToString(TOwner obj)
{
return PrintToString(obj, 0);
}


private string PrintToString(object obj, int nestingLevel)
{
//TODO apply configurations
if (nestingLevel > MaxNestingLevel)
return "Maximum serialization depth has been reached";

if (obj == null)
return "null" + Environment.NewLine;
return "null";

var type = obj.GetType();

if (excludedTypes.Contains(type))
return string.Empty;

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

if (typeSerializers.TryGetValue(type, out var serializer))
return serializer(obj).ToString();

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

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.Append(identation + propertyInfo.Name + " = " +
PrintToString(propertyInfo.GetValue(obj),
nestingLevel + 1));
var propertyValue = propertyInfo.GetValue(obj);
var propertyType = propertyInfo.PropertyType;
var propertyName = propertyInfo.Name;

if (excludedProperties.Contains(propertyInfo))
continue;
if (excludedTypes.Contains(propertyType))
continue;

if (typeSerializers.TryGetValue(propertyType, out var typeSerializer))
{
sb.AppendLine($"{identation}{propertyName} = {typeSerializer(propertyValue)}");
continue;
}

if (propertySerializers.TryGetValue(propertyName, out var propertySerializer))
{
sb.AppendLine($"{identation}{propertyName} = {propertySerializer(propertyValue)}");
continue;
}

if (propertyTrim.TryGetValue(propertyName, out var toTrimLength) && propertyValue is string stringValue)
{
sb.AppendLine($"{identation}{propertyName} = {stringValue.Substring(0, Math.Min(toTrimLength, stringValue.Length))}");
continue;
}

sb.AppendLine($"{identation}{propertyName} = {PrintToString(propertyValue, nestingLevel + 1)}");
}
return sb.ToString();
}

private string SerializeCollection(ICollection collection, int nestingLevel)
{
var identation = new string('\t', nestingLevel + 1);
var sb = new StringBuilder();

sb.AppendLine("[");

if (collection is IDictionary dictionary)
{
foreach (var key in dictionary.Keys)
{
sb.Append($"{identation}{{{PrintToString(key, nestingLevel)}: " +
$"{PrintToString(dictionary[key], nestingLevel + 1)}}}\n");
}
}
else
{
foreach (var value in collection)
{
sb.Append('\t', nestingLevel + 1);
sb.Append(PrintToString(value, nestingLevel + 1));
}
}

sb.AppendLine($"{identation}]");
return sb.ToString();
}

private MemberInfo GetPropertyName<TPropType>(Expression<Func<TOwner, TPropType>> propertySelector)
{
if (propertySelector.Body is MemberExpression memberExpression)
return memberExpression.Member;

if (propertySelector.Body is UnaryExpression unaryExpression && unaryExpression.Operand is MemberExpression operand)
return operand.Member;

throw new ArgumentException("Invalid property selector expression");
}

internal void AddPropertySerializer(string propertyName, Func<object, string> serializer) =>
propertySerializers[propertyName] = serializer;

internal void AddTypeSerializer<TPropType>(Func<object, string> serializer) =>
typeSerializers[typeof(TPropType)] = serializer;

internal void AddStringPropertyTrim(string propertyName, int maxLength) =>
propertyTrim[propertyName] = maxLength;
}
}
33 changes: 33 additions & 0 deletions ObjectPrinting/PropertyPrintingConfiguration.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using System;
using System.Reflection;

namespace ObjectPrinting;

public class PropertyPrintingConfiguration<TOwner, TPropType>
{
private readonly PrintingConfig<TOwner> parentConfig;
private readonly MemberInfo? propertyMemberInfo;

internal PrintingConfig<TOwner> ParentConfig => parentConfig;
internal MemberInfo? PropertyMemberInfo => propertyMemberInfo;

public PropertyPrintingConfiguration(PrintingConfig<TOwner> parentConfig, MemberInfo? propertyMemberInfo)
{
this.parentConfig = parentConfig;
this.propertyMemberInfo = propertyMemberInfo;
}

public PrintingConfig<TOwner> Using(Func<TPropType, string> printingMethod)
{
if (propertyMemberInfo == null)
{
parentConfig.AddTypeSerializer<TPropType>(obj => printingMethod((TPropType)obj));
}
else
{
parentConfig.AddPropertySerializer(propertyMemberInfo.Name, obj => printingMethod((TPropType)obj));
}

return parentConfig;
}
}
23 changes: 19 additions & 4 deletions ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
using NUnit.Framework;
using System;
using System.Globalization;
using ObjectPrinting.Extensions;

namespace ObjectPrinting.Tests
{
Expand All @@ -8,20 +11,32 @@ public class ObjectPrinterAcceptanceTests
[Test]
public void Demo()
{
var person = new Person { Name = "Alex", Age = 19 };
var person = new Person { Age = 19, Name = "Alex", Height = 173.65, Id = new Guid() };

var printer = ObjectPrinter.For<Person>();
var printer = ObjectPrinter.For<Person>()
//1. Исключить из сериализации свойства определенного типа
.Exclude<Guid>()
//2. Указать альтернативный способ сериализации для определенного типа
.Printing<int>().Using(i => i.ToString("X"))
//3. Для числовых типов указать культуру
.Printing<double>().UseCulture(CultureInfo.InvariantCulture)
//4. Настроить сериализацию конкретного свойства
.Printing(x => x.Name).Using(p => $"---{p}---")
//5. Настроить обрезание строковых свойств (метод должен быть виден только для строковых свойств)
.Printing(p => p.Name).TrimmedToLength(10)
//6. Исключить из сериализации конкретного свойства

.Exclude(p => p.Age);

string s1 = printer.PrintToString(person);
Console.WriteLine(s1);

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

//7. Синтаксический сахар в виде метода расширения, сериализующего по-умолчанию
//8. ...с конфигурированием
var s3 = person.PrintToString(p => p.Exclude(x => x.Age));
Console.WriteLine(s3);
}
}
}
Loading