C# deserialize XML - c#

I have problem with deserialize document to object using XmlSerializer class.
Code my function for deserialize:
static public TYPE xmlToObject<TYPE>( string xmlDoc ) {
MemoryStream stream = new MemoryStream();
byte[] xmlObject = ASCIIEncoding.ASCII.GetBytes( xmlDoc );
stream.Write( xmlObject, 0, xmlObject.Length );
stream.Position = 0;
TYPE message;
XmlSerializer xmlSerializer = new XmlSerializer( typeof( TYPE ) );
try {
message = (TYPE)xmlSerializer.Deserialize( stream );
} catch ( Exception e ) {
message = default( TYPE );
} finally {
stream.Close();
}
return message;
}
And I have class:
public class Test {
public int a;
public int b;
}
And deserialize:
string text = File.ReadAllText( "blue1.xml" );
Test a = XmlInterpreter.xmlToObject<Test>( text );
Ok, when I read file like this:
<?xml version="1.0" encoding="UTF-8"?>
<Test>
<a>2</a>
<b>5</b>
</Test>
everything is OK. But like this:
<?xml version="1.0" encoding="UTF-8"?>
<Test>
<a>2</a>
<b></b>
</Test>
is wrong because
<b></b>
is empty and conversion into int is impossible.
How can I solve this? For example I want in this context that b will be not declared.
What when my class is:
public class Test {
public enum Pro {
VALUE1,
VALUE2
}
public Pro p1;
}
And I want accept xmlDocument, where field p1 is empty.

I expect that first example is just some type because it has empty b as well. First of all not every XML can be deserialized to object. Especially empty elements are dangerous and should not be used. If you want to express that b is not defined then do not include it in XML file:
<?xml version="1.0" encoding="UTF-8"?>
<Test>
<a>2</a>
</Test>
or make your b property nullable:
public class Test {
public int a;
public int? b;
}
and define XML as:
<?xml version="1.0" encoding="UTF-8"?>
<Test xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<a>2</a>
<b xsi:nil="true" />
</Test>
Generally if you want to use deserialization try to first use serialization to understand how must a valid XML look like.

Related

How to serialize an object containing an XML property

Note this is .NET 4.8
I have created this sample code to illustrate the problem
[XmlRoot(ElementName = "RESULT", Namespace = "", IsNullable = false)]
public class Result
{
public string Message { get; set; }
public XElement Stuff { get; set; }
public override string ToString()
{
var ser = new XmlSerializer(GetType());
using (var stream = new StringWriter())
{
ser.Serialize(stream, this);
return stream.ToString();
}
}
}
I will have some XML already that looks like this
<FOO>
<BAR>Hello World</BAR>
<BAR2>Hello World</BAR2>
<BAR3>Hello World</BAR3>
</FOO>
This is assigned to the XElement Stuff property and when an instance of Result is then serialized, you get this XML:
<?xml version="1.0" encoding="utf-16"?>
<RESULT xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Message>Hello World</Message>
<Stuff>
<FOO>
<BAR>Hello World</BAR>
<BAR2>Hello World</BAR2>
<BAR3>Hello World</BAR3>
</FOO>
</Stuff>
</RESULT>
Question: Is there any way to get this result instead?
<?xml version="1.0" encoding="utf-16"?>
<RESULT xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Message>Hello World</Message>
<FOO>
<BAR>Hello World</BAR>
<BAR2>Hello World</BAR2>
<BAR3>Hello World</BAR3>
</FOO>
</RESULT>
Note: FOO could be Stuff - I don't care (because I know how to change that name) I just don't want two levels of nested XML for that property in the serialised XML
You can play with the sample code here
If you're happy for the root name to be hard-coded, then you can write a wrapper type for the elements and implement IXmlSerializable within.
This is likely preferable to implementing in Result, as I imagine the real type would have more than 2 properties.
A quick and dirty example - I'll leave implementing ReadXml to you (if you need it):
public class ElementsWrapper : IXmlSerializable
{
public XElement[] Elements { get; set; }
public XmlSchema GetSchema()
{
return null;
}
public void ReadXml(XmlReader reader)
{
}
public void WriteXml(XmlWriter writer)
{
foreach (var element in Elements)
{
element.WriteTo(writer);
}
}
}
And change your property in Result to:
public ElementsWrapper FOO { get; set; }
When used, the serialiser for Result will write <FOO>, then delegate to the custom serialisation, and then write </FOO>.
You can see an updated fiddle here.

C# Serialize an object with a list of objects in it

In C# if I serialize an object that has a list of objects in it will it also serialize the list?
Example
public class Move {
public string MoveName {get; set;}
public List<Tag> oTags = new List<Tag>;
}
public class Tag {
public string TagName {get; set;}
}
If I serialize move will all the tags stored in move get serialized as well? Also if it will not serialize the list how would I go about making it do that?
<Move>
<MoveName>name</MoveName>
<Tag>Value</Tag>
...
</Move>
Yes, using the XmlSerializer it will serialize a List<T> so long as T (or in your case Tag) is serializable.
Move move = new Move { MoveName = "MyName" };
move.oTags.Add(new Tag { TagName = "Value1" } );
move.oTags.Add(new Tag { TagName = "Value2" } );
move.oTags.Add(new Tag { TagName = "Value3" } );
StringBuilder output = new StringBuilder();
var writer = new StringWriter(output);
XmlSerializer serializer = new XmlSerializer(typeof(Move));
serializer.Serialize(writer, move);
Console.WriteLine(output.ToString());
This outputs using your current class structure as:
<?xml version="1.0" encoding="utf-16"?>
<Move xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<oTags>
<Tag>
<TagName>Value1</TagName>
</Tag>
<Tag>
<TagName>Value2</TagName>
</Tag>
<Tag>
<TagName>Value3</TagName>
</Tag>
</oTags>
<MoveName>MyName</MoveName>
</Move>
I'll see if I can find a way to match your current XML schema, but you can look up how to apply XmlAttributes and play around with it yourself.
EDIT:
If you change your class declaration to use the following XmlAttributes, you will achieve the exact XML schema as in your example:
public class Move
{
[XmlElement(Order = 1)]
public string MoveName {get; set;}
[XmlElement(Order = 2, ElementName = "Tags")]
public List<Tag> oTags = new List<Tag>();
}
public class Tag
{
[XmlText]
public string TagName {get; set;}
}
Which when serialized will produce:
<?xml version="1.0" encoding="utf-16"?>
<Move xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<MoveName>MyName</MoveName>
<Tags>Value1</Tags>
<Tags>Value2</Tags>
<Tags>Value3</Tags>
</Move>
Are you sure that your class Declarations are right in your Question ?
you are just declaring Public Move, It should be Public class Move
Try this code
XmlSerializer serializer = new XmlSerializer(typeof(YourClass));
In Your case
Move m = new Move();
m.oTags.Add(new Tag() { TagName = "X" });
m.oTags.Add(new Tag() { TagName = "XX" });
XmlSerializer x = new XmlSerializer(typeof(Move));
System.IO.Stream s;
var xmlwriter = System.Xml.XmlWriter.Create("C:\\MXL.txt");
x.Serialize(xmlwriter, m);
OutPut
<?xml version="1.0" encoding="utf-8"?>
<Move xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<oTags>
<Tag>
<TagName>X</TagName>
</Tag>
<Tag>
<TagName>XX</TagName>
</Tag>
</oTags></Move>
By default, no it won't, since the items within the list may not be serializable.
If they are, then you may find the following page userful:
XML Serialize generic list of serializable objects

Unable to read xml data present in webservice response using c# proxy class

I need to consume web service in C# website application. I generated the proxy class using wsdl command and I am able to use it to invoke the webservice and get the result.
The issue is that I have 2 fields in response xml which provide data in cdata tags. The values for these 2 fields are returned as empty strings. I tried to add the XMLText attribute to the field definition in the proxy as shown below.
[XmlText]
public string Title {
get {
return this.TitleField;
}
set {
this.TitleField = value;
}
}
[XmlText]
public string Description {
get {
return this.descriptionField;
}
set {
this.descriptionField = value;
}
}
but I am getting the following error when the above code change is done:
Exception Details: System.InvalidOperationException: Cannot serialize object of type 'WService.XXXXXXXXXX' because it has multiple XmlText attributes. Consider using an array of strings with XmlTextAttribute for serialization of a mixed complex type.
Here is how the values appear in the response:
<Title><![CDATA[test title]]></Title>
<Description><![CDATA[test description
]]></Description>
The datatype for both these elements is specified as string in the XSD. Please let me know how this issue needs to be fixed.
From How do you serialize a string as CDATA using XmlSerializer?
[Serializable]
public class MyClass
{
public MyClass() { }
[XmlIgnore]
public string MyString { get; set; }
[XmlElement("MyString")]
public System.Xml.XmlCDataSection MyStringCDATA
{
get
{
return new System.Xml.XmlDocument().CreateCDataSection(MyString);
}
set
{
MyString = value.Value;
}
}
}
Usage:
MyClass mc = new MyClass();
mc.MyString = "<test>Hello World</test>";
XmlSerializer serializer = new XmlSerializer(typeof(MyClass));
StringWriter writer = new StringWriter();
serializer.Serialize(writer, mc);
Console.WriteLine(writer.ToString());
Output:
<?xml version="1.0" encoding="utf-16"?>
<MyClass xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<MyString><![CDATA[<test>Hello World</test>]]></MyString>
</MyClass>

How to add an XML sub-element to a simple data type in C#

I'm trying to serialize a simple data type into XML, but in a particular way to satisfy an existing API. (assume that the desired XML must be formed in this way)
Here is the desired XML:
<foo>
<value>derp</value>
</foo>
I would ideally like to represent this in a class as just
String foo;
The only two options I know of are
Simple serialization, which of course just leads to
<foo>derp</foo>
creating a Foo class, which creates the desired XML, but forces the user to type
myFoo.Value = "derp";
instead of the preferable foo = "derp";
Is there any way to have the simple string in the class, but represent it with the <value> sub-element?
Use this implementation:
[XmlRoot("foo")]
public class Foo
{
[XmlElement("value")]
public string Value { get; set; }
public static implicit operator Foo(string s)
{
return new Foo { Value = s };
}
}
Usage:
private static void Main()
{
Foo foo = "abc";
var ns = new XmlSerializerNamespaces();
ns.Add(string.Empty, string.Empty);
var serialzier = new XmlSerializer(typeof(Foo));
using (var writer = new StringWriter())
{
serialzier.Serialize(writer, foo, ns);
Console.WriteLine(writer.ToString());
}
}
Output:
<?xml version="1.0" encoding="utf-16"?>
<foo>
<value>abc</value>
</foo>

C# xmlserialization storing one class in two different way - possible?

is it possible to use C# XmlSerialization API to store single class in two different ways ?
For example
class Test
{
int group1_attr1;
int group1_attr2;
int group2_attr1;
}
I would like to have a way how to split class fields into two parts(groups - with specified attributes) and each time I call Serialize to control which part will be stored. For example if saving as group1
<?xml version="1.0" encoding="utf-8"?>
<Test xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org /2001/XMLSchema">
<group1_attr1>0</group1_attr1>
<group1_attr2>0</group1_attr2>
</Test>
if saving as group2
<?xml version="1.0" encoding="utf-8"?>
<Test xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org /2001/XMLSchema">
<group2_attr1>0</group2_attr1>
</Test>
Is there a way how to do it in a "clean" way ? If not in xmlserialization then in binary perhaps ?
Then I would like to know what is the best way how to merge those two files into single instance of Test. (Note that fields from group1 and group2 do not overlap)
Here's code demonstrating how to serialize like you want, however, deserialization will be more tricky because it's not straightforward to deserialize into an existing instance (see How to use XmlSerializer to deserialize into an existing instance?).
public class Test
{
public int group1_attr1;
public int group1_attr2;
public int group2_attr1;
}
class Program
{
static void Main(string[] args)
{
System.Xml.Serialization.XmlAttributes xa =
new System.Xml.Serialization.XmlAttributes();
xa.XmlIgnore = true;
System.Xml.Serialization.XmlAttributeOverrides xo1 =
new System.Xml.Serialization.XmlAttributeOverrides();
xo1.Add(typeof(Test), "group1_attr1", xa);
xo1.Add(typeof(Test), "group1_attr2", xa);
System.Xml.Serialization.XmlAttributeOverrides xo2 =
new System.Xml.Serialization.XmlAttributeOverrides();
xo2.Add(typeof(Test), "group2_attr1", xa);
System.Xml.Serialization.XmlSerializer xs1 =
new System.Xml.Serialization.XmlSerializer(typeof(Test), xo1);
System.Xml.Serialization.XmlSerializer xs2 =
new System.Xml.Serialization.XmlSerializer(typeof(Test), xo2);
Test t1 = new Test();
t1.group1_attr1 = 1;
t1.group1_attr2 = 2;
t1.group2_attr1 = 3;
using (System.IO.StringWriter sw = new System.IO.StringWriter())
{
xs1.Serialize(sw, t1);
Console.WriteLine(sw);
}
using (System.IO.StringWriter sw = new System.IO.StringWriter())
{
xs2.Serialize(sw, t1);
Console.WriteLine(sw);
}
}
}
The deserialization could maybe merge the XML before deserializing:
Test t2 = new Test();
System.Xml.Serialization.XmlSerializer xs3 = new System.Xml.Serialization.XmlSerializer(typeof(Test));
string mergedXml = MergeSerializedXml(group1, group2);
using (System.IO.StringReader sr = new System.IO.StringReader(mergedXml))
{
t2 = (Test)xs3.Deserialize(sr);
}
...
static string MergeSerializedXml(string x1, string x2)
{
System.Xml.XmlDocument xd = new System.Xml.XmlDocument();
xd.LoadXml(x1);
System.Xml.XmlReaderSettings xrs = new System.Xml.XmlReaderSettings();
xrs.IgnoreWhitespace = true;
using (System.Xml.XmlReader xr = System.Xml.XmlReader.Create(new System.IO.StringReader(x2), xrs))
{
while (xr.Read() && !xr.IsStartElement())
;
xr.MoveToContent();
xr.Read();
System.Xml.XmlNode xn = xd.ChildNodes[1];
do
{
xn.AppendChild(xd.ReadNode(xr));
} while (xr.NodeType != System.Xml.XmlNodeType.EndElement);
}
return xd.OuterXml;
}
Composition will sort of allow you to do this, http://en.wikipedia.org/wiki/Composition_over_inheritance. If you serialize CompositeGroup1 then stick it in an instance of CompositeData. Do the same for CompositeGroup2. There's no way to do what you are asking for, as you asked it, with the built in serializers. When you serialize data it will always generate the full serialization for that type.
class CompositeData
{
public CompositeGroup1 Group1 { get; set; }
public CompositeGroup2 Group2 { get; set; }
}
class CompositeGroup1
{
public int Attr1 { get; set; }
public int Attr2 { get; set; }
}
class CompositeGroup2
{
public int Attr1 { get; set; }
}
You could also consider making CompositeData implement abstract classes, one for each grouping. You can use some other JSON serializers that use the type passed in instead of the runtime type to generate the serialized data.
var json1 = serializer.Serialize<AbstractGroup1>(new CompositeData());
var json2 = serializer.Serialize<AbstractGroup2>(new CompositeData());
However, putting them back together again would still be your problem.
You can loook into my project, Xml Serialization Framework, that will use interfaces idiom to solve the same issue as you might have
Example:
To serialize class instance/object
class Person
{
public string Name { get; set; }
}
you would probably declare interfaces to use in metadata driven xml runtime serialization:
interface IPersonRoot
{
string Name { get; set; }
}
[XmlRootSerializer("Body")]
interface IPersonCustomRoot
{
string Name { get; set; }
}
interface IPersonCustomAttribute
{
[XmlAttributeRuntimeSerializer("Id")]
string Name { get; set; }
}
This framework supports even working with partial update of living instances using particular interfaces/XML formats:
to/from XML using IPersonRoot interface:
<Person>
<Name>John Doe</Name>
</Person>
to/from XML using IPersonCustomRoot interface:
<Body>
<Name>John Doe</Name>
</Body>
to/from XML using IPersonCustomAttribute interface:
<Person Id="John Doe" />
Have a nice day,
Cheers ;)
Artur M.

Categories