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

Зубков Андрей #193

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
26 changes: 26 additions & 0 deletions ObjectPrinting/MemberPrintingConfig.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using System;
using System.Reflection;

namespace ObjectPrinting;

public class MemberPrintingConfig<TOwner, TMemberType>
{
internal readonly PrintingConfig<TOwner> PrintingConfig;
internal readonly MemberInfo MemberInfo;

public MemberPrintingConfig(PrintingConfig<TOwner> printingConfig, MemberInfo memberInfo = null)
{
PrintingConfig = printingConfig;
MemberInfo = memberInfo;
}

public PrintingConfig<TOwner> Using(Func<TMemberType, string> printingMethod)
{
if (MemberInfo is null)
PrintingConfig.CustomTypeSerializers[typeof(TMemberType)] = printingMethod;
else
PrintingConfig.CustomMemberSerializers[MemberInfo] = printingMethod;

return PrintingConfig;
}
}
33 changes: 33 additions & 0 deletions ObjectPrinting/MemberPrintingConfigExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using System;
using System.Globalization;

namespace ObjectPrinting;

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

public static PrintingConfig<TOwner> TrimmedToLength<TOwner>(
this MemberPrintingConfig<TOwner, string> memberConfig, int maxLen)
{
if (maxLen < 0)
throw new ArgumentException("Length to trim must be non negative");

var memberInfo = memberConfig.MemberInfo;
memberConfig.PrintingConfig.TrimmedMembers[memberInfo] = maxLen;

return memberConfig.PrintingConfig;
}

public static PrintingConfig<TOwner> WithCulture<TOwner, TMemberType>(
this MemberPrintingConfig<TOwner, TMemberType> memberConfig, CultureInfo culture)
where TMemberType : IFormattable
{
memberConfig.PrintingConfig.CulturesForTypes[typeof(TMemberType)] = culture;

return memberConfig.PrintingConfig;
}
}
9 changes: 9 additions & 0 deletions ObjectPrinting/ObjectExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace ObjectPrinting;

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

public class ObjectPrinter
{
public class ObjectPrinter
public static PrintingConfig<T> For<T>()
{
public static PrintingConfig<T> For<T>()
{
return new PrintingConfig<T>();
}
return new PrintingConfig<T>();
}
}
3 changes: 2 additions & 1 deletion ObjectPrinting/ObjectPrinting.csproj
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<LangVersion>8</LangVersion>
<LangVersion>latest</LangVersion>
<TargetFramework>netcoreapp3.1</TargetFramework>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
</PropertyGroup>

<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" />
</ItemGroup>
Expand Down
81 changes: 46 additions & 35 deletions ObjectPrinting/PrintingConfig.cs
Original file line number Diff line number Diff line change
@@ -1,41 +1,52 @@
using System;
using System.Linq;
using System.Text;
using System.Collections.Generic;
using System.Globalization;
using System.Linq.Expressions;
using System.Reflection;

namespace ObjectPrinting
namespace ObjectPrinting;

public class PrintingConfig<TOwner>
{
public class PrintingConfig<TOwner>
internal readonly HashSet<Type> ExcludedTypes = new();
internal readonly HashSet<MemberInfo> ExcludedMembers = new();

internal Dictionary<Type, Delegate> CustomTypeSerializers { get; } = new();
internal Dictionary<MemberInfo, Delegate> CustomMemberSerializers { get; } = new();
internal Dictionary<Type, CultureInfo> CulturesForTypes { get; } = new();
internal Dictionary<MemberInfo, int> TrimmedMembers { get; } = new();

public MemberPrintingConfig<TOwner, TMemberType> SetPrintingFor<TMemberType>()
{
return new MemberPrintingConfig<TOwner, TMemberType>(this);
}

public MemberPrintingConfig<TOwner, TMemberType> SetPrintingFor<TMemberType>(
Expression<Func<TOwner, TMemberType>> memberSelector)
{
var expression = (MemberExpression)memberSelector.Body;
return new MemberPrintingConfig<TOwner, TMemberType>(this, expression.Member);
}

public PrintingConfig<TOwner> ExcludeMember<TMemberType>(Expression<Func<TOwner, TMemberType>> memberSelector)
{
public string PrintToString(TOwner obj)
{
return PrintToString(obj, 0);
}

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 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));
}
return sb.ToString();
}
var expression = (MemberExpression)memberSelector.Body;
ExcludedMembers.Add(expression.Member);

return this;
}

public PrintingConfig<TOwner> ExcludeMemberType<TMemberType>()
{
ExcludedTypes.Add(typeof(TMemberType));

return this;
}

public string PrintToString(TOwner obj)
{
var serializer = new Serializer<TOwner>(this);

return serializer.SerializeObject(obj);
}
}
160 changes: 160 additions & 0 deletions ObjectPrinting/Serializer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Text;

namespace ObjectPrinting;

public class Serializer<TOwner>
{
private readonly HashSet<Type> excludedTypes;
private readonly HashSet<MemberInfo> excludedMembers;
private readonly Dictionary<Type, Delegate> customTypeSerializers;
private readonly Dictionary<MemberInfo, Delegate> customMemberSerializers;
private readonly Dictionary<Type, CultureInfo> culturesForTypes;
private readonly Dictionary<MemberInfo, int> trimmedMembers;

private HashSet<object> printedObjects;

public Serializer(PrintingConfig<TOwner> config)
{
excludedTypes = config.ExcludedTypes;
excludedMembers = config.ExcludedMembers;
customTypeSerializers = config.CustomTypeSerializers;
customMemberSerializers = config.CustomMemberSerializers;
culturesForTypes = config.CulturesForTypes;
trimmedMembers = config.TrimmedMembers;
LevShisterov marked this conversation as resolved.
Show resolved Hide resolved
}

public string SerializeObject(TOwner obj)
{
printedObjects = new HashSet<object>();

return PrintToString(obj, 0);
}

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

if (!obj.GetType().IsValueType && printedObjects.Contains(obj))
return "Cycle reference detected" + Environment.NewLine;

printedObjects.Add(obj);
LevShisterov marked this conversation as resolved.
Show resolved Hide resolved

var indentation = GetIndentation(nestingLevel);

if (culturesForTypes.TryGetValue(obj.GetType(), out var culture))
return ((IFormattable)obj).ToString(null, culture) + Environment.NewLine;

if (IsTypeFinal(obj.GetType()))
return obj + Environment.NewLine;

var sb = new StringBuilder();
var type = obj.GetType();
sb.AppendLine(type.Name);

if (obj is IEnumerable enumerable)
sb.Append(GetPrintedCollection(enumerable, nestingLevel));
else
foreach (var memberInfo in type.GetMembers().Where(IsPropertyOrField))
{
if (excludedTypes.Contains(GetMemberType(memberInfo)) || excludedMembers.Contains(memberInfo))
continue;

var printingResult = GetPrintingResult(obj, memberInfo, nestingLevel);

sb.Append(indentation + memberInfo.Name + " = " + printingResult);
}

return sb.ToString();
}

private string GetPrintedCollection(IEnumerable obj, int nestingLevel)
{
if (obj is IDictionary dict)
return GetPrintedDictionary(dict, nestingLevel);
else
return GetPrintedSequence(obj, nestingLevel);
}

private string GetPrintedSequence(IEnumerable obj, int nestingLevel)
{
var sb = new StringBuilder();
var indentation = GetIndentation(nestingLevel);
var index = 0;
foreach (var element in obj)
sb.Append($"{indentation}{index++}: {PrintToString(element, nestingLevel + 1)}");

return sb.ToString();
}

private string GetPrintedDictionary(IDictionary dict, int nestingLevel)
{
var sb = new StringBuilder();
var indentation = GetIndentation(nestingLevel);
foreach (DictionaryEntry pair in dict)
sb.Append($"{indentation}{pair.Key}: {PrintToString(pair.Value, nestingLevel + 1)}");

return sb.ToString();
}

private string GetPrintingResult(Object obj, MemberInfo memberInfo, int nestingLevel)
{
string printingResult;

if (customTypeSerializers.TryGetValue(GetMemberType(memberInfo), out var typeSerializer))
printingResult = typeSerializer.DynamicInvoke(GetMemberValue(memberInfo, obj)) + Environment.NewLine;
else if (customMemberSerializers.TryGetValue(memberInfo, out var propertySerializer))
printingResult = propertySerializer.DynamicInvoke(GetMemberValue(memberInfo, obj)) + Environment.NewLine;
else
printingResult = PrintToString(GetMemberValue(memberInfo, obj),
nestingLevel + 1);

return TrimResultIfNeeded(memberInfo, printingResult);
}

private string TrimResultIfNeeded(MemberInfo memberInfo, string printingResult)
{
if (trimmedMembers.TryGetValue(memberInfo, out var length))
printingResult = printingResult[..length] + Environment.NewLine;

return printingResult;
}

private static object GetMemberValue(MemberInfo member, Object obj)
{
if (!IsPropertyOrField(member))
throw new ArgumentException("Provided member must be Field or Property");

return member.MemberType == MemberTypes.Field
? ((FieldInfo)member).GetValue(obj)
: ((PropertyInfo)member).GetValue(obj);
}

private static Type GetMemberType(MemberInfo member)
{
if (!IsPropertyOrField(member))
throw new ArgumentException("Provided member must be Field or Property");

return member.MemberType == MemberTypes.Field
? ((FieldInfo)member).FieldType
: ((PropertyInfo)member).PropertyType;
}

private static bool IsPropertyOrField(MemberInfo member)
{
return member.MemberType is MemberTypes.Field or MemberTypes.Property;
}

private static bool IsTypeFinal(Type type)
{
return type.IsValueType || type == typeof(string);
}

private static string GetIndentation(int nestingLevel) => new string('\t', nestingLevel + 1);
}
3 changes: 3 additions & 0 deletions ObjectPrintingTests/GlobalUsings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
global using NUnit.Framework;
global using FluentAssertions;
global using ObjectPrinting;
Loading