Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

XML Serialization similar to what Json.Net can do

I have the following Console application:

using System;
using System.IO;
using System.Xml.Serialization;
using Newtonsoft.Json;

namespace OutputApp
{

    public class Foo
    {
        public object Value1 { get; set; }
        public string Value2 { get; set; }
    }

    public class Bar
    {
        public int Arg1 { get; set; }
        public double Arg2 { get; set; }
    }

    class Program
    {
        public static Foo CreateFooBar()
        {
            return new Foo
            {
                Value1 = new Bar
                {
                    Arg1 = 123,
                    Arg2 = 99.9
                },
                Value2 = "Test"
            };
        }

        public static string SerializeXml(object obj)
        {
            using (var stream = new MemoryStream())
            {
                using (var reader = new StreamReader(stream))
                {
                    var serializer = new XmlSerializer(obj.GetType());
                    serializer.Serialize(stream, obj);
                    stream.Position = 0;
                    return reader.ReadToEnd();
                }
            }
        }

        static void Main(string[] args)
        {
            var fooBar = CreateFooBar();

            // Using Newtonsoft.Json

            var json = JsonConvert.SerializeObject(fooBar, Formatting.Indented);
            var xnode = JsonConvert.DeserializeXNode(json, "RootElement");
            var xml = xnode.ToString();

            // Using XmlSerializer, throws InvalidOperationException

            var badXml = SerializeXml(fooBar);

            Console.ReadLine();
        }
    }
}

I have two classes. Class Foo and class Bar. Class Foo has a property of type object. This is a requirement, because it is a contract which can hold a variety of objects and therefore I cannot set the property to a concrete type or a generic.

Now I compose a dummy fooBar object using the CreateFooBar() method. After that I first serialize it into JSON, which works wonderfully with Json.Net. Then I use Json.Net's XML converter method to convert the json string into an XNode object. It works great as well.

The output of both is the following:

{
  "Value1": {
    "Arg1": 123,
    "Arg2": 99.9
  },
  "Value2": "Test"
}

<RootElement>
  <Value1>
    <Arg1>123</Arg1>
    <Arg2>99.9</Arg2>
  </Value1>
  <Value2>Test</Value2>
</RootElement>

Now while this works, it is certainly not very nice, because I have to serialize into json only to serialize it into xml afterwards. I would like to serialize directly into xml.

When I use the XmlSerializer to do this I get the infamous InvalidOperationExceptoin, because I did not decorate my classes with the XmlInclude attribute or did one of the other workarounds.

InvalidOperationException

The type OutputApp.Bar was not expected. Use the XmlInclude or SoapInclude attribute to specify types that are not known statically.

None of the workarounds for the XmlSerializer is a good solution IMHO and I don't see the need for it as it is perfectly feasible to serialize an object into XML without crappy attributes.

Does anyone know a good Xml serializer in .NET which can do this or is there a plan to add this feature to Json.Net?

Any ideas?

Update1

I am not opposed to use attributes, but it needs to make sense. What I don't like about the XmlInclude attribute is that it forces me into circular dependencies. Say I have assembly A which defines a base class, and assembly B which implements derived classes. Now the way the XmlInclude attribute works is that I'd have to decorate the base class in Assembly A with the type name of the child class from assembly B. This would mean I have a circular dependency and is a no go!

Update2

I shall clarify that I am not looking for a solution to re-factor my console application to make it work with the XmlSerializer, I am looking for a way to XML serialize what I have there.

There was a comment below which mentions that using object as a data type is poor design. Whether this is true or not, this is a whole other discussion. The point is that there is no reason why it shouldn't be able to serialize into XML and I am curious to find such a solution.

Personally I find creating a "marker" interface a dirty design. It abusing an interface to workaround the incapabilities of one single .NET class (XmlSerializer). If I would ever swap the serialization library for something else, then the whole marker interface would be redundant clutter. I don't want to couple my classes to one serializer.

I am looking for an elegant solution (if there is one)?

like image 686
dustinmoris Avatar asked May 06 '16 11:05

dustinmoris


1 Answers

You don't need to pollute your models with XmlInclude attributes. You could explicitly indicate all known classes to the XmlSerializer's constructor:

var serializer = new XmlSerializer(obj.GetType(), new[] { typeof(Bar) });

Also using object as base class seems like a crappy approach. At least define a marker interface:

public interface IMarker
{
}

that your Bar's will implement:

public class Bar : IMarker
{
    public int Arg1 { get; set; }
    public double Arg2 { get; set; }
}

and then then specialize the Value1 property of your Foo class to this marker instead of making it like the most universal type in the universe (it can't be):

public class Foo
{
    public IMarker Value1 { get; set; }
    public string Value2 { get; set; }
}

Coz now it's pretty trivial to get all loaded types at runtime in all referenced assemblies that are implementing the marker interface and passing them to the XmlSerializer constructor:

var type = typeof(IMarker);
var types = AppDomain.CurrentDomain.GetAssemblies()
    .SelectMany(s => s.GetTypes())
    .Where(p != type)
    .Where(p => type.IsAssignableFrom(p))
    .ToArray();

var serializer = new XmlSerializer(obj.GetType(), types);

Now you've got a pretty capable XmlSerializer that will know how to properly serialize all types implementing your marker interface. You've achieved almost the same functionality as JSON.NET. And don't forget that this XmlSerializaer instantiation should reside in your Composition Root project which knows about all loaded types.

And once again using object is a poor design decision.

like image 100
Darin Dimitrov Avatar answered Oct 14 '22 23:10

Darin Dimitrov