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
120 changes: 120 additions & 0 deletions ObjectPrinting/CollectionSerializer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ObjectPrinting
{
internal static class CollectionSerializer
{
public static bool IsCollection(Type type)
{
if (type.IsArray)
return true;

return typeof(IEnumerable).IsAssignableFrom(type) &&
type != typeof(string);
}

public static bool IsDictionary(Type type)
{
if (typeof(IDictionary).IsAssignableFrom(type))
return true;

return type.IsGenericType &&
(type.GetGenericTypeDefinition() == typeof(Dictionary<,>) ||
type.GetGenericTypeDefinition() == typeof(IDictionary<,>));
}

public static string SerializeCollection(object collection, int nestingLevel, HashSet<object> visitedObjects, PrintingConfigData config)
{
var sb = new StringBuilder();
var identation = new string('\t', nestingLevel + 1);
var collectionType = collection.GetType();

sb.AppendLine("[");

var enumerable = (IEnumerable)collection;
var items = enumerable.Cast<object>().ToList();
var maxItems = config.CollectionMaxItems.GetValueOrDefault(collectionType, int.MaxValue);

foreach (var item in items.Take(maxItems))
{
string serializedItem;
if (config.CollectionSerializers.TryGetValue(collectionType, out var serializer))
{
serializedItem = serializer(item);
}
else
{
var objectSerializer = new ObjectSerializer(config);
serializedItem = objectSerializer.PrintToString(item, nestingLevel + 1, visitedObjects).TrimEnd();
}

sb.AppendLine(identation + serializedItem + ",");
}

if (items.Count > maxItems)
{
var remaining = items.Count - maxItems;
sb.AppendLine(identation + $"... and {remaining} more items");
}
else if (items.Count == 0)
{
sb.AppendLine(identation + "empty");
}

sb.AppendLine("]");
return sb.ToString();
}

public static string SerializeDictionary(object dictionary, int nestingLevel, HashSet<object> visitedObjects, PrintingConfigData config)
{
var sb = new StringBuilder();
var identation = new string('\t', nestingLevel + 1);
var dictType = dictionary.GetType();

sb.AppendLine("[");

try
{
var dict = (IDictionary)dictionary;
var count = 0;
var maxItems = config.CollectionMaxItems.GetValueOrDefault(dictType, int.MaxValue);
var objectSerializer = new ObjectSerializer(config);

foreach (DictionaryEntry entry in dict)
{
if (count >= maxItems)
{
var remaining = dict.Count - maxItems;
sb.AppendLine(identation + $"... and {remaining} more items");
break;
}

var serializedKey = objectSerializer.PrintToString(entry.Key, nestingLevel + 1, visitedObjects).Trim();
var serializedValue = objectSerializer.PrintToString(entry.Value, nestingLevel + 1, visitedObjects).Trim();

serializedKey = serializedKey.Replace(Environment.NewLine, " ").Trim();
serializedValue = serializedValue.Replace(Environment.NewLine, " ").Trim();

sb.AppendLine(identation + $"{serializedKey}: {serializedValue},");
count++;
}

if (count == 0)
{
sb.AppendLine(identation + "empty");
}
}
catch (Exception ex)
{
sb.AppendLine(identation + $"Error: {ex.Message}");
}

sb.AppendLine("]");
return sb.ToString();
}
}
}
41 changes: 41 additions & 0 deletions ObjectPrinting/CollectionsConfigurator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
using System;

namespace ObjectPrinting;

public class CollectionsConfigurator<TOwner, TCollection>
{
private readonly PrintingConfig<TOwner> _printingConfig;
private readonly PrintingConfigData _data;
private readonly Type _collectionType;

public CollectionsConfigurator(PrintingConfig<TOwner> printingConfig, PrintingConfigData data, Type collectionType)
{
_printingConfig = printingConfig;
_data = data;
_collectionType = collectionType;
}

public static implicit operator PrintingConfig<TOwner>(CollectionsConfigurator<TOwner, TCollection> configurator)
{
return configurator._printingConfig;
}

public CollectionsConfigurator<TOwner, TCollection> WithMaxItems(int maxItems)
{
_data.CollectionMaxItems[_collectionType] = maxItems;
return this;
}

public CollectionsConfigurator<TOwner, TCollection> WithItemSerialization(Func<object, string> itemSerializer)
{
_data.CollectionSerializers[_collectionType] = itemSerializer;
return this;
}

public string PrintToString(TOwner obj)
{
return _printingConfig.PrintToString(obj);
}


}
33 changes: 33 additions & 0 deletions ObjectPrinting/CollectionsConfiguratorExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using System;
using System.Collections.Generic;

namespace ObjectPrinting;

public static class CollectionsConfiguratorExtensions
{
public static CollectionsConfigurator<TOwner, TCollection> WithItemSerialization<TOwner, TCollection, TItem>(
this CollectionsConfigurator<TOwner, TCollection> configurator,
Func<TItem, string> itemSerializer)
{
return configurator.WithItemSerialization(obj =>
{
if (obj is TItem item)
return itemSerializer(item);
return obj?.ToString() ?? "null";
});
}

public static CollectionsConfigurator<TOwner, TCollection> Trim<TOwner, TCollection>(
this CollectionsConfigurator<TOwner, TCollection> configurator,
int start, int end)
{
return configurator.WithItemSerialization(str =>
{
var s = str as string;
if (s == null) return "null";

var length = Math.Min(s.Length - start, end - start);
return length > 0 ? s.Substring(start, length) : string.Empty;
});
}
}
1 change: 1 addition & 0 deletions ObjectPrinting/ObjectPrinter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@ public static PrintingConfig<T> For<T>()
{
return new PrintingConfig<T>();
}

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

namespace ObjectPrinting;

public static class ObjectPrinterExtensions
{
public static string PrintToString<T>(this T obj)
{
return ObjectPrinter.For<T>().PrintToString(obj);
}

public static string PrintToString<T>(this T obj, Action<PrintingConfig<T>> config)
{
var printer = ObjectPrinter.For<T>();
config(printer);
return printer.PrintToString(obj);
}
}
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="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" />
Expand Down
117 changes: 117 additions & 0 deletions ObjectPrinting/ObjectSerializer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text;

namespace ObjectPrinting
{
internal class ObjectSerializer
{
private readonly PrintingConfigData _config;
private static readonly HashSet<Type> FinalTypes = new()
{
typeof(int), typeof(double), typeof(float), typeof(string),
typeof(DateTime), typeof(TimeSpan), typeof(bool), typeof(decimal),
typeof(long), typeof(short), typeof(byte), typeof(char), typeof(Guid)
};

public ObjectSerializer(PrintingConfigData config)
{
_config = config;
}

public string Serialize(object obj)
{
return PrintToString(obj, 0, new HashSet<object>());
}

public string PrintToString(object obj, int nestingLevel, HashSet<object> visitedObjects)
{
if (obj == null)
return "null" + Environment.NewLine;

if (!visitedObjects.Add(obj))
return $"Cyclic reference to {obj.GetType().Name}" + Environment.NewLine;

try
{
var type = obj.GetType();

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

if (CollectionSerializer.IsDictionary(type))
return CollectionSerializer.SerializeDictionary(obj, nestingLevel, visitedObjects, _config);

if (CollectionSerializer.IsCollection(type))
return CollectionSerializer.SerializeCollection(obj, nestingLevel, visitedObjects, _config);

return SerializeObject(obj, nestingLevel, visitedObjects);
}
finally
{
visitedObjects.Remove(obj);
}
}

private string SerializeObject(object obj, int nestingLevel, HashSet<object> visitedObjects)
{
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())
{
if (_config.ExcludedTypes.Contains(propertyInfo.PropertyType) ||
_config.ExcludedProperties.Contains(propertyInfo.Name))
continue;

var propertyValue = propertyInfo.GetValue(obj);
var serializedValue = SerializeProperty(propertyInfo, propertyValue, nestingLevel, visitedObjects);

sb.Append(identation + propertyInfo.Name + " = " + serializedValue);
}

return sb.ToString();
}

private string SerializeProperty(PropertyInfo propertyInfo, object propertyValue, int nestingLevel, HashSet<object> visitedObjects)
{
var propertyName = propertyInfo.Name;
var propertyType = propertyInfo.PropertyType;

if (_config.PropertySerializers.TryGetValue(propertyName, out var propertySerializer))
return propertySerializer(propertyValue) + Environment.NewLine;

if (_config.PropertyCultures.TryGetValue(propertyName, out var culture) && propertyValue is IFormattable formattableProperty)
return formattableProperty.ToString(null, culture) + Environment.NewLine;

if (_config.TypeSerializers.TryGetValue(propertyType, out var typeSerializer))
return typeSerializer(propertyValue) + Environment.NewLine;

if (_config.TypeCultures.ContainsKey(propertyType) && propertyValue is IFormattable formattable)
return formattable.ToString(null, _config.TypeCultures[propertyType]) + Environment.NewLine;

return PrintToString(propertyValue, nestingLevel + 1, visitedObjects);
}

private string ApplySerialization(object value, Type type)
{
if (value == null)
return "null";

if (_config.TypeSerializers.TryGetValue(type, out var serializer))
return serializer(value);

if (_config.TypeCultures.ContainsKey(type) && value is IFormattable formattable)
return formattable.ToString(null, _config.TypeCultures[type]);

if (type == typeof(Guid))
return value.ToString();

return value.ToString() ?? "null";
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

проверка на null уже выше есть

}
}
}
Loading