public class XmlStringSerializer
{
static Dictionary<string, Type> types = new Dictionary<string, Type>();
public string Serialize(object o)
{
StringBuilder sb = new StringBuilder();
Type t = o.GetType();
if (t.IsSerializable)
{
XmlSerializer serializer = new XmlSerializer(t);
using (TextWriter tw = new StringWriter(sb))
{
var namespaces = new XmlSerializerNamespaces();
namespaces.Add("", "");
serializer.Serialize(tw, o, namespaces);
tw.Close();
}
}
else
{
throw new InvalidOperationException(string.Format("Objects of type {0} are not serializable.", t.Name));
}
sb.Remove(0, 42);
sb.Insert(0, '.');
sb.Insert(0, t.Namespace);
sb.Insert(0, '<');
return sb.ToString();
}
public T DeSerializeObject<T>(string content)
{
var typeName = content.Substring(1, content.IndexOf(' ', 20) - 1);
Type type;
if (!types.ContainsKey(typeName))
{
type = AppDomain.CurrentDomain.GetAssemblies().SelectMany(a => a.GetTypes()).Single(t => t.FullName == typeName);
lock (types)
{
types[typeName] = type;
}
}
else
{
type = types[typeName];
}
object o;
var serializer = new XmlSerializer(type);
int posAfterTypeName = content.IndexOf(' ');
int posOfStartOfTypeName = content.LastIndexOf('.', posAfterTypeName) + 1;
content = @"<?xml version=""1.0"" encoding=""utf-16""?><" + content.Substring(posOfStartOfTypeName);
using (TextReader tr = new StringReader(content))
{
o = serializer.Deserialize(tr);
tr.Close();
}
return (T) o;
}
}
Refactorings
No refactoring yet !
Moonshield
January 22, 2009, January 22, 2009 03:45, permalink
Unfortunately, your code wasn't working for me. I think that my default xml encoding differs from yours so that your hard-coded string manipulation isn’t working properly. On the other side, I worked on a cleaner way to achieve what you want. It can serializes an object of type A, then deserializes it back to the same type or to an object of type B : A like you mentioned. The key of the implementation is to use a general root name (see XmlRoot in my code) for both objects. Enough talking, see the code below :)
using System;
using System.IO;
using System.Xml.Serialization;
namespace ConsoleApplication1
{
public class Program
{
static void Main(string[] args)
{
// Serialize a Person object and deserialize it back to a Person or an Employee
string SerializedObject = SerializedObject = CustomSerializer.Serialize(new Person() { Id = 1, FirstName = "Peter", LastName = "Pan" });
Person oPerson = CustomSerializer.DeSerialize<Person>(SerializedObject);
Employee oEmployee = CustomSerializer.DeSerialize<Employee>(SerializedObject);
// At this point the Employee.Title is empty, everything's normal. Then we will
// serialize the same Employee object and deserialize it back to a Person object
oEmployee.Title = "Hero";
SerializedObject = CustomSerializer.Serialize(oEmployee);
oPerson = CustomSerializer.DeSerialize<Person>(SerializedObject);
Console.ReadLine();
}
}
/// <summary>
/// Contains information about a person.
/// </summary>
/// <remarks>
/// The XmlRoot attribute allow to serialize a Person object
/// and to deserialize it back to a Person or an Employee
/// object without "manipulating" the xml string.
/// </remarks>
[Serializable, XmlRoot("Base")]
public class Person
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
/// <summary>
/// Contains information about an employee.
/// </summary>
/// <remarks>
/// The XmlRoot attribute allow to serialize a Person object
/// and to deserialize it back to a Person or an Employee
/// object without "manipulating" the xml string.
/// </remarks>
[Serializable, XmlRoot("Base")]
public class Employee : Person
{
public string Title { get; set; }
}
/// <summary>
///
/// </summary>
public class CustomSerializer
{
/// <summary>
/// Serialize an object to xml
/// </summary>
/// <param name="pSource">Object Type</param>
/// <returns>String</returns>
public static string Serialize(object pSource)
{
// Normally I'd use this 4 lines but since you need cleaner xml see below
//StringWriter oWriter = new StringWriter();
//XmlSerializer oSerializer = new XmlSerializer(pSource.GetType());
//oSerializer.Serialize(oWriter, pSource);
//return oWriter.ToString();
XmlSerializerNamespaces oNamespaces = new XmlSerializerNamespaces();
oNamespaces.Add("", "");
StringWriter oWriter = new StringWriter();
XmlSerializer oSerializer = new XmlSerializer(pSource.GetType());
oSerializer.Serialize(oWriter, pSource, oNamespaces);
string Serialized = oWriter.ToString();
return Serialized.Remove(0, Serialized.IndexOf('<', 1));
}
/// <summary>
/// Deserialize an xml string to an object
/// </summary>
/// <param name="pXmlSource">Xml Source</param>
/// <returns>Deserialized object</returns>
public static T DeSerialize<T>(string pXmlSource)
{
return (T)(new XmlSerializer(typeof(T)).Deserialize(new StringReader(pXmlSource)));
}
}
}
mcintyre321
January 22, 2009, January 22, 2009 10:28, permalink
Good effort but I'm afraid it doesn't quite fit the bill of what I'm looking for (I should have been a bit clearer about the requirements)! I actually need to have the object B returned as type B, even though it is stored in a field reference of type A. I've rewritten my code so it a/ should work better and b/ has a unit tests. The tests should stay the same
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml.Serialization;
using NUnit.Framework;
namespace XmlSerializerTests
{
namespace XmlSerializerTests.SomeOtherNamespace
{
[Serializable]
public class Person
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
}
[Serializable]
public class Person
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
[Serializable]
public class Employee : Person
{
public string Title { get; set; }
}
[TestFixture]
public class Tests
{
CustomSerializer serializer = new CustomSerializer();
[Test]
public void TestThatAClassCanBeDeserializedAsItsBaseClass()
{
var employee = new Employee() {FirstName = "John", LastName = "Doe", Id = 1, Title = "Dogsbody"};
var xml = serializer.Serialize(employee);
var obj = serializer.DeSerializeObject<Person>(xml);
Assert.AreEqual(typeof (Employee), obj.GetType());
}
[Test, ExpectedException(typeof(InvalidCastException))]
public void NoDuckSerializationTest()
{
var person = new Person() { FirstName = "John", LastName = "Doe", Id = 1};
var xml = serializer.Serialize(person);
var someOtherPerson = serializer.DeSerializeObject<XmlSerializerTests.SomeOtherNamespace.Person>(xml);
}
}
public class CustomSerializer
{
static Dictionary<string, Type> types = new Dictionary<string, Type>();
public string Serialize(object o)
{
StringBuilder sb = new StringBuilder();
Type t = o.GetType();
if (t.IsSerializable)
{
XmlSerializer serializer = new XmlSerializer(t);
using (TextWriter tw = new StringWriter(sb))
{
var namespaces = new XmlSerializerNamespaces();
namespaces.Add("", "");
serializer.Serialize(tw, o, namespaces);
tw.Close();
}
}
else
{
throw new InvalidOperationException(string.Format("Objects of type {0} are not serializable.", t.Name));
}
sb.Remove(0, 42);
sb.Insert(0, '.');
sb.Insert(0, t.Namespace);
sb.Insert(0, '<');
sb.Remove(sb.Length - t.Name.Length - 2, t.Name.Length + 2);
sb.Append(t.FullName);
sb.Append(@"/>");
return sb.ToString();
}
public T DeSerializeObject<T>(string content)
{
var indexOfFirstSpace = content.IndexOf(' ', 20);
var indexOfFirstBreak = content.IndexOf("\r\n");
var indexOfFirstCloseBrace = content.IndexOf(">");
var endOfTypeNameIndex = new[] {indexOfFirstSpace, indexOfFirstBreak, indexOfFirstCloseBrace}.Where(i => i > -1).Min();
var typeName = content.Substring(1, endOfTypeNameIndex - 1).Trim();
Type type;
if (!types.ContainsKey(typeName))
{
type = AppDomain.CurrentDomain.GetAssemblies().SelectMany(a => a.GetTypes()).Single(t => t.FullName == typeName);
lock (types)
{
types[typeName] = type;
}
}
else
{
type = types[typeName];
}
object o;
var serializer = new XmlSerializer(type);
int posAfterTypeName = content.IndexOf(' ');
int posOfStartOfTypeName = content.LastIndexOf('.', posAfterTypeName) + 1;
content = @"<?xml version=""1.0"" encoding=""utf-16""?><" + content.Substring(posOfStartOfTypeName);
content = content.Substring(0, content.Length - typeName.Length - 2) + '/' + type.Name + '>';
using (TextReader tr = new StringReader(content))
{
o = serializer.Deserialize(tr);
tr.Close();
}
return (T)o;
}
}
}
The default XML serializer does some things I consider undesirable
1. The XML it produces is full of unnecessary XML Namespace stuff
2. It is not polymorphic - say you have class B : A { }, and have serialized a type B, you should be able to call Deserialize<A>() on it
My implementation corrects these issues, but it seems a bit crazy!