45

Oftentimes, I need to serialize an object, either for logging or debugging. This is a one-way serialization -- I don't need to get it back out later, I just need to turn an object into a string to write it somewhere.

Yes, yes -- this is why you should always override the ToString method. I know this. But I'm often dealing with objects I didn't write and can't change. Additionally, I don't want to have to write and update a ToString method for every class I write.

XML serialization offers a seemingly perfect solution -- just flatten that object out into XML. But there are so many limitations, specifically that you can't serialize IDictionary, and you have to have a parameterless constructor. I can get around these in my classes, but -- again -- I'm often working with other people's classes.

So, what's the solution to getting an comprehensive string representation of an object? Is there something simple that I'm missing?

Alex Rashkov
  • 9,833
  • 3
  • 32
  • 58
Deane
  • 8,269
  • 12
  • 58
  • 108
  • 3
    Maybe some reflection to iterate and render the members into a string? However, that's slow and therefore only sensible for error scenarios where performance does not matter (anymore)... – Matthias Meid Dec 19 '12 at 15:25

2 Answers2

38

How about an extension method with your own logic (and maybe some Reflection)?

public static class SerializerExtension
{
    public static String OneWaySerialize(this Object obj)
    {
        if (Object.ReferenceEquals(obj, null))
        {
            return "NULL";
        }
        if (obj.GetType().IsPrimitive || obj.GetType() == typeof(String))
        {
            if (obj is String)
                return String.Format("\"{0}\"", obj);
            if (obj is Char)
                return String.Format("'{0}'", obj);
            return obj.ToString();
        }

        StringBuilder builder = new StringBuilder();
        Type objType = obj.GetType();
        if (IsEnumerableType(objType))
        {
            builder.Append("[");

            IEnumerator enumerator = ((IEnumerable)obj).GetEnumerator();
            Boolean moreElements = enumerator.MoveNext();
            while (moreElements)
            {
                builder.Append(enumerator.Current.OneWaySerialize());
                moreElements = enumerator.MoveNext();
                if (moreElements)
                {
                    builder.Append(",");
                }
            }

            builder.Append("]");
        }
        else
        {
            builder.AppendFormat("{0} {{ ", IsAnonymousType(objType) ? "new" : objType.Name);

            PropertyInfo[] properties = objType.GetProperties();
            for (Int32 p = properties.Length; p > 0; p--)
            {
                PropertyInfo prop = properties[p-1];
                String propName = prop.Name;
                Object propValue = prop.GetValue(obj, null);
                builder.AppendFormat("{0} = {1}", propName, propValue.OneWaySerialize());
                if (p > 1)
                {
                    builder.Append(", ");
                }
            }

            builder.Append(" }");
        }

        return builder.ToString();
    }

    // http://stackoverflow.com/a/2483054/298053
    private static Boolean IsAnonymousType(Type type)
    {
        if (type == null)
        {
            return false;
        }
        return Attribute.IsDefined(type, typeof(System.Runtime.CompilerServices.CompilerGeneratedAttribute), false)
            && type.IsGenericType && type.Name.Contains("AnonymousType")
            && (type.Name.StartsWith("<>") || type.Name.StartsWith("VB$"))
            && (type.Attributes & TypeAttributes.NotPublic) == TypeAttributes.NotPublic;
    }

    private static Boolean IsEnumerableType(Type type)
    {
        if (type == null)
        {
            return false;
        }
        foreach (Type intType in type.GetInterfaces())
        {
            if (intType.GetInterface("IEnumerable") != null || (intType.IsGenericType && intType.GetGenericTypeDefinition() == typeof(IEnumerable<>)))
            {
                return true;
            }
        }
        return false;
    }
}

Call it like so:

someDefinedObject.OneWaySerialize();

Revisisons

  1. Initial version
  2. Updated 12.26.2012
    • Added check for IEnumerable (thanks aboveyou00)
    • Added check for anonymous type (and just label it "new" when output)
Brad Christie
  • 100,477
  • 16
  • 156
  • 200
  • +1: Extension method and reflection would be my favorites here, making sure to serialize only publicly available properties. Might be a little boggy for large classes though. – Joel Etherton Dec 19 '12 at 15:28
  • 1
    Obviously not for the performance sensitive, but it seems more like a debugging tool than a production one so reflection should be _satisfactory_. – Brad Christie Dec 19 '12 at 15:31
  • Absolutely, just putting it out there in case OP decided to put it into a logging mechanism for large objects in a heavy use environment. Given OP doesn't want to write one for "every object" I'd guess he's planning to use this "a lot". – Joel Etherton Dec 19 '12 at 15:33
  • 1
    How would you handle recursion? If a property is a List, for instance, how do I list all the values of that? I know I can check for IEnumerable, but I'm just wondering how far you take. How far do you recurse down the rabbithole? – Deane Dec 19 '12 at 15:49
  • @Deane: updated, take that for a spin. tried it on some basic objects, but I'm sure it's not 100% bullet-proof (after all, i wrote it ;p) – Brad Christie Dec 19 '12 at 17:14
  • I'm getting a Stack Overflow exception, which tells me it's recursing uncontrollably somewhere. I'm trying to debug... – Deane Dec 19 '12 at 17:26
  • Tried to do it in LinqPad and it crashed it. I think calling OneWaySerialize all the way down the stack is biting off more than the runtime wants to chew... :-) – Deane Dec 19 '12 at 17:27
  • @Deane: let me know. All i tested it on was primitive types, a basic object with primitive types, an object with primitive and enumerable of primitive and a more complex object with an enumerable of other objects (which have primitive types). All worked, but didn't go deep in to testing. – Brad Christie Dec 19 '12 at 17:29
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/21399/discussion-between-deane-and-brad-christie) – Deane Dec 19 '12 at 17:39
  • 1
    Just so you know: IsEnumerableType only checks for generic enumerations (IEnumerable<>). It doesn't check for IEnumerable. Not a big deal, but for the sake of completeness... – leviathanbadger Dec 25 '12 at 18:58
  • @aboveyou00: Indeed, you are correct. I didn't get _too_ exhaustive on the list of possibilities, but the basic premise is there. I'll add a short-circuit in there though. ;-) – Brad Christie Dec 26 '12 at 20:42
14

If you're comfortable serializing to JSON, Json.NET is a great solution to this problem.

David Peden
  • 17,596
  • 6
  • 52
  • 72