I have a datacontract like below:
[DataContract]
class Person
{
private string m_name;
[DataMember]
public string Name
{ get {return m_name;}
set {m_name = value;}
}
}
When xml serializer serializes this object, it is generating xml with private members like
<person><m_name>john</m_name></person>
How can I force the serializer to serialize only public properties.
thanks in advance.
It’s strange that your private member writes to xml. I’ve tried to imitate your situation and serializer wrote only public field:
[DataContract]
public class Person
{
private string m_name;
[DataMember]
public string Name
{
get { return m_name; }
set { m_name = value; }
}
}
private void Form1_Load(object sender, EventArgs e)
{
var person = new Person() {Name = "john"};
var xs = new XmlSerializer(typeof(Person));
var sw = new StreamWriter(#"c:\person.xml");
xs.Serialize(sw, person);
}
You can also read this: http://msdn.microsoft.com/en-us/library/83y7df3e%28VS.71%29.aspx
I did something similar to Andark's answer, except I used the DataContractSerializer instead of XmlSerializer. This was done in VS 2012 targeting .NET 4.5.
Here's the test code:
using Sytem;
using System.IO;
using System.Runtime.Serialization;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
Person myPerson = new Person() { Name = "Tim" };
using (FileStream writer = new FileStream("Person.xml", FileMode.Create, FileAccess.Write))
{
DataContractSerializer dcs = new DataContractSerializer(typeof(Person));
dcs.WriteObject(writer, myPerson);
}
}
}
[DataContract]
class Person
{
private string m_name;
public string Name
{
get
{
return m_name;
}
set
{
m_name = value;
}
}
}
}
When I run this, I get the following XML:
<Person xmlns="http://schemas.datacontract.org/2004/07/ConsoleApplication1"
xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<Name>Tim</Name>
<Person>
Which is what is expected.
It's important to note that the default serializer for DataContract is the DataContractSerializer, not XmlSerializer, and there are some differences. Only members that are marked with [DataMember] should be serialized, and the access level (private, public, etc) is irrelevant - if you mark a private field or member with [DataMember], it will be serialized.
Related
I've got a simple .Net framework C# console app that serializes a a class that is of a derived type, where a property is also of a derived type.
The derived classes have names that are the same as the base class, but are in a different namespace to prevent them from clashing. It seems though that the reflection the XmlSerializer uses does not work with this too well. Maybe there is some way of wrangling the attributes that I can still end up with the base class using pretty names (as it will be a DLL interface when used) and the XML also using pretty names (as it will be human editable)... pretty names for the derived classes are not required (though would be a bonus).
The XML would hopefully look like:
<?xml version="1.0" encoding="utf-8"?>
<Person xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Details>
<Detail>
<Description>bald</Description>
</Detail>
<Detail>
<Description>red tie</Description>
</Detail>
</Details>
</Person>
But the closest I can get without exceptions is where the <Detail> elements are
<Detail xsi:type="DerivedDetail"> ... </Detail>
Having to add this xs:type attribute is not the best for human-editable XML.
This is achieved with the below C# code. If I remove the marked XmlType attribute then the element should serialize without the xsi:type attribute, but instead I get an exception:
InvalidOperationException: Types 'Test.Detail' and 'Test.Xml.Detail' both use the XML type name, 'Detail', from namespace ''. Use XML attributes to specify a unique XML name and/or namespace for the type.
I tried marking the derived Xml.Detail class as an anonymous XML type but then the exception reads:
InvalidOperationException: Cannot include anonymous type 'Test.Xml.Detail'.
I have read many similar questions but have not encountered anything that solves this just yet.
In this code below Person is an abstract class that has a property that is an array of the abstract type Detail. These types are derived by Xml.Person and Xml.Detail respectively. The program creates a test Xml.Person object and attempts to serialize it:
using System;
using System.Text;
using System.IO;
using System.Xml;
using System.Xml.Serialization;
namespace Test
{
class Program
{
static void Main(string[] args)
{
// Create test details array
var TestDetails = new Xml.Detail[]
{
new Xml.Detail
{
Description = "bald"
},
new Xml.Detail
{
Description = "red tie"
}
};
// create test person object that holds details array
var TestBar = new Xml.Person()
{
Details = TestDetails
};
// serialize the person object
var s = new Xml.Serializer();
var TestOutput = s.Serialize(TestBar);
Console.WriteLine(TestOutput);
}
}
// base classes
public abstract class Person
{
public abstract Detail[] Details { get; set; }
}
public abstract class Detail
{
public abstract string Description { get; set; }
}
namespace Xml
{
// derived classes
[Serializable]
[XmlType(AnonymousType = true)]
[XmlRoot(IsNullable = false)]
public class Person : Test.Person
{
[XmlArrayItem("Detail", typeof(Detail))]
[XmlArray(IsNullable = false)]
public override Test.Detail[] Details { get; set; }
}
// This attribute makes serialization work but also adds the xsi:type attribute
[XmlType("DerivedDetail")]
[Serializable]
public class Detail : Test.Detail
{
public override string Description { get; set; }
}
// class that does serializing work
public class Serializer
{
private static XmlSerializer PersonSerializer =
new XmlSerializer(typeof(Person), new Type[] { typeof(Detail) });
public string Serialize(Test.Person person)
{
string Output = null;
var Stream = new MemoryStream();
var Encoding = new UTF8Encoding(false, true);
using (var Writer = new XmlTextWriter(Stream, Encoding))
{
Writer.Formatting = Formatting.Indented;
PersonSerializer.Serialize(Writer, person);
Output = Encoding.GetString(Stream.ToArray());
}
Stream.Dispose();
return Output;
}
}
}
}
Not sure why you're using base classes instead of interfaces when you don't have any member fields. Regardless, I assumed you wanted Xml.Person to be a concrete instantiation of abstract Person or any classes derived from abstract Person without decorating abstract Person with XML attributes. I accomplished this by forcing abstract Person to become a concrete instantiation of Xml.Person before serializing it. Please replace XmlSerializationProject with Test.
using System;
using System.IO;
using System.Text;
using System.Xml;
using System.Xml.Serialization;
namespace XmlSerializationProject
{
class Program
{
static void Main(string[] args)
{
// Create test details array
var TestDetails = new Xml.Detail[]
{
new Xml.Detail
{
Description = "bald"
},
new Xml.Detail
{
Description = "red tie"
}
};
// create test person object that holds details array
var TestBar = new Xml.Person()
{
Details = TestDetails
};
// serialize the person object
var s = new Xml.Serializer();
var TestOutput = s.Serialize(TestBar);
Console.WriteLine(TestOutput);
Console.ReadKey();
}
}
// base classes
public abstract class Person
{
public abstract Detail[] Details { get; set; }
}
public abstract class Detail
{
public abstract string Description { get; set; }
}
namespace Xml
{
[Serializable]
[XmlType(AnonymousType = true)]
[XmlRoot(IsNullable = false)]
public class Person : XmlSerializationProject.Person
{
public Person()
{ }
public Person(XmlSerializationProject.Person person)
{
// Deep copy
if (person.Details == null) return;
this.Details = new Detail[person.Details.Length];
for (int i = 0; i < person.Details.Length; i++)
{
this.Details[i] = new Detail { Description = person.Details[i].Description };
}
}
[XmlArray(ElementName = "Details")]
[XmlArrayItem("Detail", typeof(Detail))]
[XmlArrayItem("ODetail", typeof(XmlSerializationProject.Detail))]
public override XmlSerializationProject.Detail[] Details
{
get;
set;
}
}
[Serializable]
public class Detail : XmlSerializationProject.Detail
{
public override string Description { get; set; }
}
// class that does serializing work
public class Serializer
{
private static readonly XmlSerializer PersonSerializer;
private static Serializer()
{
var xmlAttributeOverrides = new XmlAttributeOverrides();
// Change original "Detail" class's element name to "AbstractDetail"
var xmlAttributesOriginalDetail = new XmlAttributes();
xmlAttributesOriginalDetail.XmlType = new XmlTypeAttribute() { TypeName = "AbstractDetail" };
xmlAttributeOverrides.Add(typeof(XmlSerializationProject.Detail), xmlAttributesOriginalDetail);
// Ignore Person.Details array
var xmlAttributesOriginalDetailsArray = new XmlAttributes();
xmlAttributesOriginalDetailsArray.XmlIgnore = true;
xmlAttributeOverrides.Add(typeof(XmlSerializationProject.Person), "Details", xmlAttributesOriginalDetailsArray);
PersonSerializer = new XmlSerializer(
typeof(Person), xmlAttributeOverrides, new Type[] { typeof(Detail) }, new XmlRootAttribute(), "default");
}
public string Serialize(XmlSerializationProject.Person person)
{
return Serialize(new Person(person));
}
public string Serialize(Person person)
{
string Output = null;
var Stream = new MemoryStream();
var Encoding = new UTF8Encoding(false, true);
using (var Writer = new XmlTextWriter(Stream, Encoding))
{
Writer.Formatting = Formatting.Indented;
PersonSerializer.Serialize(Writer, person);
Output = Encoding.GetString(Stream.ToArray());
}
Stream.Dispose();
return Output;
}
}
}
}
my class code as
public class OpenShipments
{
private string _xmlns = "";
private OpenShipment _OpenShipment = null;
[XmlAttribute("xmlns")]
public string xmlns
{
get { return _xmlns; }
set { _xmlns = "x-schema:" + value; }
}
[XmlElement("OpenShipment")]
public OpenShipment OpenShipment
{
get { return _OpenShipment; }
set { _OpenShipment = value; }
}
public OpenShipments()
{
_OpenShipment = new OpenShipment();
}
}
I include a property called public string xmlns which will have namespace after serializing the code, but no namespace is added when we see the xml.
In my case i need to add namespace dynamically to OpenShipments element whose value will be dynamically set. In my case namespace will look like xmlns="x-schema:C:\UPSLabel\OpenShipments.xdr" and sometime will look like xmlns="x-schema:d:\UPSLabel\OpenShipments.xdr".
So the namespace value x-schema:d:\UPSLabel\OpenShipments.xdr will differ based on condition. I need advice how to add namespace dynamically to handle the situation.
You can do it this way:
when serialzing your class
System.Xml.Serialization.XmlSerializer xs = new XmlSerializer(<my class>.GetType());
XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
if(<condition>)
{
ns.Add(string.Empty, #"x-schema:C:\UPSLabel\OpenShipments.xdr");
}
else
{
ns.Add(string.Empty, #"x-schema:D:\UPSLabel\OpenShipments.xdr");
}
xs.Serialize(<stream>, <your class instance>,ns);
I have the following class structure which I want to serialize:
[XmlRoot("SomeClass")]
public class SomeClass
{
private BaseClass[] _itemArray;
public BaseClass[] ItemArray
{
get { return _itemArray; }
set { _itemArray = value; }
}
public PPSPStatReportMessage()
: base()
{
}
}
public class SomeOtherClass
{
private int _property5;
[XmlAttribute("Property5")]
public int Property5
{
get { return _property5; }
set { _property5 = value; }
}
private string _property6;
[XmlText()]
public string Property6
{
get { return _property6; }
set { _property6 = value; }
}
public SomeOtherClass()
{
}
}
[XmlInclude(typeof(DerivedClass1))]
[XmlInclude(typeof(DerivedClass2))]
[XmlRoot("BaseClass")]
[XmlType("")]
public class BaseClass
{
private string _property1;
[XmlAttribute("Property1")]
public string Property1
{
get { return _property1; }
set { _property1 = value; }
}
public SomeClass(string PropertyVal)
{
_property1 = PropertyVal;
}
}
[XmlRoot("BaseClass")]
[XmlTypeAttribute(Namespace = "")]
public class DerivedClass1 : BaseClass
{
private string _property2;
[XmlAttribute("Property2")]
public string Property2
{
get { return _property2; }
set { _property2 = value; }
}
private SomeOtherClass _property3;
[XmlElement("SomeOtherClass")]
public SomeOtherClass Property3
{
get { return _property3; }
set { _property3 = value; }
}
public DerivedClass()
: base("PropertyVal1")
{
}
}
[XmlRoot("BaseClass", Namespace = "")]
[XmlType("")]
public class DerivedClass2 : BaseClass
{
private Int64 _property4;
[XmlAttribute("Property4")]
public Int64 Property4
{
get { return _property4; }
set { _property4 = value; }
}
public DerivedClass2()
: base("PropertyVal2")
{
}
}
And this is the method I use to serialize SomeClass:
public static string SerializeXML(object Value, System.Type ObjectType)
{
XmlSerializer serializer = new XmlSerializer(ObjectType);
XmlSerializerNamespaces namespaceSerializer = new XmlSerializerNamespaces();
namespaceSerializer.Add("", "");
StringWriter ms = new StringWriter();
serializer.Serialize(ms, Value, namespaceSerializer);
return ms.ToString();
}
This method generates an XML structure that looks like the following:
<?xml version="1.0" encoding="utf-16"?>
<SomeClass>
<ItemArray>
<BaseClass d3p1:type="DerivedClass1" Property1="PropertyVal1" Property2="123" xmlns:d3p1="http://www.w3.org/2001/XMLSchema-instance">
<SomeOtherClass Property5="0" >STRING DATA</SomeOtherClass>
</BaseClass>
<BaseClass d3p1:type="DerivedClass2" Property="PropertyVal2" Property4="456" xmlns:d3p1="http://www.w3.org/2001/XMLSchema-instance" />
</ItemArray>
</SomeClass>
However, I need to omit the d3p1:type and xmlns:d3p1 attributes and generate an XML structure that looks like this:
<?xml version="1.0" encoding="utf-16"?>
<SomeClass>
<ItemArray>
<BaseClass Property1="PropertyVal1" Property2="123">
<SomeOtherClass Property5="0" >STRING DATA</SomeOtherClass>
</BaseClass>
<BaseClass Property="PropertyVal2" Property4="456" />
</ItemArray>
</SomeClass>
As you can see in the code, I've tried to use XmlType and XmlTypeAttribute, but without success.
Any advice on how can I generate the XML structure as described above (without d3p1:type and xmlns:d3p1 attributes)?
Do you need the subclass elements to be called "BaseClass" - or would the name of the derived class do?
I am serializing similar subclass structures, and also wanted to get rid of the "d3p1:type" and "xmlns:d3p1" tags - instead replacing the "BaseClass" tags with derived class tags. So it is possible to generate xml for your example:
<ItemArray>
<DerivedClass1 Property1="PropertyVal1" Property2="123">
....
</DerivedClass1>
You're using an XmlInclude attribute on your BaseClass to let the serliazer know which derived classes to expect. Instead, you can tell the serializer about the expected subtypes on each element:
public class SomeClass
{
private BaseClass[] _itemArray;
[XmlElement(typeof(DerivedClass1))]
[XmlElement(typeof(DerivedClass2))]
public BaseClass[] ItemArray
{
get { return _itemArray; }
set { _itemArray = value; }
}
Eliminating the "type" attributes. Deserialiazation will also work fine.
You cannot accomplish what you want directly.
If the type being serialized isn't the exact type contained, .NET has to track what the type really is to allow proper deserialization.
To accomplish what you want - use RegEx.Replace() or other post-processing to replace the namespaces.
(e.g. serializing Object which is really a String will cause this problem).
XmlWriterSettings settings = new XmlWriterSettings();
settings.OmitXmlDeclaration = true;
settings.Indent = true;
using (XmlWriter writer = XmlWriter.Create(file, settings))
{
XmlSerializer serializer = new XmlSerializer(source.GetType());
XmlSerializerNamespaces namespaces = new XmlSerializerNamespaces();
namespaces.Add(string.Empty, string.Empty);
// ...
}
If I create a class in C#, how can I serialize/deserialize it to a file? Is this somethat that can be done using built in functionality or is it custom code?
XmlSerializer; note that the exact xml names can be controlled through various attributes, but all you really need is:
a public type
with a default constructor
and public read/write members (ideally properties)
Example:
using System;
using System.Xml;
using System.Xml.Serialization;
public class Person {
public string Name { get; set; }
}
static class Program {
static void Main() {
Person person = new Person { Name = "Fred"};
XmlSerializer ser = new XmlSerializer(typeof(Person));
// write
using (XmlWriter xw = XmlWriter.Create("file.xml")) {
ser.Serialize(xw, person);
}
// read
using (XmlReader xr = XmlReader.Create("file.xml")) {
Person clone = (Person) ser.Deserialize(xr);
Console.WriteLine(clone.Name);
}
}
}
You need to use class XmlSerializer. Main methods are Serialize and Deserialize. They accept streams, text readers\writers and other classes.
Code sample:
public class Program
{
public class MyClass
{
public string Name { get; set; }
}
static void Main(string[] args)
{
var myObj = new MyClass { Name = "My name" };
var fileName = "data.xml";
var serializer = new XmlSerializer(typeof(MyClass));
using (var output = new XmlTextWriter(fileName, Encoding.UTF8))
serializer.Serialize(output, myObj);
using (var input = new StreamReader(fileName))
{
var deserialized = (MyClass)serializer.Deserialize(input);
Console.WriteLine(deserialized.Name);
}
Console.WriteLine("Press ENTER to finish");
Console.ReadLine();
}
}
Can I make XmlSerializer ignore the namespace (xmlns attribute) on deserialization so that it doesn't matter if the attribute is added or not or even if the attribute is bogus? I know that the source will always be trusted so I don't care about the xmlns attribute.
Yes, you can tell the XmlSerializer to ignore namespaces during de-serialization.
Define an XmlTextReader that ignores namespaces. Like so:
// helper class to ignore namespaces when de-serializing
public class NamespaceIgnorantXmlTextReader : XmlTextReader
{
public NamespaceIgnorantXmlTextReader(System.IO.TextReader reader): base(reader) { }
public override string NamespaceURI
{
get { return ""; }
}
}
// helper class to omit XML decl at start of document when serializing
public class XTWFND : XmlTextWriter {
public XTWFND (System.IO.TextWriter w) : base(w) { Formatting= System.Xml.Formatting.Indented;}
public override void WriteStartDocument () { }
}
Here's an example of how you would de-serialize using that TextReader:
public class MyType1
{
public string Label
{
set { _Label= value; }
get { return _Label; }
}
private int _Epoch;
public int Epoch
{
set { _Epoch= value; }
get { return _Epoch; }
}
}
String RawXml_WithNamespaces = #"
<MyType1 xmlns='urn:booboo-dee-doo'>
<Label>This document has namespaces on its elements</Label>
<Epoch xmlns='urn:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'>0</Epoch>
</MyType1>";
System.IO.StringReader sr;
sr= new System.IO.StringReader(RawXml_WithNamespaces);
var s1 = new XmlSerializer(typeof(MyType1));
var o1= (MyType1) s1.Deserialize(new NamespaceIgnorantXmlTextReader(sr));
System.Console.WriteLine("\n\nDe-serialized, then serialized again:\n");
XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
ns.Add("urn", "booboo-dee-doo");
s1.Serialize(new XTWFND(System.Console.Out), o1, ns);
Console.WriteLine("\n\n");
The result is like so:
<MyType1>
<Label>This document has namespaces on its elements</Label>
<Epoch>0</Epoch>
</MyType1>
If you expect no namespace, but the input has namespaces, then you can set
Namespaces = false
on your XmlTextReader.
Exdended Wolfgang Grinfeld answer (w/o exception handling):
public static Message Convert(XmlDocument doc)
{
Message obj;
using (TextReader textReader = new StringReader(doc.OuterXml))
{
using (XmlTextReader reader = new XmlTextReader(textReader))
{
reader.Namespaces = false;
XmlSerializer serializer = new XmlSerializer(typeof(Message));
obj = (Message)serializer.Deserialize(reader);
}
}
return obj;
}
Solved this by using XmlSerializer Deserialize to read from xml instead from stream. This way before xml is Deserialized, using Regex to remove xsi:type from the xml. Was doing this is Portable Class Library for Cross Platform, so did not had many other options :(. After this the deserialization seems to work fine.
Following code can help,
public static TClass Deserialize<TClass>(string xml) where TClass : class, new()
{
var tClass = new TClass();
xml = RemoveTypeTagFromXml(xml);
var xmlSerializer = new XmlSerializer(typeof(TClass));
using (TextReader textReader = new StringReader(xml))
{
tClass = (TClass)xmlSerializer.Deserialize(textReader);
}
return tClass;
}
public static string RemoveTypeTagFromXml(string xml)
{
if (!string.IsNullOrEmpty(xml) && xml.Contains("xsi:type"))
{
xml = Regex.Replace(xml, #"\s+xsi:type=""\w+""", "");
}
return xml;
}
Why try to make the XmlSerializer forget how XML works? It's a fact of XML that two elements with the same name but different namespaces are different elements.
If you want to process XML that has no namespaces, then you should pre-process it to remove the namespaces, and then pass it to the serializer.