XMLSerializer : Ignoring Elements in List - c#

I got a fine working XMLSerializer for a sophisticated List of 3D models with many different elements. Now I want to serialize another List beside of that, that is saving only few Elements of it.
I just tried to bar some of them out with [XMLIgnore] but this causes problems in the deserialization of my other list.
Here's the Code of my (De)Serializer:
public void SerializeObject<T>(T serializableObject, string fileName)
{
if (serializableObject == null) return;
var serializer = new XmlSerializer(serializableObject.GetType());
using (var stream = File.Open(fileName, FileMode.Create))
{
serializer.Serialize(stream, serializableObject);
}
}
public T DeserializeObject<T>(string fileName)
{
if (string.IsNullOrEmpty(fileName)) return default(T);
var serializer = new XmlSerializer(typeof(T),overrides);
using (var stream = File.Open(fileName, FileMode.Open))
{
return (T)serializer.Deserialize(stream);
}
}
Maybe its an idea to clone my list, and to remove all the Elements I don't wish to save before Serialization?

You can use base derived relation to build your collection type where one ignore serialization and other override what you ignored.
[Serializable]
public abstract class BaseClass
{
[XmlIgnore]
public virtual bool BaseMember { get; set; }
}
[Serializable]
public class DerivedClass : BaseClass
{
public string DerivedMember { get; set; }
}
[Serializable]
public class XmlIgnore : BaseClass
{
// no XmlIgnore
public override bool BaseMember
{
get
{
return base.BaseMember;
}
set
{
base.BaseMember = value;
}
}
}

Related

XML serializing node as parent class incorrectly instead of child

I am serializing a class that is incorrectly creating the node of one of its properties. my class structure is as follows:
Here is the top class i am serializing
[DataContract]
public class XmlReportConfiguration
{
[DataMember]
[XmlArrayItem(nameof(SingleValueDescription), typeof(SingleValueDescription))]
[XmlArrayItem(nameof(MultiValueDescription), typeof(MultiValueDescription))]
public List<Description> Descriptions { get; set; }
}
MultiValueDescription inherits from SingleValueDescription which inherits from Description.
Description has the XMlInclude tag for both the single and multi value description
My issue is when i go to serialize a Description that is of type MultiValueDescription , the xml node is serializing it as SingleValueDescription.
If i remove the XmlArrayItem Entry for the SingleValueDescription from the XmlReportConfiguration class, it then works as I want it to, but I cant remove that declaration for obvious reasons.
Is there some tag/declaration I'm missing here that is causing the serializer to ignore the child class for the node and use the parent class?
Here is the method when creating the serializer:
public static string SerializeReportConfiguration(XmlReportConfiguration config)
{
XmlSerializer serializer = new XmlSerializer(typeof(XmlReportConfiguration));
StringBuilder sb = new StringBuilder();
using (TextWriter writer = new StringWriter(sb))
{
serializer.Serialize(writer, config);
}
return sb.ToString();
}
public static XmlReportConfiguration DeserializeReportConfiguration(string xml)
{
XmlSerializer serializer = new XmlSerializer(typeof(XmlReportConfiguration));
using (StringReader reader = new StringReader(xml))
{
XmlReportConfiguration sessionConfig = serializer.Deserialize(reader) as XmlReportConfiguration;
return sessionConfig;
}
}
Found a solution. The polymorphism causes issues when serializing. In my report config I used a list of the following new class and it solved my issues.
[DataContract]
public class SerializableDescription : IXmlSerializable
{
#region Properties
public SerializableDescription()
{
}
public SerializableDescription(Description description)
{
Description = description;
}
[DataMember]
public Description Description { get; set; }
#endregion
#region Methods
public XmlSchema GetSchema()
{
return null;
}
public void ReadXml(XmlReader reader)
{
reader.MoveToContent();
string typeStr = reader.GetAttribute("Type");
Type type = TypeCache.GetTypeEx(typeStr);
XmlSerializer ser = new XmlSerializer(type);
reader.ReadStartElement();
Description = (Description)ser.Deserialize(reader);
reader.ReadEndElement();
}
public void WriteXml(XmlWriter writer)
{
Type type = Description.GetType();
writer.WriteAttributeString("Type", type.FullName);
XmlSerializer ser = new XmlSerializer(type);
ser.Serialize(writer, Description);
}
#endregion
}

Ignore null values - Serialization

How can I set up the System.Runtime.Serialization serializer to ignore null values?
Or do I have to use the XmlSerializer for that? If so, how?
(I don't want <ecommerceflags i:nil="true"/> tags like this to be written, if it's null then just skip it)
With System.Runtime.Serialization.DataContractSerializer you need to mark the property with [DataMember(EmitDefaultValue = false)].
Example, the code below:
class Program
{
static void Main()
{
Console.WriteLine(SerializeToString(new Person { Name = "Alex", Age = 42, NullableId = null }));
}
public static string SerializeToString<T>(T instance)
{
using (var ms = new MemoryStream())
{
var serializer = new DataContractSerializer(typeof(T));
serializer.WriteObject(ms, instance);
ms.Seek(0, SeekOrigin.Begin);
using (var sr = new StreamReader(ms))
{
return sr.ReadToEnd();
}
}
}
}
[DataContract]
public class Person
{
[DataMember]
public string Name { get; set; }
[DataMember]
public int Age { get; set; }
[DataMember(EmitDefaultValue = false)]
public int? NullableId { get; set; }
}
prints the following:
<Person xmlns="http://schemas.datacontract.org/2004/07/ConsoleApplication4" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<Age>42</Age>
<Name>Alex</Name>
</Person>
Though it has less value (except that it makes the serialized stream shorter), you can customize your serialization to achieve this.
When using System.Runtime.Serialization, you can implement the ISerializable interface:
[Serializable]
public class MyClass: ISerializable
{
private string stringField;
private object objectField;
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
if (stringField != null)
info.AddValue("str", stringField);
if (objectField != null)
info.AddValue("obj", objectField);
}
// the special constructor for deserializing
private MyClass(SerializationInfo info, StreamingContext context)
{
foreach (SerializationEntry entry in info)
{
switch (entry.Name)
{
case "str":
stringField = (string)entry.Value;
break;
case "obj":
objectField = entry.Value;
break;
}
}
}
}
When using XML serialization, you can implement the IXmlSerializable interface to customize the output in a similar way.
As far as I read, you could use the Specified-Feature
public int? Value { get; set; }
[System.Xml.Serialization.XmlIgnore]
public bool ValueSpecified { get { return this.Value != null; } }
To only write it if it's specified.
Another way would be
[System.Xml.Serialization.XmlIgnore]
private int? value;
public int Value { get { value.GetValueOrDefault(); } }

DataContractSerializer compatibility after namespace changed

I have a class need to be serialized.
namespace serializedobject
{
[DataContract]
public class Class1
{
string string1_;
string string2_;
EntityA entity_;
[DataMember]
public string string3
{
get { return string1_; }
set { string1_ = value; }
}
[DataMember]
public string string2
{
get { return string2_; }
set { string2_ = value; }
}
[DataMember]
public EntityA Entity
{
get { return entity_; }
set { entity_ = value; }
}
public static Class1 FromXML(string desc)
{
using (MemoryStream ms = new MemoryStream())
{
StreamWriter writer = new StreamWriter(ms);
writer.Write(desc);
writer.Flush();
ms.Seek(0, 0);
DataContractSerializer ser = new DataContractSerializer(typeof(Class1));
return (Class1)ser.ReadObject(ms);
}
}
public string ToXML()
{
using (MemoryStream ms = new MemoryStream())
{
DataContractSerializer ser = new DataContractSerializer(typeof(Class1));
ser.WriteObject(ms, this);
ms.Seek(0, 0);
StreamReader reader = new StreamReader(ms);
return reader.ReadToEnd();
}
}
}
[DataContract]
public class EntityA
{
string name_;
[DataMember]
public string Name
{
get { return name_; }
set { name_ = value; }
}
}
}
it is works fine with FromXML and ToXML. one of serialized context like:
<Class1 xmlns="http://schemas.datacontract.org/2004/07/serializedobject" xmlns:i="http://www.w3.org/2001/XMLSchema-instance"><Entity><Name>az</Name></Entity><string2 i:nil="true"/><string3>test</string3></Class1>
Later I need to move class EntityA to another namespace "outside", now the serialized context like:
<Class1 xmlns="http://schemas.datacontract.org/2004/07/serializedobject" xmlns:i="http://www.w3.org/2001/XMLSchema-instance"><Entity xmlns:a="http://schemas.datacontract.org/2004/07/outside"><a:Name>az</a:Name></Entity><string2 i:nil="true"/><string3>test</string3></Class1>
but now the serialized xml which created before change namespace can't be deserialized correctly. I guess this is because of for class "EntityA" changed namespace (xmlns:a added).
does anybody run into the problem before? any suggestion?
You can stop the namespace being added to the XML by specifying [DataContract(Namespace="")]. This relies on you setting that attribute BEFORE you save any xml code.
You can use this approach only if you have not already serialized any data, so this is the approach you would use when first designing a class to be serialized.
(If you have already got serialized data that you must deal with, see the second part of my answer below.)
This code sample has the two classes called Demo in two different namespaces, Test1 and Test2.
We serialize the code using the class from one namespace, and deserialize it using the class from the other namespace:
using System;
using System.IO;
using System.Runtime.Serialization;
using System.Xml;
namespace ConsoleApp1
{
namespace Test1
{
[DataContract(Namespace="")]
public sealed class Demo
{
[DataMember]
public string Value { get; set; }
}
}
namespace Test2
{
[DataContract(Namespace="")]
public sealed class Demo
{
[DataMember]
public string Value { get; set; }
}
}
sealed class Program
{
private void run()
{
string filename = Path.GetTempFileName();
var demo1 = new Test1.Demo {Value = "DEMO"};
ToFile(filename, demo1);
var demo2 = FromFile<Test2.Demo>(filename);
Console.WriteLine(demo2.Value);
}
public static void ToFile(string filename, object obj)
{
DataContractSerializer serializer = new DataContractSerializer(obj.GetType());
using (var streamWriter = File.CreateText(filename))
using (var xmlWriter = XmlWriter.Create(streamWriter, new XmlWriterSettings{Indent = true}))
{
serializer.WriteObject(xmlWriter, obj);
}
}
public static T FromFile<T>(string filename)
{
DataContractSerializer serializer = new DataContractSerializer(typeof(T));
using (var textReader = File.OpenText(filename))
using (var xmlReader = XmlReader.Create(textReader))
{
return (T)serializer.ReadObject(xmlReader);
}
}
[STAThread]
static void Main(string[] args)
{
new Program().run();
}
}
}
If you have already serialized data without the Namespace="" attribute, then you will need instead to apply the appropriate namespace to the new class:
namespace Test1
{
[DataContract]
public sealed class Demo
{
[DataMember]
public string Value { get; set; }
}
}
namespace Test2
{
// Note the namespace includes both nested namespaces, i.e. ConsoleApp1.Test1
[DataContract(Namespace="http://schemas.datacontract.org/2004/07/ConsoleApp1.Test1")]
public sealed class Demo
{
[DataMember]
public string Value { get; set; }
}
}

Prepend parent node when serializing C# property

I'd like to serialize the following class to xml:
public class Survey
{
[XmlElement("edit")]
public string EditLink { get; set; }
}
As expected this serializes as (removed extra stuff not important to the question)
<Survey><edit>http://example.com/editlink</edit></Survey>
However, I'd like to prepend a parent node to the edit node, so that the resultant xml is:
<Survey><links><edit>http://example.com/editlink</edit></links></Survey>
Is there a way to do this with just the serialization attributes, without modifying the structure of the class?
You can't with that structure. If you expose EditLink as a collection then you can:
public class Survey
{
[XmlArray("links")]
[XmlArrayItem("edit")]
public string[] edit
{
get
{
return new [] {EditLink};
}
set
{
EditLink = value[0];
}
}
[XmlIgnore]
public string EditLink { get; set; }
}
Which yields:
<Survey>
<links>
<edit>http://example.com/editlink</edit>
</links>
</Survey>
You can try using the XMLSerializer class.
public class Survey
{
public string EditLink { get; set; }
}
private void SerializeSurvey()
{
XmlSerializer serializer = new XmlSerializer(typeof(Survey));
Survey survey = new Survey(){EditLink=""};
// Create an XmlTextWriter using a FileStream.
Stream fs = new FileStream(filename, FileMode.Create);
XmlWriter writer = new XmlTextWriter(fs, Encoding.Unicode);
// Serialize using the XmlTextWriter.
serializer.Serialize(writer, survey);
writer.Close();
}

C# [XmlElement] attribute doesn't implicitly cast Type?

So i have a class, Texture2DProcessor, that inherits IXmlSerializable and Implicitly casts to and from Texture2D
public static implicit operator Texture2D(Texture2DProcessor o)
{
return o.Data;
}
public static implicit operator Texture2DProcessor(Texture2D o)
{
return o == null ? null : new Texture2DProcessor(o);
}
I then have a struct, GunProperties, that contains a Texture2D property with an XmlElement attribute with the type set to Texture2DProcessor
Texture2D _sideSprite;
[XmlElement(typeof(Texture2DProcessor))]
public Texture2D SideSprite
{
get { return _sideSprite; }
set { _sideSprite = value; }
}
I get the following runtime error
Cannot serialize member '...GunProperties.SideSprite' of type 'Microsoft.Xna.Framework.Graphics.Texture2D'
why is XmlSerializer not using Texture2DProcessor to read and write the Xml data?
Also I know that my ReadXml and WriteXml methods work because this works fine and i am able to use the texture.
Texture2D texture;
XmlSerializer serializer = new XmlSerializer(typeof(Texture2DProcessor));
serializer.Deserialize(new FileStream(path, FileMode.Open), texture);
The reason I'm going through this trouble is I'm using monogame and the content pipeline is pretty messed up especially for custom types and other than this issue i have it all working.
It looks like this may be a Mono XmlSerializer limitation. I have a small test app which works under .NET, but not under Mono 3.0.6.
But this looks fairly simple to work around:
[XmlIgnore]
public Texture2D SideSprite { get; set; }
[XmlElement("SideSprite")]
[EditorBrowsable(EditorBrowsableState.Never)]
public Texture2DProcessor SideSpriteProcessor
{
get { return SideSprite; }
set { SideSprite = value; }
}
We have the same issue in my Noda Time project, as XML serialization doesn't mix well with immutable types. I've given pretty much the same advice there.
EDIT: Okay, so here's some sample code which does work:
using System;
using System.IO;
using System.Xml;
using System.Xml.Schema;
using System.Xml.Serialization;
public class Person
{
public string Name { get; set; }
}
public class PersonWrapper : IXmlSerializable
{
public Person Person { get; set; }
public static implicit operator Person(PersonWrapper wrapper)
{
return wrapper == null ? null : wrapper.Person;
}
public static implicit operator PersonWrapper(Person person)
{
return new PersonWrapper { Person = person };
}
public XmlSchema GetSchema()
{
return null;
}
public void ReadXml(XmlReader reader)
{
string name = reader.ReadString();
reader.ReadEndElement();
Person = new Person { Name = name };
}
public void WriteXml(XmlWriter writer)
{
writer.WriteString(Person.Name);
}
}
public class Company
{
[XmlElement(typeof(PersonWrapper))]
public Person Director { get; set; }
[XmlElement(typeof(PersonWrapper))]
public Person Manager { get; set; }
}
class Test
{
static void Main()
{
var serializer = new XmlSerializer(typeof(Company));
var original = new Company
{
Director = new Person { Name = "Holly" },
Manager = new Person { Name = "Jon" }
};
var writer = new StringWriter();
serializer.Serialize(writer, original);
Console.WriteLine("XML:");
Console.WriteLine(writer.ToString());
var reader = new StringReader(writer.ToString());
var deserialized = (Company) serializer.Deserialize(reader);
Console.WriteLine("Deserialized:");
Console.WriteLine("Director: {0}", deserialized.Director.Name);
Console.WriteLine("Manager: {0}", deserialized.Manager.Name);
}
}

Categories