Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
167068c
fix: move tests to separate project
tazik23 Nov 20, 2025
7f440b4
test: add acceptance test
tazik23 Nov 20, 2025
d0847d8
feat: add method to exclude types
tazik23 Nov 20, 2025
f73c295
feat: add method to exclude members
tazik23 Nov 20, 2025
a1fdee2
feat: add support for custom serializers for types
tazik23 Nov 20, 2025
2cf2c90
feat: add support for cultures
tazik23 Nov 20, 2025
a07751a
feat: add support for custom serializers for members
tazik23 Nov 20, 2025
a1659b5
feat: add support for trimmed length
tazik23 Nov 20, 2025
f3db969
feat: add printToString realization
tazik23 Nov 20, 2025
2712df7
feat: add extension methods for printing objects
tazik23 Nov 20, 2025
108c9db
test: add test for excluding method
tazik23 Nov 20, 2025
82f6693
feat: add recursionDepth limit
tazik23 Nov 20, 2025
abb4d81
fix: remove unused method
tazik23 Nov 20, 2025
1805737
feat: add test entites
tazik23 Nov 20, 2025
aa26a5f
test: add collection tests
tazik23 Nov 20, 2025
b9aa3eb
test: add printing object tests
tazik23 Nov 20, 2025
cada713
test: add culture tests
tazik23 Nov 20, 2025
e72933b
test: add serializers tests
tazik23 Nov 20, 2025
a39cb1d
test: add excluded tests
tazik23 Nov 20, 2025
2a72e20
test: add nested objects tests
tazik23 Nov 20, 2025
28bbf2e
test: add trim tests
tazik23 Nov 20, 2025
61a30cd
fix: remove unused using
tazik23 Nov 20, 2025
32108d4
fix: refactor PrintingConfig
tazik23 Nov 20, 2025
8ee6564
Merge branch 'homework/object-printer-realization'
tazik23 Nov 20, 2025
74b9ee1
fix: now use invariant culture for printing
tazik23 Nov 20, 2025
1cc1faf
fix: fix logic with class interaction
tazik23 Nov 23, 2025
a9ababe
feat: add printer settings class
tazik23 Nov 23, 2025
42c4d9a
fix: brought out the logic for printing an object in ObjectPrinter class
tazik23 Nov 23, 2025
784cf12
fix: rename PrintToString method
tazik23 Nov 23, 2025
8de41a7
fix: remove unnecessary brackets, simplified checking for circular re…
tazik23 Nov 23, 2025
2b3f553
fix: refactor ObjectPrinter class
tazik23 Nov 23, 2025
fd55ee0
fix: move test classes to separate folder
tazik23 Nov 23, 2025
6c58e6f
fix: add more understandable line output
tazik23 Nov 23, 2025
77dc127
fix: add more understandable line output for collection tests
tazik23 Nov 23, 2025
6dc2bfa
fix: add more understandable line output for culture tests
tazik23 Nov 23, 2025
caf6b1a
fix: add more understandable line output for serializers tests
tazik23 Nov 23, 2025
d986cf8
fix: add more understandable line output for excluding tests
tazik23 Nov 23, 2025
8e4111d
test: add test to check excluding domination over printing + using
tazik23 Nov 23, 2025
e891255
test: add files to verify lib tests
tazik23 Nov 23, 2025
88d7e98
fix: fix recursion depth check
tazik23 Nov 23, 2025
56f789e
fix: rename create method
tazik23 Nov 23, 2025
10f0388
merge conflict
tazik23 Nov 23, 2025
b8fe149
fix: fix culture in custom erializers
tazik23 Nov 24, 2025
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
224 changes: 220 additions & 4 deletions ObjectPrinting/ObjectPrinter.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,226 @@
namespace ObjectPrinting
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Text;
using ObjectPrinting.PrintingConfigs;

namespace ObjectPrinting;

public class ObjectPrinter
{
public class ObjectPrinter
private readonly PrinterSettings settings;

public ObjectPrinter(PrinterSettings settings)
{
public static PrintingConfig<T> For<T>()
this.settings = settings;
}

public static PrintingConfig<T> For<T>()
{
return new PrintingConfig<T>();
}

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

private string PrintToString(object? obj, int nestingLevel, HashSet<object> visited)
{
if (nestingLevel > settings.MaxRecursionDepth)
return string.Empty;

if (obj is null)
return "null" + NewLine;

var type = obj.GetType();

if (settings.ExcludedTypes.Contains(type))
return string.Empty;

if (IsCyclicReference(obj, type, visited))
return "(cyclic reference)" + NewLine;

return FormatByType(obj, nestingLevel, visited, type);
}

private string FormatByType(object obj, int nestingLevel, HashSet<object> visited, Type type)
{
if (TrySerializeType(type, obj, out var result))
return result + NewLine;

if (TryFormatFormattable(obj, type, out result))
return result + NewLine;

if (IsFinalType(type))
return Convert.ToString(obj, CultureInfo.InvariantCulture) + NewLine;

return obj switch
{
IDictionary dictionary => PrintDictionary(dictionary, nestingLevel, visited),
IEnumerable enumerable => PrintEnumerable(enumerable, nestingLevel, visited),
_ => PrintObject(obj, nestingLevel, visited)
};
}

private string PrintObject(object obj, int nestingLevel, HashSet<object> visited)
{
var type = obj.GetType();
var indent = Indent(nestingLevel + 1);
var sb = new StringBuilder();

sb.AppendLine(type.Name);

var members = GetFilteredMembers(type);

foreach (var member in members)
{
sb.Append(ProcessMember(obj, member, indent, nestingLevel, visited));
}

return sb.ToString();
}

private string ProcessMember(
object obj, MemberInfo member, string indent, int nestingLevel, HashSet<object> visited)
{
var value = GetMemberValue(obj, member);
var prefix = $"{indent}{member.Name} = ";

if (value is null)
return prefix + "null" + NewLine;

if (settings.ExcludedTypes.Contains(value.GetType()))
return string.Empty;

if (TrySerializeMember(member, value, out var result))
return prefix + result + NewLine;

if (TryTrimStringMember(member, value, out var trimmed))
return prefix + trimmed + NewLine;

return prefix + PrintToString(value, nestingLevel + 1, visited);
}

private string PrintEnumerable(IEnumerable enumerable, int nestingLevel, HashSet<object> visited)
{
var indent = Indent(nestingLevel + 1);
var sb = new StringBuilder();

sb.AppendLine("[");

foreach (var item in enumerable)
{
sb.Append(indent).Append(PrintToString(item, nestingLevel + 1, visited));
}

sb.Append(Indent(nestingLevel)).AppendLine("]");
return sb.ToString();
}

private string PrintDictionary(IDictionary dict, int nestingLevel, HashSet<object> visited)
{
var indent = Indent(nestingLevel + 1);
var sb = new StringBuilder();

sb.AppendLine("{");

foreach (DictionaryEntry entry in dict)
{
sb.Append(indent)
.Append('[')
.Append(PrintToString(entry.Key, nestingLevel + 1, visited).TrimEnd())
.Append("] = ")
.Append(PrintToString(entry.Value, nestingLevel + 1, visited));
}

sb.Append(Indent(nestingLevel)).AppendLine("}");
return sb.ToString();
}

private bool TrySerializeType(Type type, object obj, out string? result)
{
if (settings.TypeSerializers.TryGetValue(type, out var serializer))
{
result = serializer(obj);
return true;
}

result = null;
return false;
}

private bool TrySerializeMember(MemberInfo member, object value, out string? result)
{
if (settings.MemberSerializers.TryGetValue(member, out var serializer))
{
return new PrintingConfig<T>();
result = Convert.ToString(serializer(value), CultureInfo.InvariantCulture);
return true;
}

result = null;
return false;
}

private bool TryFormatFormattable(object obj, Type type, out string? result)
{
if (obj is IFormattable formattable && settings.TypeCultures.TryGetValue(type, out var culture))
{
result = Convert.ToString(formattable, culture);
return true;
}

result = string.Empty;
return false;
}

private bool TryTrimStringMember(MemberInfo member, object value, out string? result)
{
if (value is string stringValue && settings.TrimLengths.TryGetValue(member, out var trimLength))
{
result = stringValue.Length > trimLength
? stringValue.Substring(0, trimLength)
: stringValue;
return true;
}

result = null;
return false;
}

private static bool IsCyclicReference(object obj, Type type, HashSet<object> visited)
{
return !type.IsValueType && !visited.Add(obj);
}

private static bool IsFinalType(Type type)
{
return type.IsPrimitive
|| type == typeof(string)
|| type.IsEnum
|| typeof(IFormattable).IsAssignableFrom(type);
}

private IEnumerable<MemberInfo> GetFilteredMembers(Type type)
{
return type.GetProperties()
.Concat(type.GetFields().Cast<MemberInfo>())
.Where(m => !settings.ExcludedMembers.Contains(m));
}

private static object? GetMemberValue(object? obj, MemberInfo member)
{
return member switch
{
PropertyInfo p => p.GetValue(obj),
FieldInfo f => f.GetValue(obj),
_ => throw new ArgumentOutOfRangeException($"Unsupported member type: {member.MemberType}")
};
}

private static string Indent(int level) => new('\t', level);
private static string NewLine => Environment.NewLine;
}
6 changes: 0 additions & 6 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>
35 changes: 35 additions & 0 deletions ObjectPrinting/PrinterSettings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Reflection;

namespace ObjectPrinting;

public class PrinterSettings
{
public int MaxRecursionDepth { get; }
public IReadOnlySet<Type> ExcludedTypes { get; }
public IReadOnlySet<MemberInfo> ExcludedMembers { get; }
public IReadOnlyDictionary<Type, Func<object, string>> TypeSerializers { get; }
public IReadOnlyDictionary<MemberInfo, Func<object, string>> MemberSerializers { get; }
public IReadOnlyDictionary<Type, CultureInfo> TypeCultures { get; }
public IReadOnlyDictionary<MemberInfo, int> TrimLengths { get; }

public PrinterSettings(
int maxRecursionDepth,
IReadOnlySet<Type> excludedTypes,
IReadOnlySet<MemberInfo> excludedMembers,
IReadOnlyDictionary<Type, Func<object, string>> typeSerializers,
IReadOnlyDictionary<MemberInfo, Func<object, string>> memberSerializers,
IReadOnlyDictionary<Type, CultureInfo> typeCultures,
IReadOnlyDictionary<MemberInfo, int> trimLengths)
{
MaxRecursionDepth = maxRecursionDepth;
ExcludedTypes = excludedTypes;
ExcludedMembers = excludedMembers;
TypeSerializers = typeSerializers;
MemberSerializers = memberSerializers;
TypeCultures = typeCultures;
TrimLengths = trimLengths;
}
}
41 changes: 0 additions & 41 deletions ObjectPrinting/PrintingConfig.cs

This file was deleted.

17 changes: 17 additions & 0 deletions ObjectPrinting/PrintingConfigs/Extensions/ObjectExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using System;

namespace ObjectPrinting.PrintingConfigs.Extensions;

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

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

Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using System;
using System.Globalization;

namespace ObjectPrinting.PrintingConfigs.Extensions;

public static class TypePrintingConfigExtensions
{
public static PrintingConfig<TOwner> Using<TOwner, TProp>(
this TypePrintingConfig<TOwner, TProp> config, CultureInfo cultureInfo)
where TProp : IFormattable
{
return config.Config.SetCultureFor<TProp>(cultureInfo);
}
}
Loading