I have been able to use ShouldSerializeProperty pattern with XmlSerializer to ignore a property of type ICollection<>. Below is the sample code. It works if I use XmlIgnore on ListOfTestClassB property but I want to achieve the same functionality utilizing ShouldSerialize pattern. If I run the code below I get the 'System.InvalidOperationException' saying:
Cannot serialize member ConsoleApplication3.TestClassA.ListOfTestClassB of type System.Collections.Generic.ICollection`1[[ConsoleApplication3.TestClassB, ConsoleApplication3, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]] because it is an interface.
Can anyone highlight what am I missing?
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
using System.Threading.Tasks;
using System.Xml;
using System.Xml.Serialization;
namespace ConsoleApplication3
{
[Serializable]
public class TestClassA
{
public int A { get; set; }
public int B { get; set; }
public string C { get; set; }
public TestClassB TestClass { get; set; }
public virtual ICollection<TestClassB> ListOfTestClassB { get; set; }
public bool ShouldSerializeListOfTestClassB()
{
return false;
}
}
[Serializable]
public class TestClassB
{
public int Int32 { get; set; }
public string String { get; set; }
}
class Program
{
static object GetObject()
{
return new TestClassA { A = 1, B = 2, C = "test class a", TestClass = new TestClassB { Int32 = 11, String = "test class b"} };
}
static void Main(string[] args)
{
var result = new StringBuilder();
var entity = GetObject();
var ser = new XmlSerializer(entity.GetType());
var settings = new XmlWriterSettings { OmitXmlDeclaration = true };
using (var stream = new MemoryStream())
{
// make a copy of the entity - we do not want to serialize ZIP file !
var formatter = new BinaryFormatter();
formatter.Serialize(stream, entity);
stream.Position = 0;
entity = formatter.Deserialize(stream);
}
// serialize object
ser.Serialize(XmlWriter.Create(result, settings), entity);
Console.WriteLine(result.ToString());
Console.ReadLine();
}
}
}
This is where the check happens: http://referencesource.microsoft.com/#System.Xml/System/Xml/Serialization/Models.cs,249.
As you can see, it doesn't call the ShouldSerialize* method until after it's already identified fields/properties to serialize. So, your ListOfTestClassB has to be serializable, or it must be decorated with [XmlIgnore]. In order to be serializable your property must be of a concrete type that has the [Serializable] attribute applied.
There's a workaround if you can't modify the class. One of the overloads of the XmlSerializer.Serialize(...) method accepts an overrides object. I've created a simple example below:
[Serializable]
public class Foo
{
public IList<int> Numbers { get; set; }
public string TheNumber { get; set; }
}
class Program
{
private static void Main(string[] args)
{
var attributes = new XmlAttributes
{
XmlIgnore = true
};
var overrides = new XmlAttributeOverrides();
overrides.Add(typeof(Foo), "Numbers", attributes);
var serializer = new XmlSerializer(typeof(Foo), overrides);
// the rest of this is for demo purposes.
// the code above is whats important
//
using (var ms = new MemoryStream())
using (var reader = new StreamReader(ms))
{
serializer.Serialize(ms, new Foo() { TheNumber = "5" });
ms.Flush();
ms.Seek(0, SeekOrigin.Begin);
Debug.WriteLine(reader.ReadToEnd());
}
}
}
This generates:
<?xml version="1.0"?>
<Foo xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<TheNumber>5</TheNumber>
</Foo>
As you can see, I'm dynamically adding the XmlIgnore attribute to my Numbers element, and the serializer consequently ignores it. :) You should have no trouble adapting this to your own code.
Note: As noted by dbc, it's important to cache this serializer and re-use it, otherwise you're going to have a lot of memory leaks. You can keep a static reference to it, or use a hashtable to store different serializers for different types.
To increase performance, the XML serialization infrastructure dynamically generates assemblies to serialize and deserialize specified types. The infrastructure finds and reuses those assemblies. This behavior occurs only when using the following constructors:
XmlSerializer.XmlSerializer(Type)
XmlSerializer.XmlSerializer(Type, String)
If you use any of the other constructors, multiple versions of the same assembly are generated and never unloaded, which results in a memory leak and poor performance. The easiest solution is to use one of the previously mentioned two constructors. Otherwise, you must cache the assemblies in a Hashtable, as shown in the following example.
If you use XmlIgnore, then it will not care about that property at all. If you use ShouldSerialize, it doesn't know until runtime whether it should serialize that type, so it must be able to. In this case, the type you're trying to serialize must be a concrete class. Try using List<TestClassB>.
Related
I am converting my working XML serialization so that the model classes inherit from abstract base classes (to allow for future use of different serial formats).
My serialization as-is is working fine but when I switch to using models derived from a base class I get all kinds of exceptions so I'm unsure of how to proceed.
My class base class is:
namespace Data
{
public abstract class Configuration
{
public abstract string Schema { get; set; }
public abstract Command[] Commands { get; set; }
}
public abstract class Command
{
public abstract string Name { get; set; }
}
}
My derived concrete class (the class that is working just fine before it was derived) is then in a child namespace:
namespace Data.Xml
{
[Serializable()]
[XmlType(AnonymousType = true)]
[XmlRoot(Namespace = "", IsNullable = false)]
public class Configuration : Data.Configuration
{
[XmlAttribute("noNamespaceSchemaLocation",
Namespace = System.Xml.Schema.XmlSchema.InstanceNamespace)]
public override string Schema { get; set; }
[XmlArrayItem("Command", IsNullable = false)]
public override Data.Command[] Commands { get; set; }
}
[Serializable()]
public class Command : Data.Command
{
public override string Name { get; set; }
}
}
I call the serializer within that child namespace like so:
public override Data.Configuration DeserializeConfig(StreamReader config)
{
var cs = new XmlSerializer(typeof(Configuration),
new Type[] { typeof(Command) });
return (Configuration)ConfigSerializer.Deserialize(ConfigStreamReader);
}
public override string SerializeConfig(Data.Configuration c, Encoding encoding)
{
string Output = null;
var Stream = new MemoryStream();
using (var Writer = new XmlTextWriter(Stream, encoding))
{
Writer.Formatting = Formatting.Indented;
XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
ns.Add("xsi", XmlSchema.InstanceNamespace);
(new XmlSerializer(typeof(Configuration))).Serialize(Writer, c, ns);
Output = encoding.GetString(Stream.ToArray());
}
Stream.Dispose();
return Output;
}
The resulting XML should look like:
<?xml version="1.0" encoding="utf-8"?>
<Configuration
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="SomeSchema.xsd">
<Commands>
<Command>
<Name>SomeNameValue</Name>
</Command>
</Commands>
</Configuration>
I am seeing the following exception when attempting to instantiate the serializer (first line in DeserializeConfig() method above):
InvalidOperationException: Types 'Data.Command' and 'Data.Xml.Command' both use the XML type name, 'Command', from namespace ''. Use XML attributes to specify a unique XML name and/or namespace for the type.
I'm not really sure why the serializer is trying to create something from the base class, sure the names are the same, that's kind of the idea of derivation and namespaces etc ... How do I properly mark this up with attributes to have it de/serialize properly?
FYI: I did see several questions already on this, but the answers all seemed specific enough to the askers requirements that I could not work out how to apply the information to this, seemingly simple, scenario.
Update: I figured out how to pass included types into the serializer at instantiation instead of having to annotate the base class so I have removed that part from my question and updated the code. This outdates bruno's suggestion and my response (although the suggested question still does not apply).
Update: I attempted to separate the names in XML namespaces by adding the derived class to a namespace (i.e. adding [XmlElement(Namespace = "http://www.foo.com/data/xml")] to each property in the derived class) but this made no difference as the serializer still seems to "see" both the base and derived class together and so thinks they're both in that namespace.
Finally flipping figured most of this out.
I stepped back and started with a very simple working non-derived example and worked up to what I needed.
There were two things going on here. First the clashing type names, then the clashing property names. While I had bits of each of these right, the amount of permutations of options for structuring each when combined together had me confused.
To prevent the abstract and derived type names from clashing when serialized I needed to make the derived class type anonymous (here using the XmlType attribute).
To stop the property names clashing I needed to ignore both the property in the derived class and the base class. To do this without editing the base class I was missing a vital piece, XmlAttributeOverrides. I had seen this mentioned in the MSDN documentation for XmlSerializer.Serialize() but the information there was pretty minimal in explaining what it pertained to. This answer to another question led me to David Woodward's excellent explanation.
I have yet to try any of this with a derived type list property, or with deserialization.
Below is complete basic example of a program that outputs a string with some serialized XML on the console output:
using System;
using System.Text;
using System.IO;
using System.Xml;
using System.Xml.Serialization;
namespace Test
{
class Program
{
static void Main(string[] args)
{
var TestBar = new MyXml.Bar()
{
Name = "John Smith",
};
Serializer s = new MyXml.Serializer();
var TestOutput = s.Serialize(TestBar);
Console.WriteLine(TestOutput);
}
}
public abstract class Bar
{
public abstract string Name { get; set; }
}
public abstract class Serializer
{
public abstract string Serialize(Bar bar);
}
namespace MyXml
{
public class Serializer : Test.Serializer
{
public override string Serialize(Test.Bar bar)
{
string Output = null;
var Stream = new MemoryStream();
var Encoding = new UTF8Encoding(false, true);
// Ignore the Name property in the *base* class!
var ao = new XmlAttributeOverrides();
var a = new XmlAttributes();
a.XmlElements.Clear(); // Clear any element attributes
a.XmlAttribute = null; // Remove any attribute attributes
a.XmlIgnore = true; // Set the ignore attribute value true
ao.Add(typeof(Test.Bar), "Name", a); // Set to use with Test.Bar.Name
using (var Writer = new XmlTextWriter(Stream, Encoding))
{
Writer.Formatting = Formatting.Indented;
var s = new XmlSerializer(typeof(Bar), ao);
s.Serialize(Writer, bar);
Output = Encoding.GetString(Stream.ToArray());
}
Stream.Dispose();
return Output;
}
}
[Serializable]
[XmlType(AnonymousType = true)] // Make type anonymous!
[XmlRoot(IsNullable = false)]
public class Bar : Test.Bar
{
[XmlIgnore] // Ignore the Name property in the *derived* class!
public override string Name
{
get => Unreverse(ReverseName);
set => ReverseName = Reverse(value);
}
[XmlElement("Name", IsNullable = false)]
public string ReverseName { get; set; }
private string Unreverse(string name)
{
return "John Smith"; // Smith, John -> John Smith
}
private string Reverse(string name)
{
return "Smith, John"; // John Smith -> Smith, John
}
}
}
}
I am trying to deserialize incoming XML data with C#/.NET 3.5 using a class hierarchy that starts with a class tagged as XmlRoot and contains classes and members with the required attributes. This works so far.
Now I have come across input data that looks approximately like this:
<?xml version="1.0" encoding="UTF-8"?>
<ns1:foo xmlns:ns1="http://some.name/space">
<ns1:bar>
<payload interesting="true">
<stuff type="interesting"/>
</payload>
</ns1:bar>
</ns1:foo>
The outermost two elements - ns1:foo and ns1:bar - are results of the serialization process in the remote system that I have no control over. They always exist in exactly the same constellation and are of no interest whatsoever.
Following my current approach, I could write a class Foo that references a class Bar which in turn references the class Payload. However, since I'm not interested in the foo and bar elements, is there a way to skip them during the deserialization and have the Deserializer return only the payload? If not: Is it possible to make a class FooPayload with an XmlRoot(ElementName = "foo", Namespace = "http://some.name/space")attribute, but then somehow skip the bar and payload levels so that the FooPayload class directly contains properties named Interesting and Stuff?
I modified your XML to correct for errors. See interesting solution below.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Serialization;
using System.IO;
using System.Text.RegularExpressions;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
string input =
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
"<ns1:foo xmlns:ns1=\"http://some.name/space\">" +
"<ns1:bar>" +
"<ns1:payload interesting=\"true\">" +
"<ns1:stuff type=\"interesting\"/>" +
"</ns1:payload>" +
"</ns1:bar>" +
"</ns1:foo>";
//remove prefix
string pattern = "(</?)([^:]*:)";
Regex expr = new Regex(pattern);
input = expr.Replace(input, "$1");
XmlSerializer xs = new XmlSerializer(typeof(Foo));
StringReader s_reader = new StringReader(input);
XmlTextReader reader = new XmlTextReader(s_reader);
Foo foo = (Foo)xs.Deserialize(reader);
foo.GetStuff();
}
}
[XmlRoot(ElementName = "foo")]
public class Foo
{
[XmlElement("bar")]
public Bar bar { get; set; }
private Boolean interesting;
private string type;
public void GetStuff()
{
interesting = bar.payload.interesting;
type = bar.payload.stuff.type;
}
}
[XmlRoot("bar")]
public class Bar
{
[XmlElement("payload")]
public Payload payload { get; set; }
}
[XmlRoot("payload")]
public class Payload
{
[XmlAttribute("interesting")]
public Boolean interesting { get; set; }
[XmlElement("stuff")]
public Stuff stuff { get; set; }
}
[XmlRoot("stuff")]
public class Stuff
{
[XmlAttribute("type")]
public string type { get; set; }
}
}
Say I have the following classes:
[DataContract]
class Entry<T>
{
[DataMember]
public T Data { get; set; }
}
[DataContract]
class DataList<T>
{
[DataMember]
public IList<T> Collection { get; set; }
}
[DataContract]
[KnownType(typeof(User))]
class ExtendedDataList : DataList<object>
{
[DataMember]
public string SomeExtraParameter { get; set; }
}
[DataContract]
class User
{
[DataMember]
public string Name { get; set; }
}
I've applied KnownTypeAttribute to ExtendedDataList since that class extends the base class of general type object, and it will store different kinds of objects in the list. In this example, I've marked a known type of User since I know it'll contain User objects.
Here's the serialization code:
var user = new User { Name = "Bob" };
var users = new ExtendedDataList { Collection = new List<object> { user } };
serialize(users);
with
static void serialize<T>(T obj)
{
var entry = new Entry<T>();
entry.Data = obj;
var stream = new MemoryStream();
var serializer = new DataContractJsonSerializer(typeof(Entry<T>));
serializer.WriteObject(stream, entry);
stream.Seek(0, SeekOrigin.Begin);
var r = new StreamReader(stream);
var s = r.ReadToEnd();
System.Diagnostics.Debug.WriteLine(s);
}
the line serializer.WriteObject(stream, entry); throws a SerializationException saying that the type User was not expected and that I should use KnownTypeAttribute to specify it. But I did (indirectly)!
How can I make this work? I cannot move KnownType to the Entry<T> class, because it needs to be general. Why can't DataContractJsonSerializer see that ExtendedDataList specifies the User as a known type?
I figured out a way to get it to work, but it still doesn't explain why DataContractJsonSerializer is ignoring the KnownType attribute I've applied to ExtendedDataList.
It seems that DataContractJsonSerializer isn't smart enough to notice the KnownType attributes that I've specified on the ExtendedDataList class; apparently it only honors the known types that I've attached to the Entry<T> class since typeof(Entry<T>) is the type that I give to the constructor of DataContractJsonSerializer
I want Entry<T> to remain general, and I do not want to litter it with all sorts of KnownType attributes. So instead, I use the following class definition:
[DataContract]
[KnownType("KnownTypes")]
class Entry<T>
{
[DataMember]
public T Data { get; set; }
public static IEnumerable<Type> KnownTypes()
{
var attrs = typeof(T).GetTypeInfo().GetCustomAttributes<KnownTypeAttribute>();
return attrs.Select(attr => attr.Type);
}
}
This uses reflection to get the KnownType attributes attached to the inner type T and then passes these types to whatever data contract serializer that serializes Entry<T>.
Note: requires using System.Linq and using System.Reflection.
I'm using protobuf-net version 2.0.0.640 to serialize some data as shown below.
[ProtoContract(ImplicitFields = ImplicitFields.AllFields)]
public interface ITestMessage
{
}
[ProtoContract(ImplicitFields = ImplicitFields.AllFields)]
public class MyOrder : ITestMessage
{
public int Amount { get; set; }
}
[ProtoContract(ImplicitFields = ImplicitFields.AllFields)]
public class MyOrderWrapper
{
public MyOrder Order { get; set; }
}
[TestMethod]
public void TestOrderSerialize()
{
var order = new MyOrder() {Amount = 10};
var orderWrapper = new MyOrderWrapper() { Order = order };
using (var file = File.Create("C:\\temp\\order.bin"))
{
Serializer.Serialize<MyOrderWrapper>(file, orderWrapper);
}
}
Now, If I declare an inheritance dependency between 'ITestMessage' & 'MyOrder' via code using:
RuntimeTypeModel.Default[typeof(ITestMessage)].AddSubType(2, typeof(MyOrder));
I get the following error when trying to deserialize my prevously saved data.
"No parameterless constructor found for ITestMessage".
[TestMethod]
public void TestOrderDeserialize()
{
RuntimeTypeModel.Default[typeof(ITestMessage)].AddSubType(2, typeof(MyOrder));
MyOrderWrapper orderWrapper;
using (var file = File.OpenRead("C:\\temp\\order.bin"))
{
orderWrapper = Serializer.Deserialize<MyOrderWrapper>(file);
}
}
Can someone please explain why this would happen when 'MyOrderWrapper' does not reference anything higher than 'MyOrder' in the inheritance hirarchy.
Also, why it works when I explictly include '[ProtoInclude(2, typeof(MyOrder))]' on 'ITestMessage'
Thanks
Basically, that is a breaking change as far as the serializer is concerned - at the wire layer, neither "classes" nor "interfaces" exist, so in terms of storage, this is akin to changing the base-type of the class; when serializing, the root type was MyOrder - and during deserialization the root type is ITestMessage. This will not make it happy.
Basically: you can't do that.
I have different kinds of objects in c# that I would like to save to a file (XML is preferred) but I can't use serialization since the class are not written by me but are from a DLL.
What is the best solution for this ?
I eventually used JavaScriptSerializer and it does exactly what I was looking for:
List<Person> persons = new List<Person>();
persons.Add(new Person(){Name = "aaa"});
persons.Add(new Person() { Name = "bbb" });
JavaScriptSerializer javaScriptSerializer = new JavaScriptSerializer();
var strData = javaScriptSerializer.Serialize(persons);
var persons2 = javaScriptSerializer.Deserialize<List<Person>>(strData);
I've whipped up a quick little extension method that will "serialize" to XML, given a non-serializable object. It's pretty rough and doesn't do a heck of a lot of checking and the XML it generates you can easily tweak to meet your needs:
public static string SerializeObject<T>(this T source, bool serializeNonPublic = false)
{
if (source == null)
{
return null;
}
var bindingFlags = BindingFlags.Instance | BindingFlags.Public;
if (serializeNonPublic)
{
bindingFlags |= BindingFlags.NonPublic;
}
var properties = typeof(T).GetProperties(bindingFlags).Where(property => property.CanRead).ToList();
var sb = new StringBuilder();
using (var writer = XmlWriter.Create(sb))
{
writer.WriteStartElement(typeof(T).Name);
if (properties.Any())
{
foreach (var property in properties)
{
var value = property.GetValue(source, null);
writer.WriteStartElement(property.Name);
writer.WriteAttributeString("Type", property.PropertyType.Name);
writer.WriteAttributeString("Value", value.ToString());
writer.WriteEndElement();
}
}
else if (typeof(T).IsValueType)
{
writer.WriteValue(source.ToString());
}
writer.WriteEndElement();
}
return sb.ToString();
}
I tested it on this class:
private sealed class Test
{
private readonly string name;
private readonly int age;
public Test(string name, int age)
{
this.name = name;
this.age = age;
}
public string Name
{
get
{
return this.name;
}
}
public int Age
{
get
{
return this.age;
}
}
}
as well as the number 3 and object. The resulting XML is as such:
<?xml version="1.0" encoding="utf-16"?>
<Test>
<Name Type="String" Value="John Doe" />
<Age Type="Int32" Value="35" />
</Test>
<?xml version="1.0" encoding="utf-16"?>
<Int32>3</Int32>
<?xml version="1.0" encoding="utf-16"?>
<Object />
respectively.
write your own serializable wrappers around the non-serializable classes of the DLL.
EDIT: AutoMapper was suggested in the comments and I hadn't heard of it yet, but now that I have I'd definitely use that instead of writing the wrappers myself. Unless there's some reflection required to capture some of the internal state of the non-serializable object (if possible), I don't know if AutoMapper has anything to offer there or you'd have to see if you could capture that in your wrapper.
I would write a POCO (Plain Old Class Object) class that mimics the object from the DLL returned. Generally if you are using, I believe, .NET 3.5 or higher you have the ability to use LINQ. I favor Linq to put objects into other classes or perform sorting or other operations on them.
Here is a simple example where you would be dummying in your return object for example. Keep in mind in a DLL of course you could have many differing objects and do this multiple times. I also would wrap my methods up in their own class for re usability instead of doing it in the main. But here is a simple proof of concept
using System;
using System.Linq;
using System.Windows.Forms;
using System.IO;
using System.Xml.Serialization;
using System.Collections.Generic;
using System.Xml.Linq;
namespace ExampleSerializer
{
class Program
{
// example class to serialize
[Serializable]
public class SQLBit
{
[XmlElement("Name")]
public string Name { get; set; }
[XmlText]
public string data { get; set; }
}
// example class to populate to get test data
public class example
{
public string Name { get; set; }
public string data { get; set; }
}
static void Main(string[] args)
{
string s = "";
// make a generic and put some data in it from the test
var ls = new List<example> { new example { Name = "thing", data = "data" }, new example { Name = "thing2", data = "data2" } };
// make a second generic and put data from the first one in using a lambda
// statement creation method. If your object returned from DLL is a of a
// type that implements IEnumerable it should be able to be used.
var otherlist = ls.Select(n => new SQLBit
{
Name = n.Name,
data = n.data
});
// start a new xml serialization with a type.
XmlSerializer xmler = new XmlSerializer(typeof(List<SQLBit>));
// I use a textwriter to start a new instance of a stream writer
TextWriter twrtr = new StreamWriter(#"C:\Test\Filename.xml");
// Serialize the stream to the location with the list
xmler.Serialize(twrtr, otherlist);
// Close
twrtr.Close();
// TODO: You may want to put this in a try catch wrapper and make up your
// own classes. This is a simple example.
}
}
}
I think the term "without-serialization" in the question header is misleading.
If i understood you correctly you want to serialize objects that have no serilisation-attributes.
There are libraries like sharpserializer and protobuf-net that can do the job for you.