Ignore null values - Serialization - c#

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(); } }

Related

Xmlserializer to C# object, store original XML element

Is it possible to store the original XML element in a C# class, for example?
Original XML:
<data someattributea="" someattributeb="" someattributec="" />
C#
using System;
using System.Xml.Serialization;
using System.Collections.Generic;
namespace Xml2CSharp
{
[XmlRoot(ElementName="data")]
public class Data {
[XmlAttribute(AttributeName="someattributea")]
public string Someattributea { get; set; }
[XmlAttribute(AttributeName="someattributeb")]
public string Someattributeb { get; set; }
[XmlAttribute(AttributeName="someattributec")]
public string Someattributec { get; set; }
public sourceXML { get; set; } //this would return <data someattributea="" someattributeb="" someattributec="" />
}
}
I understand I could deserialize the class again but some XML objects are unknown at design time.
If you really need to capture everything about the <data /> element including the element name and namespace itself into a string literal, you will need to implement IXmlSerializable and serialize your Data type manually. For instance, here is a prototype implementation:
[XmlRoot(ElementName = ElementName)]
public class Data : IXmlSerializable
{
public const string ElementName = "data";
XElement element = new XElement((XName)ElementName);
public string Someattributea
{
get { return (string)element.Attribute("someattributea"); }
set { element.SetAttribute("someattributea", value); }
}
public string Someattributeb
{
get { return (string)element.Attribute("someattributeb"); }
set { element.SetAttribute("someattributeb", value); }
}
public string Someattributec
{
get { return (string)element.Attribute("someattributec"); }
set { element.SetAttribute("someattributec", value); }
}
public string SourceXML
{
get
{
return element.ToString();
}
set
{
if (value == null)
throw new ArgumentNullException();
element = XElement.Parse(value);
}
}
#region IXmlSerializable Members
public XmlSchema GetSchema() { return null; }
public void ReadXml(XmlReader reader)
{
reader.MoveToContent();
element = (XElement)XNode.ReadFrom(reader);
}
public void WriteXml(XmlWriter writer)
{
foreach (var attr in element.Attributes())
writer.WriteAttributeString(attr.Name.LocalName, attr.Name.NamespaceName, attr.Value);
foreach (var child in element.Elements())
child.WriteTo(writer);
}
#endregion
}
public static class XElementExtensions
{
public static void SetAttribute(this XElement element, XName attributeName, string value)
{
var attr = element.Attribute(attributeName);
if (value == null)
{
if (attr != null)
attr.Remove();
}
else
{
if (attr == null)
element.Add(new XAttribute(attributeName, value));
else
attr.Value = value;
}
}
}
Notes:
When reading, the complete XML is loaded into an XElement member which can be queried using LINQ to XML. As a result the original formatting may get lost.
IXmlSerializable is tricky to implement correctly. See Proper way to implement IXmlSerializable? and How to Implement IXmlSerializable Correctly for some tips on how to do it.
The known properties Someattributea, Someattributeb and Someattributec now become surrogate lookups into the underlying XElement.
Working .Net fiddle here.
If, on the other hand, you only need to capture unknown elements, attributes and text content, you can use [XmlAnyAttribute], [XmlAnyElement] and [XmlText] (the first two of which are suggested in this answer to XmlSerializer equivalent of IExtensibleDataObject by Marc Gravell). This approach results in a much simpler version of Data:
[XmlRoot(ElementName = "data")]
public class Data
{
[XmlAttribute(AttributeName = "someattributea")]
public string Someattributea { get; set; }
[XmlAttribute(AttributeName = "someattributeb")]
public string Someattributeb { get; set; }
[XmlAttribute(AttributeName = "someattributec")]
public string Someattributec { get; set; }
[XmlAnyAttribute]
public XmlAttribute[] Attributes { get; set; }
[XmlAnyElement]
[XmlText] // Captures mixed content at the root level as well as child elements.
public XmlNode[] ChildNodes { get; set; }
}
Working .Net fiddle #2 here.

Serialize / Deserialize XML Derived From Treenode

I have two classes, "company" derived from Treenode, and "document".
[Serializable]
[XmlRoot("Company")]
public class Company : TreeNode, IXmlSerializable
{
private string _x;
private string _y;
public Company() { }
[XmlElement("X")]
public string X { get; set; }
[XmlElement("Y")]
public string Y { get; set; }
public System.Xml.Schema.XmlSchema GetSchema()
{
return null;
}
public void ReadXml(XmlReader reader)
{
if (reader.MoveToContent() == XmlNodeType.Element && reader.LocalName == "Company")
{
x = reader["X"].ToString;
y = reader["Y"].ToString;
}
}
public void WriteXml(XmlWriter writer)
{
writer.WriteElementString("X", this.X.ToString());
writer.WriteElementString("Y", this.Y.ToString());
}
}
public class Document
{
private int _id;
private string _name;
private Company _company;
public Document() { }
[XmlElement("ID")]
public int ID { get; set; }
[XmlElement("Name")]
public string Name { get; set; }
[XmlElement("Company")]
public Company Comp { get; set; }
}
When I try to serialize document, then save to file, its work. But when I deserialize, reader parameter always null. Any solutions ?
This is my code for deserialize. "sr" is variable that hold xml text.
var sr = new StreamReader(ms);
var myStr = sr.ReadToEnd();
XmlSerializer serializer = new XmlSerializer(typeof(List<Document>));
using (TextReader tr = new StringReader(myStr))
{
List<Document> docu = (List<Document>)serializer.Deserialize(tr);
}
I try to implement ISerialization and debug but never fired, and try to overide serialize and deserialize method, and no luck.
I am using .NET Framework 3.5
As explained in this article, implementing IXmlSerializable correctly is in fact quite tricky. Your implementation of ReadXml() appears to violate the requirement that it consume the wrapper element itself as well as all the contents, like so:
public void ReadXml(System.Xml.XmlReader reader)
{
reader.MoveToContent();
// Read attributes
Boolean isEmptyElement = reader.IsEmptyElement; // (1)
reader.ReadStartElement();
if (!isEmptyElement) // (1)
{
// Read Child elements X and Y
// Consume the end of the wrapper element
reader.ReadEndElement();
}
}
Furthermore, reader["X"] returns the value of the XML attribute named "X", as explained in the docs. In your WriteXml() you wrote the values of X and Y as nested XML elements. This explains the NullReferenceException. You need to fix your read and write methods to be consistent.
However, I'd suggest an alternative to implementing IXmlSerializable, which is to introduce a surrogate type for your Company. First, extract all the non-TreeNode properties of Company into an interface:
public interface ICompany
{
string X { get; set; }
string Y { get; set; }
}
public class Company : TreeNode, ICompany
{
public Company() { }
public string X { get; set; }
public string Y { get; set; }
}
This is optional but makes the code clearer. Next, introduce a surrogate POCO that implements the same interface but does not inherit from TreeNode:
public class CompanySurrogate : ICompany
{
public string X { get; set; }
public string Y { get; set; }
public static implicit operator CompanySurrogate(Company company)
{
if (company == null)
return null;
// For more complex types, use AutoMapper
return new CompanySurrogate { X = company.X, Y = company.Y };
}
public static implicit operator Company(CompanySurrogate surrogate)
{
if (surrogate == null)
return null;
// For more complex types, use AutoMapper
return new Company { X = surrogate.X, Y = surrogate.Y };
}
}
Notice that the surrogate can be implicitly converted to your original Company type? Now you can use the surrogate in XML serialization by setting the XmlElementAttribute.Type attribute property to be that of the surrogate:
public class Document
{
public Document() { }
[XmlElement("ID")]
public int ID { get; set; }
[XmlElement("Name")]
public string Name { get; set; }
[XmlElement("Company", Type = typeof(CompanySurrogate))]
public Company Comp { get; set; }
}
This avoids all possibilities of error in implementing IXmlSerializable. Given the following input list:
var list = new List<Document>
{
new Document { Name = "my name", ID = 101, Comp = new Company { X = "foo", Y = "bar", NodeFont = new System.Drawing.Font("Arial", 10) } },
new Document { Name = "2nd name", ID = 222, Comp = new Company { X = "tlon", Y = "ukbar" } },
};
The following XML can will be generated, and can be deserialized successfully:
<ArrayOfDocument xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Document>
<ID>101</ID>
<Name>my name</Name>
<Company>
<X>foo</X>
<Y>bar</Y>
</Company>
</Document>
<Document>
<ID>222</ID>
<Name>2nd name</Name>
<Company>
<X>tlon</X>
<Y>ukbar</Y>
</Company>
</Document>
</ArrayOfDocument>
That being said, I don't really recommend this design. Your UI should present your data model, it should not be your data model. See for instance How does one implement UI independent applications?. Replacing Company with ICompany whenever possible could be a first step to changing your design; you may find it becomes easier to replace your existing architecture with a TreeNode that looks like this:
public class CompanyNode : TreeNode
{
public ICompany Company { get; set; }
}

How to serialize class as attribute?

I am trying to serialize a class as an attribute. Here's the use case: I need to store a large number of engineering parameters in an xml config file. Scientific notation is involved, and it is required by the customer that the entered values remain in the exact form the user entered them. For example, if someone enters "5.3e-1" then it must remain like that, and cannot be converted into, say, "0.53". To this end I've created a Params class that stores both the entered string and double values (actually storing the double values is only for processing efficiency later). Now here's the trick: I only want the string value to be serialized, and I want that to serialize as an attribute.
For example, if an object contains two parameters, A and B, where the string values are A.stringVal = "1.2e5" and B.stringVal = "2.0" then I would like:
public class MyObject
{
[XmlAttribute]
public MyParam A { get; set; }
[XmlAttribute]
public MyParam B { get; set; }
...more stuff...
}
to serialize to:
<myObject A="1.2e5" B="2.0">
more stuff...
</myObject>
My question is very similar to one asked here. Unlike him I am fine with implementing IXmlSerializable (and would prefer to), I just can't make it work. When I try to serialize it I get a cryptic exception saying, "There was error reflection type." What am I doing wrong?
public class MyParam : IXmlSerializable
{
string name;
string stringVal;
double doubleVal;
public string Val
{
get
{
return stringVal;
}
set
{
stringVal = value;
doubleVal = double.Parse(value);
}
}
public double DoubleVal
{
get
{
return doubleVal;
}
set
{
doubleVal = value;
stringVal = value.ToString();
}
}
public MyParam(string name)
{
this.name = name;
this.stringVal = string.Empty;
this.doubleVal = double.NaN;
}
public System.Xml.Schema.XmlSchema GetSchema()
{
return null;
}
public void ReadXml(System.Xml.XmlReader reader)
{
throw new NotImplementedException();
}
public void WriteXml(System.Xml.XmlWriter writer)
{
writer.WriteAttributeString(name, stringVal);
}
}
To get what you want you need to control the serialization on the container that holds the properties, not the property itself. You can still encapsulate the serialization code in the property however, see below.
public class MyObject : IXmlSerializable
{
public MyParam A { get; set; }
public MyParam B { get; set; }
public XmlSchema GetSchema()
{
return null;
}
public void ReadXml(XmlReader reader)
{
throw new NotImplementedException();
}
public void WriteXml(XmlWriter writer)
{
//Call each properties "WriteAttribute" call.
A.WriteAttribute(writer);
B.WriteAttribute(writer);
}
}
public class MyParam
{
string name;
string stringVal;
double doubleVal;
public string Val
{
get
{
return stringVal;
}
set
{
stringVal = value;
doubleVal = double.Parse(value);
}
}
public double DoubleVal
{
get
{
return doubleVal;
}
set
{
doubleVal = value;
stringVal = value.ToString();
}
}
public MyParam(string name)
{
this.name = name;
this.stringVal = string.Empty;
this.doubleVal = double.NaN;
}
internal void WriteAttribute(XmlWriter writer)
{
writer.WriteAttributeString(name, stringVal);
}
}
class Program
{
static void Main(string[] args)
{
var test = new MyObject()
{
A = new MyParam("A"),
B = new MyParam("B"),
};
test.A.Val = "1.2e5";
test.B.Val = "2.0";
var ser = new XmlSerializer(typeof(MyObject));
var sb = new StringBuilder();
using (var stream = new StringWriter(sb))
{
ser.Serialize(stream, test);
}
Console.WriteLine(sb);
Console.ReadLine();
}
}
Outputs
<?xml version="1.0" encoding="utf-16"?>
<MyObject A="1.2e5" B="2.0" />
If you don't need the name of the property in the property itself you can modify the code to the following.
public class MyObject : IXmlSerializable
{
//....
public void WriteXml(XmlWriter writer)
{
//Call each properties "WriteAttribute" call.
A.WriteAttribute(writer, "A");
B.WriteAttribute(writer, "B");
}
}
public class MyParam
{
//...
public MyParam()
{
this.stringVal = string.Empty;
this.doubleVal = double.NaN;
}
internal void WriteAttribute(XmlWriter writer, string name)
{
writer.WriteAttributeString(name, stringVal);
}
}
After I've added the parameterless constructor, I got the following exception:
The element 'A' type ConsoleApplication1.MyParam can not be serialized. XmlAttribute / XmlText can not be used to encode types that implement IXmlSerializable.
After removing IXmlSerializable I got an exception XmlAttribute/XmlText cannot be used to encode complex types. And found this answer
and this.
So, I think, it is better to find another way to serialize attributes.

XmlSerializer putting element name inside a property when deserializing

I have an issue with the .NET XML Serializer where I have do different XML Elements with different names that map to the same type. Basically, the objects should be exactly the same, but I want them to have a string or enum or something that identifies which of the three possible element names were used. So here's an example:
<Body>
<MyTypeA>
<Foo>bar</Foo>
</MyTypeA>
<MyTypeB>
<Foo>bar</Foo>
</MyTypeB>
</Body>
Now, for the classes, MyTypeA and MyTypeB will both be the same type. For example:
public class Body {
public MyType MyTypeA { get; set; }
public MyType MyTypeB { get; set; }
}
public class MyType {
public string Foo { get; set; }
[XmlIgnore]
public MyTypeType { get; set; }
}
public enum MyTypeType
{
MyTypeA,
MyTypeB
}
When serializing it works fine, because I can always just ensure one way or another that the enum is set properly before serializing. But when deserializing it is not getting set and I'm not sure there's a way how.
For the record, I unfortunately don't get to set the schema, otherwise I would have built it in such a way that I didn't have this problem.
If i understood your question correctly, this might help you. Just write you XML file path on the 4th line and try it.
namespace ConsoleApplication1
{
class Program
{
//private const string xmlPath = #"C:\Users\Jumast\Desktop\StackOverflowQuestion.xml";
private const string xmlPath, // put the file path here
static Body makeBody()
{
var instance1 = new MyType() { Category = Category.MyTypeA, Foo = "bar" };
var instance2 = new MyType() { Category = Category.MyTypeB, Foo = "bar" };
return new Body(){Instance1 = instance1, Instance2 = instance2};
}
static void serializeBody(Body body, string path)
{
var ser = new DataContractSerializer(body.GetType(), body.GetType().Name, "");
using (var w = XmlWriter.Create(path, new XmlWriterSettings() { Indent = true }))
{
ser.WriteObject(w, body);
}
}
static Body deseerializeBody(string xmlPath)
{
Body deserializedBody;
var ser = new XmlSerializer(typeof(Body));
using (Stream stream = File.OpenRead(xmlPath))
{
deserializedBody = (Body)ser.Deserialize(stream);
}
return deserializedBody;
}
static void writeBodyToConsole(Body body)
{
Console.WriteLine("Instance1: " + body.Instance1);
Console.WriteLine("Instance2: " + body.Instance2);
Console.ReadKey();
}
static void Main(string[] args)
{
serializeBody(makeBody(), xmlPath);
writeBodyToConsole(deseerializeBody(xmlPath));
}
}
public class Body : IXmlSerializable
{
#region Properties
public MyType Instance1 { get; set; }
public MyType Instance2 { get; set; }
#endregion
#region IXmlSerializable
public System.Xml.Schema.XmlSchema GetSchema()
{
return null;
}
public void ReadXml(System.Xml.XmlReader reader)
{
reader.ReadStartElement();
Instance1 = new MyType(reader);
Instance2 = new MyType(reader);
reader.ReadEndElement();
}
public void WriteXml(System.Xml.XmlWriter writer)
{
Instance1.WriteXml(writer);
Instance2.WriteXml(writer);
}
#endregion
}
public class MyType : IXmlSerializable
{
#region Fields
private Category _category;
#endregion
#region Constructors
public MyType()
{
_category = Category.nil;
Foo = string.Empty;
}
public MyType(XmlReader reader) { ReadXml(reader);}
#endregion
#region Methods
public override string ToString()
{
var sb = new StringBuilder();
sb.Append(string.Format("Foo = {0}", Foo));
sb.Append(" , ");
sb.Append(string.Format("Category = {0}", Category));
return sb.ToString();
}
#endregion
#region Properties
public string Foo { get; set; }
public Category Category
{
get { return this._category; }
set { this._category = value; }
}
#endregion
#region IXmlSerializable
public System.Xml.Schema.XmlSchema GetSchema()
{
return null;
}
public void ReadXml(System.Xml.XmlReader reader)
{
Enum.TryParse(reader.Name, out _category);
reader.Read();
Foo = reader.ReadElementContentAsString("Foo", "");
reader.ReadEndElement();
}
public void WriteXml(System.Xml.XmlWriter writer)
{
writer.WriteStartElement(this.Category.ToString(), "");
writer.WriteElementString("Foo", Foo);
writer.WriteEndElement();
}
#endregion
}
public enum Category
{
MyTypeA,
MyTypeB,
nil
}
}

Deserialise missing XML attribute to Nullable type

There are various solutions for serialising Nullable types on SO but what i need is a solution for deserialising to a nullable type. The Specified and ShouldSerailise techniques dont seem to apply to deserialising.
So if my XML document is missing an attribute I want the int in the class to be null not 0.
Unfortunately you cant serialise directly to a nullable int because the serializes throws a reflection error.
So in the example below i want result2.SomeInt to be null and result1.SomeInt = 12
class TestProgram
{
public static void Main(string[] args)
{
XmlSerializer deserializer = new XmlSerializer(typeof(Result));
Stream xmlStream = new MemoryStream(Encoding.ASCII.GetBytes(docWithVal().InnerXml));
var result1 = (Result)deserializer.Deserialize(xmlStream);
Stream xmlStream2 = new MemoryStream(Encoding.ASCII.GetBytes(docWithoutVal().InnerXml));
var result2 = (Result)deserializer.Deserialize(xmlStream2);
}
public static XmlDocument docWithoutVal()
{
var doc = new XmlDocument();
doc.LoadXml(#"<Result/>");
return doc;
}
public static XmlDocument docWithVal()
{
var doc = new XmlDocument();
doc.LoadXml(#"<Result SomeInt = ""12""/>");
return doc;
}
}
[Serializable]
public class Result
{
[XmlAttribute]
public int? SomeInt { get; set; }
}
You can infact use the Specified techniques after deserialization. Modify your Result class this way:
[Serializable]
public class Result
{
[XmlAttribute]
public int SomeInt { get; set; }
[XmlIgnore]
public bool SomeIntSpecified;
}
Now use this logic after deserialization for Nullable types:
var value = SomeIntSpecified ? SomeInt : null;
OR you can also implement IXmlSerializable in your Result class:
[Serializable]
public class Result : IXmlSerializable
{
public int? SomeInt { get; set; }
#region IXmlSerializable members
public void WriteXml(XmlWriter writer)
{
if (SomeInt != null) { writer.WriteValue(writer); }
}
public void ReadXml(XmlReader reader)
{
int result;
if (int.TryParse(reader.GetAttribute("SomeInt"), out result))
SomeInt = result;
}
public XmlSchema GetSchema()
{
return (null);
}
#endregion
}
Reference: Using XmlSerializer to deserialize into a Nullable

Categories