45
$\begingroup$

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?

$\endgroup$
1
  • 3
    $\begingroup$ 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)... $\endgroup$ Commented Dec 19, 2012 at 15:25

2 Answers 2

38
$\begingroup$

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)
$\endgroup$
Sign up to request clarification or add additional context in comments.

11 Comments

+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.
Obviously not for the performance sensitive, but it seems more like a debugging tool than a production one so reflection should be satisfactory.
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".
How would you handle recursion? If a property is a List<string>, 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?
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...
|
14
$\begingroup$

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

$\endgroup$

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.