Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
13 commits
Select commit Hold shift + click to select a range
db042a5
Базовая настройка проекта, исключил предложенный тестовый файл
Kostornoj-Dmitrij Dec 11, 2024
01a6a67
Создание проекта для тестов
Kostornoj-Dmitrij Dec 11, 2024
d29703d
Тесты + вспомогательные классы для них
Kostornoj-Dmitrij Dec 11, 2024
a8e70c9
Расширения и основной класс для ObjectPrinter
Kostornoj-Dmitrij Dec 11, 2024
a6a6328
Основная логика сериализатора
Kostornoj-Dmitrij Dec 11, 2024
af4664a
Add verified files because forgot
Kostornoj-Dmitrij Dec 12, 2024
6e5b2f7
Удалил папку тестов и Solved из ObjectOrinting и ненужные зависимости.
Kostornoj-Dmitrij Dec 13, 2024
4136bfa
Удалил папку Solved из ObjectPrinting
Kostornoj-Dmitrij Dec 13, 2024
d604940
Добавил константу для разделителя, интерполяцию вместо конкатенации
Kostornoj-Dmitrij Dec 13, 2024
c0dcf83
Разделил подготовку к тестам и проверки. Добавил тесты на исключения
Kostornoj-Dmitrij Dec 13, 2024
b3fe665
Обновил verified файлы
Kostornoj-Dmitrij Dec 13, 2024
2972683
Добавил обработку исключений, сериализацию типов объектов коллекций
Kostornoj-Dmitrij Dec 13, 2024
5212f0e
Изменил конкатенацию на использование StringBuilder
Kostornoj-Dmitrij Dec 13, 2024
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
14 changes: 14 additions & 0 deletions ObjectPrinting/Extensions/ObjectPrinterExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using System;
using ObjectPrinting.Interfaces;

namespace ObjectPrinting.Extensions;

public static class ObjectPrinterExtensions
{
public static string? Serialize<TSerialize>(this IObjectPrinter printer, TSerialize obj) =>
printer.For<TSerialize>().PrintToString(obj);

public static string? Serialize<TSerialize>(this IObjectPrinter printer, TSerialize obj,
Func<ObjectPrinterSettings<TSerialize>, ObjectPrinterSettings<TSerialize>> config) =>
config(printer.For<TSerialize>()).PrintToString(obj);
}
6 changes: 6 additions & 0 deletions ObjectPrinting/Interfaces/IObjectPrinter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace ObjectPrinting.Interfaces;

public interface IObjectPrinter
{
ObjectPrinterSettings<T> For<T>();
}
13 changes: 7 additions & 6 deletions ObjectPrinting/ObjectPrinter.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
namespace ObjectPrinting
using ObjectPrinting.Interfaces;

namespace ObjectPrinting;

public class ObjectPrinter : IObjectPrinter
{
public class ObjectPrinter
public ObjectPrinterSettings<T> For<T>()
{
public static PrintingConfig<T> For<T>()
{
return new PrintingConfig<T>();
}
return new ObjectPrinterSettings<T>();
}
}
219 changes: 219 additions & 0 deletions ObjectPrinting/ObjectPrinterSettings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
using System;
using System.Text;
using System.Reflection;
using System.Collections;
using System.Linq.Expressions;
using System.Collections.Generic;

namespace ObjectPrinting;

public class ObjectPrinterSettings<TOwner>
{
private readonly HashSet<Type> finalTypes =
[
typeof(bool), typeof(byte), typeof(int), typeof(double), typeof(float), typeof(char), typeof(string),
typeof(DateTime), typeof(TimeSpan), typeof(Guid)
];

private readonly HashSet<Type> excludedTypes = [];
private readonly HashSet<MemberInfo> excludedProperties = [];
private readonly HashSet<object> processedObjects = new(ReferenceEqualityComparer.Instance);
private readonly Dictionary<Type, IFormatProvider> typeCulture = [];
private readonly Dictionary<Type, Func<object, string>> typeSerializers = [];
private readonly Dictionary<MemberInfo, Func<object, string>> propertySerializers = [];
private int? maxStringLength;
private const int MaxCollectionItems = 100;
private const char IndentChar = '\t';

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

public ObjectPrinterSettings<TOwner> Exclude<T>(Expression<Func<TOwner, T>> propertyExpression)
{
var propertyName = GetPropertyInfo(propertyExpression);
excludedProperties.Add(propertyName);
return this;
}

public ObjectPrinterSettings<TOwner> SetCustomSerialization<T>(Func<T, string> serializer)
{
typeSerializers[typeof(T)] = obj =>
{
try
{
return serializer((T)obj);
}
catch (Exception ex)
{
return $"[Error serializing type '{typeof(T).Name}']: {ex.Message}";
}
};
return this;
}

public ObjectPrinterSettings<TOwner> SetCustomSerialization<T>(Expression<Func<TOwner, T>> propertyExpression, Func<T, string> serializer)
{
var propertyName = GetPropertyInfo(propertyExpression);

This comment was marked as resolved.

propertySerializers[propertyName] = obj =>
{
try
{
return serializer((T)obj);
}
catch (Exception ex)
{
return $"[Error serializing property '{propertyName.Name}']: {ex.Message}";
}
};
return this;
}

public ObjectPrinterSettings<TOwner> SetCulture<T>(IFormatProvider culture, string? format = null) where T : IFormattable
{
typeCulture[typeof(T)] = culture;
if (!string.IsNullOrEmpty(format))
typeSerializers[typeof(T)] = obj => ((IFormattable)obj).ToString(format, culture);
return this;
}

public ObjectPrinterSettings<TOwner> TrimStringsToLength(int maxLength)
{
maxStringLength = maxLength;
return this;
}

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

private string? PrintToString(object? obj, int nestingLevel)
{
if (obj == null)
return AppendNewLine("null");

if (processedObjects.Contains(obj))
return AppendNewLine("[Circular Reference]");

if (TrySerializeFinalType(obj, out var result))
return result;

return TrySerializeCollection(obj, nestingLevel, out var collectionResult) ? collectionResult : SerializeComplexType(obj, nestingLevel);
}

private bool TrySerializeCollection(object obj, int nestingLevel, out string? collectionResult)
{
if (obj is IEnumerable enumerable)
{
var text = new StringBuilder();
var pad = new string(IndentChar, nestingLevel + 1);
var count = 0;

text.AppendLine($"({obj.GetType().Name}):");
foreach (var item in enumerable)
{
if (count++ >= MaxCollectionItems)
{
text.Append($"{pad}... (truncated)");
break;
}

var elementType = item?.GetType().Name ?? "unknown";

text.Append($"{pad}({elementType}):{PrintToString(item, nestingLevel + 1)}");
}

collectionResult = text.ToString();
return true;
}

collectionResult = null;
return false;
}

private bool TrySerializeFinalType(object obj, out string? result)
{
var type = obj.GetType();
if (finalTypes.Contains(type))
{
result = (obj switch
{
IFormattable formattable when typeCulture.ContainsKey(type) =>
formattable.ToString("G", typeCulture[type]),
string str => Trim(str),
_ => obj.ToString()
})!;

result = AppendNewLine(result);
return true;
}
result = null;
return false;
}

private string Trim(string str)
{
if (maxStringLength.HasValue && str.Length > maxStringLength)
return str[..maxStringLength.Value];
return str;
}

private string SerializeComplexType(object obj, int nestingLevel)
{
processedObjects.Add(obj);
try
{
var pad = new string(IndentChar, nestingLevel + 1);
var text = new StringBuilder();
var type = obj.GetType();
text.AppendLine(type.Name);

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

var value = property.GetValue(obj);
var serializedValue = SerializeProperty(property, value, nestingLevel + 1);
text.Append($"{pad}{property.Name} = {serializedValue}");
}

return text.ToString();
}
finally
{
processedObjects.Remove(obj);
}
}

private string? SerializeProperty(PropertyInfo property, object? value, int nestingLevel)
{

if (propertySerializers.TryGetValue(property, out var propertySerializer))
return AppendNewLine(propertySerializer(value!));

if (typeSerializers.TryGetValue(property.PropertyType, out var typeSerializer))
return AppendNewLine(typeSerializer(value!));

return PrintToString(value, nestingLevel);

}

private static MemberInfo GetPropertyInfo<T>(Expression<Func<TOwner, T>> propertyExpression)
{
if (propertyExpression.Body is not MemberExpression member)
throw new ArgumentException("Expression must be a property access.", nameof(propertyExpression));
return member.Member;
}

private static string AppendNewLine(string? text)
{
if (text != null && text.EndsWith(Environment.NewLine))
return text;

var result = new StringBuilder(text);
result.Append(Environment.NewLine);

return result.ToString();
}
}
8 changes: 1 addition & 7 deletions ObjectPrinting/ObjectPrinting.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,4 @@
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<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>
</Project>
</Project>
41 changes: 0 additions & 41 deletions ObjectPrinting/PrintingConfig.cs

This file was deleted.

10 changes: 0 additions & 10 deletions ObjectPrinting/Solved/ObjectExtensions.cs

This file was deleted.

10 changes: 0 additions & 10 deletions ObjectPrinting/Solved/ObjectPrinter.cs

This file was deleted.

Loading