13

I'm looking for a serializer which could take an instance, and serialize it to a string which would contain c# code, representing the contents of the graph. The class would function similar to SerializeObject in JSON.NET.

I know only a very narrow set of structures will work, but the ones I'm interested in are quite simple and they would.

Bonus points if anyone knows of a Visual Studio Visualizer with similar functionality.

Edit: The output will be used in a different application at compile time. I don't need to deserialize the output(c# code) at runtime, it gets saved to a file for analysis.

var foo = new Foo() { Number = 1, Bar = new Bar() { Str = "Bar"}};
string sourceCode = Magic.SerializeObject(foo);

Output:

Foo obj = new Foo();
obj.Number = 1;
obj.RefType = null; // infer this
obj.Bar = new Bar();
obj.Bar.Str = "Bar";
jasper
  • 3,424
  • 1
  • 25
  • 46
  • http://stackoverflow.com/questions/6503762/c-sharp-serialize-object-to-element-with-attributes-and-children did you try XmlSerializer? – Gilad Apr 16 '13 at 23:09
  • @Androidy I've used it frequently. It only serializes to XML, not to c#. – jasper Apr 16 '13 at 23:11
  • I don't know a ready to use solution, but maybe you want to have a look at the T4 Text Templates Visual Studio is using for code generation ... http://msdn.microsoft.com/en-us/library/dd820620.aspx ... a little bit of reflection and you are good to go ... – DarkSquirrel42 Apr 16 '13 at 23:12
  • 3
    Technically this is C# code `Foo obj = JsonConvert.Deserialize("[the json]");` but it obviously doesn't match your sample output. Would you mind sharing why you need C# code? – default.kramer Apr 16 '13 at 23:14
  • @DarkSquirrel42 T4 is not suitable for this. I need something to work at run time to read the values of the instance. – jasper Apr 16 '13 at 23:14
  • import Microsoft.VisualStudio.TextTemplating.dll and call T4 at runtime? – DarkSquirrel42 Apr 16 '13 at 23:18
  • @default.kramer Ha, I have something very similar to that already, Kudos. The reason I would like to keep it in c#, is because I would like the compiler checking of the types if they ever change. Right now, I do use JSON, but its just 'dumb' strings, and its becoming painful to maintain. – jasper Apr 16 '13 at 23:19
  • http://msdn.microsoft.com/en-us/library/ee844259.aspx shows an example of using T4 for runtime code generation – DarkSquirrel42 Apr 16 '13 at 23:21
  • @jasper But you're still going to compile that code at runtime, no? So I think it won't actually matter if you're going to get an error from the C# compiler invoked at runtime or from the JSON serializer. – svick Apr 17 '13 at 00:50
  • Yes, if you save the object as C# source code, you will need to actually later run that code to create an object, which means using Emit, which will give you compiler errors at RUNTIME. So it won't be much different than getting serialization errors at runtime. – AaronLS Apr 17 '13 at 01:16
  • I'm not 100% sure about what you're trying to do there, but perhaps System.Codedom is what you're looking for? – Matt Apr 17 '13 at 04:49
  • 1
    hold on guys, you're misunderstanding what I'm looking for. I dont want to emit code/create new types at runtime, or anything like that. I want to take a data structure, serialize it to a c# code representation. I then save this output to a file. I take this output at a later stage, and use it in a different project, which is compiled normally, to provide 'real' test data. I want it in c#, rather than JSON/XML/etc because the compiler can ensure the data structures match if/when we change the classes. Sorry for any confusion. – jasper Apr 17 '13 at 10:55
  • Why would you need to infer the setting of `RefType`? – Jodrell Apr 17 '13 at 11:02
  • I don't see how typed serialization with the right JSON library won't fit your needs. The intermediate format seems subordinate to the type checking. And exactly that last one is perfectly possible with modern JSON seralizers. Or am I missing something? – Grimace of Despair Apr 17 '13 at 11:10

2 Answers2

6

yes, and no...

The closest solution is called CodeDOM, it's what most Visual Studio designers and wizards use to generate code.


After taking a closer look at your comments I think you should read thoroughly the Binary Serialization section in the MSDN, It will fulfill all of your requirements.

What you're looking for is serialization, you don't want to recompile the second project every time right? you just want validation , and forward \ backward compatibility .

AK_
  • 7,981
  • 7
  • 46
  • 78
  • @jasper, mostly no, why serialize to source code, essentially decompiling. Its relatively hard to do in a "round-trippable" fashion, especially in the general case. You end up with a more verbose representation of your state. You could argue its more human readable but, XML and JSON are pretty good for that. +1 – Jodrell Apr 17 '13 at 14:12
  • @Jodrell i think you ment to put this on the question ;-) – AK_ Apr 17 '13 at 14:53
  • the +1 bit was directed accurately. – Jodrell Apr 17 '13 at 14:54
  • I meant the comment, but OK :-) – AK_ Apr 17 '13 at 14:55
  • actually, i do want to recompile every time, as we want compile time checking, but it looks you're spot on with CodeDOM, esp with the solution svick has provided. thanks for the help. – jasper Apr 18 '13 at 22:19
3

Writing something like this is actually relatively simple using CodeDom:

class CSharpSerializer
{
    private readonly Dictionary<string, int> m_locals =
        new Dictionary<string, int>();

    private readonly List<CodeStatement> m_statements =
        new List<CodeStatement>();

    private string GetVariableName(string suggestedName)
    {
        suggestedName = suggestedName.TrimEnd("0123456789".ToCharArray());

        int n;
        if (m_locals.TryGetValue(suggestedName, out n))
        {
            n++;
            m_locals[suggestedName] = n;
            return suggestedName + n;
        }

        m_locals[suggestedName] = 1;
        return suggestedName;
    }

    public string SerializeObject(object obj)
    {
        m_statements.Clear();

        // dynamic used to make the code simpler
        GetExpression((dynamic)obj);

        var compiler = new CSharpCodeProvider();
        var writer = new StringWriter();
        foreach (var statement in m_statements)
        {
            compiler.GenerateCodeFromStatement(
                statement, writer, new CodeGeneratorOptions());
        }
        return writer.ToString();
    }

    private static CodeExpression GetExpression(int i)
    {
        return new CodePrimitiveExpression(i);
    }

    private static CodeExpression GetExpression(string s)
    {
        return new CodePrimitiveExpression(s);
    }

    private static CodeExpression GetExpression(DateTime dateTime)
    {
        // TODO: handle culture and milliseconds
        return new CodeMethodInvokeExpression(
            new CodeTypeReferenceExpression(typeof(Convert)), "ToDateTime",
            new CodePrimitiveExpression(Convert.ToString(dateTime)));
    }

    // and so on for other primitive types
    // and types that require special handling (including arrays)

    private CodeExpression GetExpression(object obj)
    {
        if (obj == null)
            return new CodePrimitiveExpression(null);

        var type = obj.GetType();
        string typeName = type.Name;

        string variable = GetVariableName(
            typeName[0].ToString().ToLower() + typeName.Substring(1));

        m_statements.Add(
            new CodeVariableDeclarationStatement(
                typeName, variable, new CodeObjectCreateExpression(typeName)));

        foreach (var property in type.GetProperties(
            BindingFlags.Public | BindingFlags.Instance))
        {
            var expression = GetExpression((dynamic)property.GetValue(obj));
            m_statements.Add(
                new CodeAssignStatement(
                    new CodePropertyReferenceExpression(
                        new CodeVariableReferenceExpression(variable),
                        property.Name),
                    expression));
        }

        return new CodeVariableReferenceExpression(variable);
    }
}

There are many cases where this code will fail, but for well-behaved types (public default constructor, all public instance properties have setters, no important state hidden in fields or non-public properties, no cyclic references, no namespace collisions, etc.), it will work.

svick
  • 236,525
  • 50
  • 385
  • 514
  • brilliant, really appreciate the solution. +1 for you. ideally i'd award both of you, but AK_ provided the first codeDOM answer, so I'll award him unless anyone has objections? – jasper Apr 18 '13 at 22:29
  • @jasper It's completely up to you which answer you accept, so I don't think any objections would be relevant. – svick Apr 18 '13 at 22:43