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

Брозовский Максим #224

Open
wants to merge 4 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
6 changes: 6 additions & 0 deletions ObjectPrinting/ObjectExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace ObjectPrinting;

public static class ObjectExtensions
{
public static string PrintToString<T>(this T obj) => ObjectPrinter.For<T>().PrintToString(obj);
}
12 changes: 4 additions & 8 deletions ObjectPrinting/ObjectPrinter.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
namespace ObjectPrinting
namespace ObjectPrinting;

public static class ObjectPrinter
{
public class ObjectPrinter
{
public static PrintingConfig<T> For<T>()
{
return new PrintingConfig<T>();
}
}
public static PrintingConfig<T> For<T>() => new();
}
37 changes: 37 additions & 0 deletions ObjectPrinting/ObjectPrinting.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,45 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="ApprovalTests" Version="7.0.0-beta.3" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
<PackageReference Include="NUnit" Version="4.2.2" />
<PackageReference Include="NUnit3TestAdapter" Version="4.6.0" />
</ItemGroup>

<ItemGroup>
<None Update="Tests\ObjectPrinterAcceptanceTests.Demo.approved.txt">
<DependentUpon>ObjectPrinterAcceptanceTests.cs</DependentUpon>
</None>
<None Update="Tests\ObjectPrinterAcceptanceTests.PrintToString_ShouldWorkCorrectly_WithCustomSerializationForSpecificType.approved.txt">
<DependentUpon>ObjectPrinterAcceptanceTests.cs</DependentUpon>
</None>
<None Update="Tests\ObjectPrinterAcceptanceTests.PrintToString_ShouldWorkCorrectly_WhenSerializingWithCustomCulture.approved.txt">
<DependentUpon>ObjectPrinterAcceptanceTests.cs</DependentUpon>
</None>
<None Update="Tests\ObjectPrinterAcceptanceTests.PrintToString_ShouldWorkCorrectly_WhenTrimmingStringProperties.approved.txt">
<DependentUpon>ObjectPrinterAcceptanceTests.cs</DependentUpon>
</None>
<None Update="Tests\ObjectPrinterAcceptanceTests.PrintToString_ShouldWorkCorrectly_WhenExcludingSpecificProperty.approved.txt">
<DependentUpon>ObjectPrinterAcceptanceTests.cs</DependentUpon>
</None>
<None Update="Tests\ObjectPrinterAcceptanceTests.PrintToString_ShouldWorkCorrectly_WhenChainingSerializationRules.approved.txt">
<DependentUpon>ObjectPrinterAcceptanceTests.cs</DependentUpon>
</None>
<None Update="Tests\ObjectPrinterAcceptanceTests.PrintToString_ShouldWorkCorrectly_WhenUsingExtensionMethod.approved.txt">
<DependentUpon>ObjectPrinterAcceptanceTests.cs</DependentUpon>
</None>
<None Update="Tests\ObjectPrinterAcceptanceTests.PrintToString_ShouldWorkCorrectly_WithConfiguration.approved.txt">
<DependentUpon>ObjectPrinterAcceptanceTests.cs</DependentUpon>
</None>
<None Update="Tests\ObjectPrinterAcceptanceTests.PrintToString_ShouldDetectCyclicReferences.approved.txt">
<DependentUpon>ObjectPrinterAcceptanceTests.cs</DependentUpon>
</None>
<None Update="Tests\ObjectPrinterAcceptanceTests.PrintToString_ShouldWorkCorrectly_WhenSerializingDictionary.approved.txt">
<DependentUpon>ObjectPrinterAcceptanceTests.cs</DependentUpon>
</None>
<None Update="Tests\ObjectPrinterAcceptanceTests.PrintToString_ShouldWorkCorrectly_WhenSerializingEnumerable.approved.txt">
<DependentUpon>ObjectPrinterAcceptanceTests.cs</DependentUpon>
</None>
</ItemGroup>
</Project>
161 changes: 136 additions & 25 deletions ObjectPrinting/PrintingConfig.cs
Original file line number Diff line number Diff line change
@@ -1,41 +1,152 @@
using System;
using System.Linq;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Linq.Expressions;
using System.Text;

namespace ObjectPrinting

namespace ObjectPrinting;

public class PrintingConfig<TOwner>
{
public class PrintingConfig<TOwner>
private readonly HashSet<Type> excludedTypes = [];
private readonly HashSet<string> excludedProperties = [];
private readonly Dictionary<Type, Func<object, string>> typeSerializers = new();
private readonly Dictionary<Type, CultureInfo> typeCultures = new();
private readonly Dictionary<string, Func<object, string>> propertySerializers = new();
private readonly HashSet<object> visitedObjects = [];

private static readonly HashSet<Type> FinalTypes =
[
typeof(int), typeof(double), typeof(float), typeof(string),
typeof(DateTime), typeof(TimeSpan)
];

public PropertyPrintingConfig<TOwner, TPropType> Printing<TPropType>() => new(this);

public PropertyPrintingConfig<TOwner, TPropType> Printing<TPropType>(
Expression<Func<TOwner, TPropType>> memberSelector) =>
new(this, memberSelector);

public PrintingConfig<TOwner> Excluding<TPropType>(Expression<Func<TOwner, TPropType>> memberSelector)
{
if (memberSelector.Body is not MemberExpression memberExpression)
throw new ArgumentException("Expression must be a member expression", nameof(memberSelector));
excludedProperties.Add(memberExpression.Member.Name);
return this;
}

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

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

public void AddSerializerForType<TPropType>(Func<TPropType, string> serializer) =>
typeSerializers[typeof(TPropType)] = obj => serializer((TPropType)obj);

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

public void AddCultureForType<TPropType>(CultureInfo culture) => typeCultures[typeof(TPropType)] = culture;

private string PrintToString(object? obj, int nestingLevel)
{
public string PrintToString(TOwner obj)
if (obj == null)
return "null" + Environment.NewLine;

var type = obj.GetType();

if (!visitedObjects.Add(obj) && !FinalTypes.Contains(type))
return "Cyclic reference!" + Environment.NewLine;

if (typeSerializers.TryGetValue(type, out var serializer))
return serializer(obj) + Environment.NewLine;

if (typeCultures.TryGetValue(type, out var culture) && obj is IFormattable formattable)
return formattable.ToString(null, culture) + Environment.NewLine;

if (FinalTypes.Contains(type))
return obj + Environment.NewLine;

return obj switch
{
return PrintToString(obj, 0);
IDictionary dictionary => SerializeDictionary(dictionary, nestingLevel),
IEnumerable enumerable => SerializeEnumerable(enumerable, nestingLevel),
_ => SerializeObject(obj, nestingLevel)
};
}

private string SerializeDictionary(IDictionary dictionary, int nestingLevel)
{
var dictionaryType = dictionary.GetType().Name;
var indentation = new string('\t', nestingLevel + 1);
var sb = new StringBuilder();

sb.AppendLine($"{dictionaryType}:");

foreach (DictionaryEntry entry in dictionary)
{
sb.Append(indentation + "Key = " +
PrintToString(entry.Key, nestingLevel + 1));
sb.Append(indentation + "Value = " +
PrintToString(entry.Value, nestingLevel + 1));
}

private string PrintToString(object obj, int nestingLevel)
visitedObjects.Remove(dictionary);
return sb.ToString();
}

private string SerializeEnumerable(IEnumerable enumerable, int nestingLevel)
{
var collectionType = enumerable.GetType().Name;
var indentation = new string('\t', nestingLevel + 1);
var sb = new StringBuilder();

sb.AppendLine($"{collectionType}:");

foreach (var element in enumerable)
{
//TODO apply configurations
if (obj == null)
return "null" + Environment.NewLine;
sb.Append(indentation + "- " +
PrintToString(element, nestingLevel + 1));
}

var finalTypes = new[]
visitedObjects.Remove(enumerable);
return sb.ToString();
}

private string SerializeObject(object obj, int nestingLevel)
{
var type = obj.GetType();
var indentation = new string('\t', nestingLevel + 1);
var sb = new StringBuilder();
sb.AppendLine(type.Name);

foreach (var propertyInfo in type.GetProperties())
{
if (excludedTypes.Contains(propertyInfo.PropertyType) ||
excludedProperties.Contains(propertyInfo.Name))
continue;

var propertyName = propertyInfo.Name;
var propertyValue = propertyInfo.GetValue(obj);

if (propertySerializers.TryGetValue(propertyName, out var propertySerializer))
{
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();
var type = obj.GetType();
sb.AppendLine(type.Name);
foreach (var propertyInfo in type.GetProperties())
sb.Append(indentation + propertyName + " = " +
propertySerializer(propertyValue!) + Environment.NewLine);
}
else
{
sb.Append(identation + propertyInfo.Name + " = " +
PrintToString(propertyInfo.GetValue(obj),
nestingLevel + 1));
sb.Append(indentation + propertyName + " = " +
PrintToString(propertyValue, nestingLevel + 1));
}
return sb.ToString();
}

visitedObjects.Remove(obj);
return sb.ToString();
}
}
33 changes: 33 additions & 0 deletions ObjectPrinting/PropertyPrintingConfig.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using System;
using System.Globalization;
using System.Linq.Expressions;

namespace ObjectPrinting;

public class PropertyPrintingConfig<TOwner, TPropType>(PrintingConfig<TOwner> printingConfig,
Expression<Func<TOwner, TPropType>> memberSelector = null!) : IPropertyPrintingConfig<TOwner, TPropType>
{
public Expression<Func<TOwner, TPropType>> MemberSelector => memberSelector;

public PrintingConfig<TOwner> Using(Func<TPropType, string> print)
{
printingConfig.AddSerializerForType(print);
return printingConfig;
}

public PrintingConfig<TOwner> Using(CultureInfo culture)
{
if (!typeof(TPropType).IsValueType || !typeof(IFormattable).IsAssignableFrom(typeof(TPropType)))
throw new InvalidOperationException($"Can't apply culture for type: {typeof(TPropType)}");

printingConfig.AddCultureForType<TPropType>(culture);
return printingConfig;
}

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

public interface IPropertyPrintingConfig<TOwner, TPropType>
{
PrintingConfig<TOwner> ParentConfig { get; }
}
29 changes: 29 additions & 0 deletions ObjectPrinting/PropertyPrintingConfigExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using System;
using System.Linq.Expressions;

namespace ObjectPrinting;

public static class PropertyPrintingConfigExtensions
{
public static string PrintToString<T>(this T obj, Func<PrintingConfig<T>, PrintingConfig<T>> config) =>
config(ObjectPrinter.For<T>()).PrintToString(obj);

public static PrintingConfig<TOwner> TrimmedToLength<TOwner>(
this PropertyPrintingConfig<TOwner, string> propConfig, int maxLen)
{
if (maxLen < 0)
throw new ArgumentOutOfRangeException(nameof(maxLen), "Max length must be non-negative!");

IPropertyPrintingConfig<TOwner, string> propertyConfig = propConfig;
var printingConfig = propertyConfig.ParentConfig;
var propertyName = ((MemberExpression)propConfig.MemberSelector.Body).Member.Name;

printingConfig.AddSerializerForProperty(propertyName, value =>
{
var val = value as string ?? string.Empty;
return val.Length > maxLen ? val[..maxLen] : val;
});

return printingConfig;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
Person
Id = Guid
Name = Alex
Height = 1,81
Age = 19
DateOfBirth = 09.09.1985 00:00:00
Addresses = null
Children = null
Father = null
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
Person
Id = Guid
Name = Alex
Height = 1,81
Age = 19
DateOfBirth = 01.01.0001 00:00:00
Addresses = null
Children = null
Father = Person
Id = Guid
Name = Pavel Doe
Height = 0
Age = 68
DateOfBirth = 09.09.1954 00:00:00
Addresses = null
Children = null
Father = Cyclic reference!
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Person
Name = Maxwe
Height = 1.81
DateOfBirth = понедельник, 1 января 0001 г.
Addresses = null
Children = null
Father = null
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
Person
Id = Guid
Name = Alex
Age = 19
DateOfBirth = 01.01.0001 00:00:00
Addresses = null
Children = null
Father = null
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
Person
Id = Guid
Name = Alex
Height = 1,81
Age = 19
Addresses = null
Children = null
Father = null
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
Person
Id = Guid
Name = Alex
Height = 1,81
Age = 19
DateOfBirth = 01.01.0001 00:00:00
Addresses = Dictionary`2:
Key = 1
Value = London
Key = 2
Value = New York
Key = 3
Value = Moscow
Children = null
Father = null
Loading