Skip to content
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/ObjectExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using System;

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);
}
}
13 changes: 13 additions & 0 deletions ObjectPrinting/ObjectPrinterUsingProperties.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
namespace ObjectPrinting;

public class ObjectPrinterUsingProperties<TOwner, T>
{
private PrintingConfig<TOwner> printingConfig;

public PrintingConfig<TOwner> PrintingConfig => printingConfig;

public ObjectPrinterUsingProperties(PrintingConfig<TOwner> printingConfig)
{
this.printingConfig = printingConfig;
}
}
31 changes: 31 additions & 0 deletions ObjectPrinting/ObjectPrinterUsingPropertiesExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using System;
using System.Globalization;

namespace ObjectPrinting;

public static class ObjectPrinterUsingPropertiesExtensions
{
public static PrintingConfig<TOwner> SetCulture<TOwner>(this ObjectPrinterUsingProperties<TOwner, int> properties, CultureInfo cultureInfo)
{
properties.PrintingConfig.SerializationsByType[typeof(int)] = obj => ((int)obj).ToString(cultureInfo);
return properties.PrintingConfig;
}

public static PrintingConfig<TOwner> SetCulture<TOwner>(this ObjectPrinterUsingProperties<TOwner, double> properties, CultureInfo cultureInfo)
{
properties.PrintingConfig.SerializationsByType[typeof(double)] = obj => ((double)obj).ToString(cultureInfo);
return properties.PrintingConfig;
}

public static PrintingConfig<TOwner> SetCulture<TOwner>(this ObjectPrinterUsingProperties<TOwner, decimal> properties, CultureInfo cultureInfo)
{
properties.PrintingConfig.SerializationsByType[typeof(decimal)] = obj => ((decimal)obj).ToString(cultureInfo);
return properties.PrintingConfig;
}

public static PrintingConfig<TOwner> Trim<TOwner>(this ObjectPrinterUsingProperties<TOwner, string> properties, int length)
{
properties.PrintingConfig.SerializationsByType[typeof(string)] = obj => ((string)obj).Substring(0, Math.Min(length, ((string)obj).Length));
return properties.PrintingConfig;
}
}
25 changes: 25 additions & 0 deletions ObjectPrinting/ObjectPrinterUsingProperty.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using System;

namespace ObjectPrinting;

public class ObjectPrinterUsingProperty<TOwner, T>
{
private PrintingConfig<TOwner> printingConfig;
private string propertyFullName;

public PrintingConfig<TOwner> PrintingConfig => printingConfig;

public string PropertyFullName => propertyFullName;

public ObjectPrinterUsingProperty(PrintingConfig<TOwner> printingConfig, string propertyFullName)
{
this.printingConfig = printingConfig;
this.propertyFullName = propertyFullName;
}

public PrintingConfig<TOwner> Serialize(Func<object, string> func)
{
printingConfig.SerializationsByPropertyFullName[propertyFullName] = func;
return printingConfig;
}
}
12 changes: 12 additions & 0 deletions ObjectPrinting/ObjectPrinterUsingPropertyExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using System;

namespace ObjectPrinting.Tests;

public static class ObjectPrinterUsingPropertyExtensions
{
public static PrintingConfig<TOwner> Trim<TOwner>(this ObjectPrinterUsingProperty<TOwner, string> property, int length)
{
property.PrintingConfig.SerializationsByPropertyFullName[property.PropertyFullName] = obj => ((string)obj).Substring(0, Math.Min(length, ((string)obj).Length));
return property.PrintingConfig;
}
}
6 changes: 6 additions & 0 deletions ObjectPrinting/ObjectPrinting.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,14 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="ApprovalTests" Version="7.0.0" />
<PackageReference Include="FluentAssertions" Version="8.8.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" />
</ItemGroup>

<ItemGroup>
<Folder Include="Tests\ApprovalTests\" />
</ItemGroup>
</Project>
117 changes: 114 additions & 3 deletions ObjectPrinting/PrintingConfig.cs
Original file line number Diff line number Diff line change
@@ -1,19 +1,28 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;

namespace ObjectPrinting
{
public class PrintingConfig<TOwner>
{
public readonly Dictionary<Type, Func<object, string>> SerializationsByType = [];
public readonly Dictionary<string, Func<object, string>> SerializationsByPropertyFullName = [];
private readonly HashSet<string> excludedProperties = [];
private readonly HashSet<Type> excludedTypes = [];

private readonly Stack<string> currentPropertyFullName = new();

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

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

Expand All @@ -22,20 +31,122 @@ private string PrintToString(object obj, int nestingLevel)
typeof(int), typeof(double), typeof(float), typeof(string),
typeof(DateTime), typeof(TimeSpan)
};

var propertyFullName = string.Join(".", currentPropertyFullName.Reverse());
if (SerializationsByPropertyFullName.TryGetValue(propertyFullName, out var value))
return value(obj) + Environment.NewLine;

if (SerializationsByType.ContainsKey(obj.GetType()))
return SerializationsByType[obj.GetType()](obj) + Environment.NewLine;

if (finalTypes.Contains(obj.GetType()))
return obj + Environment.NewLine;

if (obj is System.Collections.IEnumerable and not string)
{
return ProcessIEnumerable(obj, nestingLevel);
}

var identation = new string('\t', nestingLevel + 1);
var indentation = 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 + " = " +
currentPropertyFullName.Push(propertyInfo.Name);
var newPropertyFullName = string.Join(".", currentPropertyFullName.Reverse());

if (excludedProperties.Contains(newPropertyFullName))
{
currentPropertyFullName.Pop();
continue;
}

if (excludedTypes.Contains(propertyInfo.PropertyType))
continue;

sb.Append(indentation + propertyInfo.Name + " = " +
PrintToString(propertyInfo.GetValue(obj),
nestingLevel + 1));

currentPropertyFullName.Pop();
}
return sb.ToString();
}

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

var enumerable = (System.Collections.IEnumerable)obj;
var index = 0;

foreach (var item in enumerable)
{
currentPropertyFullName.Push($"[{index}]");
sb.Append(indentation + $"[{index}] = " +
PrintToString(item, nestingLevel + 1));
currentPropertyFullName.Pop();
index++;
}

return sb.ToString();
}


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

public PrintingConfig<TOwner> Exclude(Expression<Func<TOwner, object>> func)
{
var fullPropertyName = GetPropertyFullName(func.Body);
excludedProperties.Add(fullPropertyName);
return this;
}

public PrintingConfig<TOwner> Serialize<T>(Func<T, string> func)
{
SerializationsByType[typeof(T)] = obj => func((T)obj);
return this;
}

public ObjectPrinterUsingProperties<TOwner, T> Using<T>()
{
return new ObjectPrinterUsingProperties<TOwner, T>(this);
}

public ObjectPrinterUsingProperty<TOwner, T> Using<T>(Expression<Func<TOwner, T>> func)
{
return new ObjectPrinterUsingProperty<TOwner, T>(this, GetPropertyFullName(func.Body));
}

private string GetPropertyFullName(Expression expression)
{
if (expression is MemberExpression memberExpression)
{
var parent = GetPropertyFullName(memberExpression.Expression!);
return string.IsNullOrEmpty(parent) ? memberExpression.Member.Name : parent + "." + memberExpression.Member.Name;
}

if (expression is UnaryExpression unaryExpression)
{
if (unaryExpression.NodeType == ExpressionType.Convert)
{
return GetPropertyFullName(unaryExpression.Operand);
}
throw new InvalidOperationException("Unsupported expression type");
}

if (expression is ParameterExpression)
{
return string.Empty;
}
Comment on lines +136 to +148
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

не совсем понял зачем вот это вот всё

У клиента есть объект, и он хочет по особенному распарсить какое-то поле или свойство. Если он укажет там что надо распарсить obj => !obj.flag, то он накосячил. Если надо флаг инвертировать, то пусть тогда в саму функцию сериализации указывает flag => (!flag).ToString()

Copy link
Copy Markdown
Author

@Dobvik Dobvik Nov 22, 2025

Choose a reason for hiding this comment

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

Поправил. Теперь не можем вызвать obj => !obj.flag, иначе получим Exception.

throw new InvalidOperationException("Unsupported expression type");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
Family
GrandParents = Person[]
[0] = Person
Id = Guid
Name = Alex
Height = 0
Age = 19
[1] = Person
Id = Guid
Name = Kate
Height = 0
Age = 21
Parents = List`1
[0] = Person
Id = Guid
Name = Alex
Height = 0
Age = 19
[1] = Person
Id = Guid
Name = Kate
Height = 0
Age = 21
Children = Dictionary`2
[0] = KeyValuePair`2
Key = 1
Value = Person
Id = Guid
Name = Alex
Height = 0
Age = 19
[1] = KeyValuePair`2
Key = 2
Value = Person
Id = Guid
Name = Kate
Height = 0
Age = 21
Father = Person
Id = Guid
Name = Alex
Height = 0
Mother = Person
Id = Guid
Name = Kate
Height = 0
Age = 21
Age = 55335,57
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
Family
GrandParents = Person[]
[0] = Person
Id = Guid
Name = Alex
Height = 0
[1] = Person
Id = Guid
Name = Kate
Height = 0
Parents = List`1
[0] = Person
Id = Guid
Name = Alex
Height = 0
[1] = Person
Id = Guid
Name = Kate
Height = 0
Children = Dictionary`2
[0] = KeyValuePair`2
Value = Person
Id = Guid
Name = Alex
Height = 0
[1] = KeyValuePair`2
Value = Person
Id = Guid
Name = Kate
Height = 0
Father = Person
Id = Guid
Name = Alex
Height = 0
Mother = Person
Id = Guid
Name = Kate
Height = 0
Age = 55335,57
Loading