Im trying to deserialize an array of objects from a XML Document.
The document built in the following structure:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<element>
.....
</element>
<element>
.....
</element>
</root>
But for some reason Im having lots of problems doing so.
This is my function which I call to deserialize it:
public static CardModel[] Load(string text)
{
XmlRootAttribute xRoot = new XmlRootAttribute();
xRoot.ElementName = "root";
xRoot.IsNullable = true;
XmlSerializer serializer = new XmlSerializer(typeof(CardModel[]),xRoot);
StringReader reader = new StringReader(text);
CardModel[] o = serializer.Deserialize(reader) as CardModel[];
reader.Close();
return o;
}
And I am not sure if its done correctly or not. Because I know that in json you are unable to deserialize an array and you have to do some sort of "hack".
In the CardModel class (which is the element of the array) i use above the class the tag [XmlRoot("root")]. I have also tried to use [XmlRoot("element")] but still im getting stuck.
Afaik you can't directly deserialize into an array but would need a wrapper class like
[Serializable]
[XMLRoot("root")]
public class Root
{
// This does the magic of treating all "element" items nested under the root
// As part of this array
[XmlArray("element")]
public CardModel[] models;
}
And rather deserilialize into that like
public static CardModel[] Load(string text)
{
// I don't think that you need the attribute overwrite here
var serializer = new XmlSerializer(typeof(Root));
using(var reader = new StringReader(text))
{
var root = (Root) serializer.Deserialize(reader);
return root.models;
}
}
Related
I am trying to serialize and deserialize responses I am getting from service. The response is xml. The problem is that response can contain namespaces.
This is a response example which contains xmlns in root element and xmlns:ns2 in child:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<RootElement version="4.0" xmlns="http://www.test.com/test">
<Verification xmlns="" xmlns:ns2="http://www.test.com/test">
<!--some fields-->
</Verification>
</RootElement>
This is another response which does not contain xmlns and xmlns:n2:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<RootElement version="2.0">
<Verification>
<!--some fields-->
</Verification>
</RootElement>
To deserialize this response and then to serialize again I am using this code:
public static object XmlDeserializeFromString(this string objectData, Type type)
{
var serializer = new XmlSerializer(type);
object result;
using (TextReader textReader = new StringReader(objectData))
{
using (XmlTextReader xmlReader = new XmlTextReader(textReader))
{
// xmlReader.Namespaces = false;
result = serializer.Deserialize(xmlReader);
}
}
return result;
}
public static string XmlSerializeToString(this object objectInstance, System.Text.Encoding encoding)
{
var serializer = new XmlSerializer(objectInstance.GetType());
var sb = new StringBuilder();
var settings = new XmlWriterSettings
{
Encoding = encoding ?? System.Text.Encoding.UTF8,
Indent = true,
NewLineHandling = NewLineHandling.Replace
};
using (var sw = new EncodedStringWriter(encoding, sb))
{
using (var xw = XmlWriter.Create(sw, settings))
{
serializer.Serialize(xw, objectInstance);
}
}
return sb.ToString();
}
The class: This works for first type or responses but not for second. If I'll remove Namespace from attribute it will work for second but not for first type.
[Serializable, XmlRoot("RootElement", IsNullable = false, Namespace="http://www.test.com/test")]
public class RootElement
{
[XmlAttribute("version")] public string Version;
[XmlElement(Namespace="http://www.test.com/test")]
public Verification[] Verification;
}
public class Verification
{
[XmlAttribute("vendor")] public string Vendor;
}
with xmlReader.Namespaces = false line uncommented deserialization fails for first example, with commented it fails for second example. I tried to override the NamespaceURL property from XmlTextReader to ignore namespaces like it is described here and in other questions and answers and articles, also tried to override Read() method but it does not helped. I also tried to add or remove urls from class attributes but it does not helped too.
Is it possible to create a serialize and deserialize methods which can do the job for both responses? What I am doing wrong?
I have problem when I'm trying to deserialize an XML to object. My XML look like:
<?xml version="1.0" encoding="utf-16"?>
<Products
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<AllProducts>
<Product>
<ID>8</ID>
<GID>51280</GID>
<Kod>RNIKAKC1.6</Kod>
<Name>SB-800</Name>
<Ean>0018208048014</Ean>
<CommodityGroup>
<ID>86</ID>
<Name>AKCESORIA FOTO</Name>
<Path>
<Category>
<ID>60798</ID>
<Name>ARCHIWALNE</Name>
</Category>
</Path>
</CommodityGroup>
</Product>
....
Next products
...
My method code:
var MemoryStream = APIAccess.DownloadFileToStream("example.xml", "exampleContainer");
using (MemoryStream)
{
MemoryStream.Position = 0;
using (StreamReader StreamReader = new StreamReader(MemoryStream))
{
XmlSerializer serializer = new XmlSerializer(typeof(CommodityGroup));
var products = serializer.Deserialize(StreamReader);
}
}
Method DownloadFileToStream is working good, because it is useful in other classes.
I'm geting error:
InvalidOperationException: Products xmlns='' was not expected.
I want to create object of a Node CommodityGroup. I've created class selecting this node, coping it and pasting in the new class like Paste Special -> XML
Attributes of this class looks like:
[Serializable()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[XmlTypeAttribute(AnonymousType = true)]
[XmlRootAttribute(Namespace = "CommodityGroup", IsNullable = false)]
I don't know to fix it. When I'm adding into XML Serializer param new XmlRootAttribute("Products"), I'm getting "0" values.
Do you have any suggestions?
If you want to deserialize only part of an xml document, you should skip unnecessary nodes.
Do it using XmlReader.
using (StreamReader StreamReader = new StreamReader(MemoryStream))
using (var xmlReader = XmlReader.Create(StreamReader))
{
xmlReader.ReadToFollowing("CommodityGroup");
XmlSerializer serializer = new XmlSerializer(typeof(CommodityGroup));
var commodityGroup = (CommodityGroup)serializer.Deserialize(xmlReader);
}
Why does XmlSerializer populate my object property with an XmlNode array when deserializing an empty typed element using XmlNodeReader instead of an empty string like it does when using StringReader (or XmlTextReader)?
The second assertion in the following code sample fails:
var doc = new XmlDocument();
doc.Load(new StringReader(#"
<Test xmlns:xsi=""http://www.w3.org/2001/XMLSchema-instance""
xmlns:xsd=""http://www.w3.org/2001/XMLSchema"">
<Value xsi:type=""xsd:string"" />
</Test>"));
var ser = new XmlSerializer(typeof (Test));
var reader1 = new StringReader(doc.InnerXml);
var obj1 = (Test) ser.Deserialize(reader1);
Debug.Assert(obj1.Value is string);
var reader2 = new XmlNodeReader(doc.FirstChild);
var obj2 = (Test) ser.Deserialize(reader2);
Debug.Assert(obj2.Value is string);
public class Test
{
public object Value { get; set; }
}
I'm guessing it has something to do with the null internal NamespaceManager property but I'm not sure how to work around this mysterious limitation. How can I reliably deserialize a subset of my parsed XML document without converting it back into a string and re-parsing?
It looks like this is a very old XmlNodeReader bug that Microsoft have no intention of fixing. (Archived Microsoft Connect link here). I found a workaround on Lev Gimelfarb's blog here that adds namespaces to the reader's NameTable as prefixes are looked up.
public class ProperXmlNodeReader : XmlNodeReader
{
public ProperXmlNodeReader(XmlNode node) : base(node)
{
}
public override string LookupNamespace(string prefix)
{
return NameTable.Add(base.LookupNamespace(prefix));
}
}
I am using WinForms. I have an XML document that looks like this:
<?xml version="1.0" encoding="utf-8" ?>
<MarcusXMLFile xmlns:Responses="http://www.rewardstrike.com/XMLFile1.xml">
<response>
<greatmood>
<yes>
<replytocommand>
<answer>Yes.</answer>
<answer>Yes, sir.</answer>
<answer>Settings.Default.User</answer>
</replytocommand>
</yes>
</greatmood>
</response>
</MarcusXMLFile>
To read this xml document, I use:
private void Responses()
{
string query = String.Format("http://www.rewardstrike.com/XMLFile1.xml");
XmlDocument Responses = new XmlDocument();
Responses.Load(query);
XmlNode channel = Responses.SelectSingleNode("MarcusXMLFile");
if (QEvent == "yesreplytocommand")
{
XmlNodeList yesreplytocommand = Responses.SelectNodes("MarcusXMLFile/response/greatmood/yes/replytocommand/answer");
foreach (XmlNode ans in yesreplytocommand
.Cast<XmlNode>()
.OrderBy(elem => Guid.NewGuid()))
{
response = ans.InnerText;
}
}
}
and then to display:
QEvent = "yesreplytocommand";
Responses();
Console.WriteLine(response);
My problem is when it gets Settings.Default.User and displays it, I want it to display it as c# code so that it actually gets the value from the application. Right now it is actually displaying "Settings.Default.User". How do I do this?
First, you'll need a way to recognize which of your entries are literals and which are expressions. You could do it by adding an attribute to the XML node:
<?xml version="1.0" encoding="utf-8" ?>
<MarcusXMLFile xmlns:Responses="http://www.rewardstrike.com/XMLFile1.xml">
<response>
<greatmood>
<yes>
<replytocommand>
<answer>Yes.</answer>
<answer>Yes, sir.</answer>
<answer expression="true">DefaultSettings.User</answer>
</replytocommand>
</yes>
</greatmood>
</response>
</MarcusXMLFile>
Based on that you can modify your parsing code to either directly use the value from XML or evaluate it instead:
foreach (XmlNode ans in yesreplytocommand
.Cast<XmlNode>()
.OrderBy(elem => Guid.NewGuid()))
{
var attribute = ans.Attributes["expression"];
if (attribute != null && attribute.Value == "true")
{
Console.WriteLine(Evaluate(ans.InnerText));
}
else
{
Console.WriteLine(ans.InnerText);
}
}
There's still the problem of evaluating that expression. There's no easy built-in way to do that from C#. But you could use Dynamic Expresso. This is how Evaluate method could look like:
public string Evaluate(string expression)
{
var interpreter = new Interpreter();
interpreter.SetVariable("DefaultSettings", Settings.Default);
return interpreter.Eval<string>(expression);
}
As you can see, you'll still have to define the expression variables yourself. For the above to work, you will have to use DefaultSettings.User in your XML instead of Settings.Default.User. I already made that change in my sample XML at the beginning of the answer.
You should take a look at XML Serialization.
Really basic on how it works is that it can convert a struct or a class like this:
struct Foo
{
int bar = 0;
Vector2 obj = new Vector2(10, 50);
}
into this:
<?xml version="1.0" ?>
<Foo xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"">
<bar>0</bar>
<obj>
<X>10</X>
<Y>50</Y>
</ojb>
</Foo>
And the other way around.
The methods used to load and save code looks like this:
public static void Save(string filepath, Foo foobject)
{
XmlSerializer serializer = new XmlSerializer(typeof(Foo));
using (Stream stream = File.OpenWrite(filepath))
{
serializer.Serialize(stream, foobject);
}
}
public static Foo Load(string filepath)
{
Foo myFoo;
XmlSerializer serializer = new XmlSerializer(typeof(Foo));
using (Stream stream = File.OpenRead(filepath))
{
myFoo = (Foo)serializer.Deserialize(stream);
}
}
It converts xml code to c# code, and other way around.
It cannot convert methods, but it can convert most properties and classes.
This is killing me. I've read these:
http://msdn.microsoft.com/en-us/library/athddy89(v=VS.80).aspx
http://msdn.microsoft.com/en-us/library/2baksw0z(v=VS.80).aspx
But I don't see how to apply them to what I'm trying to do. I want to customize the way the following list serializes...
[Serializable]
public class FinalConcentrations : List<string> { }
so that when I pass it as the "objectToSerialze" to this...
public void serializeObject(object objectToSerialize, Stream outputStream)
{
// removes the default added namespaces
XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
ns.Add("", "");
XmlSerializer serial = new XmlSerializer(objectToSerialize.GetType());
MemoryStream ms = new MemoryStream();
serial.Serialize(ms, objectToSerialize, ns);
StreamReader reader = new StreamReader(ms);
ms.Position = 0;
ms.WriteTo(outputStream);
}
...it writes this to the output stream:
<FinalConcentrations>
<FinalConcentration>string value 1</FinalConcentration>
<FinalConcentration>string value 2</FinalConcentration>
<FinalConcentration>string value 3</FinalConcentration>
</FinalConcentration>
...instead of this
<FinalConcentrations>
<string>string value 1</string>
<string>string value 2</string>
<string>string value 3</string>
</FinalConcentration>
My serializeObject method is used to serialize a wide variety of objects, so I'm looking for a way to do this in my FinalConcentrations definition rather than within that method.
Please, help.
The easiest way to fix that is to pass in a wrapper object instead of the list itself, i.e.
public class FinalConcentrations {
private readonly List<string> items = new List<string>();
[XmlElement("FinalConcentration")]
public List<string> Items {get {return items;}}
}
that do?
Well, when I ran your example I actually got
<?xml version="1.0"?>
<ArrayOfString>
<string>Red</string>
<string>Green</string>
<string>Blue</string>
</ArrayOfString>
but by changing
[Serializable, XmlRoot( ElementName= "FinalConcentrations")]
public class FinalConcentrations : List<string> { }
I got
<?xml version="1.0"?>
<FinalConcentrations>
<string>Red</string>
<string>Green</string>
<string>Blue</string>
</FinalConcentrations>
QED?
There are a whole bunch of XML decorator attributes that can change the serialisation, eg. XmlElement. Worth having a look at.
Best of luck.