Can't seem to deserialize very simple xml using XmlSerializer in c# - c#

I feel like I am going made. I have written a hundred deserializing routines, but this one is killing me!
Below is what I get returned from a service. A very simple array of strings...I think.
<ArrayOfstring xmlns="http://schemas.microsoft.com/2003/10/Serialization/Arrays" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<string>Action & Adventure</string>
<string>Comedy</string>
<string>Drama</string>
<string>Family</string>
<string>Horror</string>
<string>Independent & World</string>
<string>Romance</string>
<string>Sci-Fi/Fantasy</string>
<string>Thriller & Crime</string>
</ArrayOfstring>
I am using out the box deserializing
var serializer = new XmlSerializer(typeof(List<string>));
var reader = new StringReader(xmlString);
var GenreList = (List<string>)serializer.Deserialize(reader);
but I get the following error on the Deserialize line:
<ArrayOfstring xmlns='http://schemas.microsoft.com/2003/10/Serialization/Arrays'> was not expected
I have tried including the namespace and creating all manner of exotic objects in an attempt to get this to work. Crazy amount of time. In the end I have requested it in JSON and deserialised that with Json.net.
However I am curious as to what I have been doing wrong!

Of course XmlSerializer can deserialize it. All you need is to create XmlSerializer as follows
var serializer = new XmlSerializer(typeof(List<string>),
new XmlRootAttribute() { ElementName = "ArrayOfstring", Namespace = "http://schemas.microsoft.com/2003/10/Serialization/Arrays" });

The XML Serializer cannot deserialize a simpletype or a list of simple types without additional specification, but the DataContractReader can:
string content = #"
<ArrayOfstring xmlns=""http://schemas.microsoft.com/2003/10/Serialization/Arrays"" xmlns:i=""http://www.w3.org/2001/XMLSchema-instance"">
<string>Action & Adventure</string>
<string>Comedy</string>
<string>Drama</string>
<string>Family</string>
<string>Horror</string>
<string>Independent & World</string>
<string>Romance</string>
<string>Sci-Fi/Fantasy</string>
<string>Thriller & Crime</string>
</ArrayOfstring>";
var serializer = new DataContractSerializer(typeof(string[]));
var reader = new XmlTextReader(new StringReader(content));
var GenreList = new List<string>((string[])serializer.ReadObject(reader));

You can also use a simple class to achieve the same results. Note that I removed the namespaces from your XML file for brevity. You can implement reading of the namespaces in the serializer if you please.
public class ArrayOfstring
{
[XmlElement("string")]
public List<string> strings;
}
private void Deserialize(string xmlString)
{
var serializer = new XmlSerializer(typeof(ArrayOfstring));
var reader = new StringReader(xmlString);
var GenreList = ((ArrayOfstring) serializer.Deserialize(reader)).strings;
}

This will work
DataContractSerializer xmlSer = new DataContractSerializer(typeof(string[]));
TextReader reader=new StreamReader(xmlString);
var stringArr= (string[])xmlSer.ReadObject(reader);
List<string> listStr=new List<>();
for(var s in stringArr)
{
listStr.Add(s);
}

I realize this is an old question, but I recently ran on to the same issue and wanted to share what worked for me. I tried all the approaches outlined as potential solutions, but couldn't get any of them to work. Even specifying the namespace in the XmlRootAttribute approach would throw the "was not expected" error reported in the original problem. I was getting the ArrayOfString as a response from an API, so I used an XDocument parse approach:
List<string> lstGenre = new List<string>();
var response = await client.PostAsync(url, content);
var responseString = await response.Content.ReadAsStringAsync();
XDocument xdoc = XDocument.Parse(responseString);
XNamespace ns = xdoc.Root.GetDefaultNamespace();
XElement root = xdoc.Element(XName.Get("ArrayOfString", ns.NamespaceName));
IEnumerable<XElement> list = root.Elements();
foreach (XElement element in list)
{
string item = element.Value; // <-- individual strings from the "ArrayOfString"
lstGenre.Add(item);
}

Related

Deserializing XML to List of C# objects in for loop too slow

I have a C# file which was auto generated by using the wsdl.exe utility.
This file contains the "MyObject" class used in the code example below.
I am now trying to Deserialize the raw response of the webservice to the object model manually.
The XML structure looks like this:
<?xml version="1.0">
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope">
<soap:Body>
<ns2:myResponse xmlns:ns2="http://myurl.com">
<return>...</return>
<return>...</return>
<return>...</return>
...
</ns2:myResponse>
</soap:Body>
</soap:Envelope>
The following code works for deserializing the "return" elements to "MyObject":
var file = #"C:\Data\example_response.xml";
var xdoc = XDocument.Load(file);
var myNamespace = "http://myurl.com";
var results = xdoc.Descendants(XName.Get("myResponse", myNamespace)).Elements();
var myObjects = new List<MyObject>();
foreach (var result in results)
{
XmlRootAttribute xRoot = new XmlRootAttribute();
xRoot.ElementName = "return";
xRoot.IsNullable = true;
var serializer = new XmlSerializer(typeof(MyObject), xRoot);
var output = (MyObject)serializer.Deserialize(result.CreateReader());
myObjects.Add(myObject);
}
The problem is the above code is terribly slow. It takes 6 secondes to iterate over 400 "return" elements.
It would probably be advised to run the XmlSerializer Deserialize method outside of the foreach-loop, but I am unable to get this working with the code below:
var file = #"C:\Data\example_response.xml";
var xdoc = XDocument.Load(file);
var myNamespace = "http://myurl.com";
var results = xdoc.Descendants(XName.Get("myResponse", myNamespace)).ElementAt(0).ToString();
XmlRootAttribute xRoot = new XmlRootAttribute();
xRoot.Namespace = myNamespace;
xRoot.ElementName = "myResponse";
xRoot.IsNullable = true;
var serializer = new XmlSerializer(typeof(List<MyObject>), xRoot);
var output = (List<MyObject>)serializer.Deserialize(new StringReader(results));
The code above is resulting into an empty list (probably because the XmlSerializer does not recognize the "return" element to be a "MyObject", but I don't want to make changes to the generated C# file.
I feel like there's a terribly easy solution for my problem, but I can't seem to figure it out.

XMLinvalide chars replacement C#

I have a string that is displayed in XML but in it I have some invalid chars like string
s = <root> something here <XMLElement>hello</XMLElement> somethig here too </root>
where XMLElement is a List like XMLElement = {"bold", "italic",...} .
What I need is to replace the < and </ if followed by any of the XMLElements to be replaced by > or < depending on the cases.
The <root> is to keep
I have tried so far some regEx
strAux = Regex.Replace(strAux, "bold=\"[^\"]*\"",
match => match.Value.Replace("<", "<").Replace(">", ">"));
or
List<string> startsWith = new List<string> { "<", "</"};
foreach(var stw in startsWith)
{
int nextLt = 0;
while ((nextLt = strAux.IndexOf(stw, nextLt)) != -1)
{
bool isMatch = strAux.Substring(nextLt + 1).StartsWith(BoldElement); // needs to ckeck all the XMLElements
//is element, leave it
if (isMatch)
{
//its not, replace
strAux = string.Format(#"{0}<{1}", strAux.Substring(0, nextLt), strAux.Substring(nextLt +1, strAux.Length - (nextLt + 1)));
}
nextLt++;
}
}
Also tried
XmlDocument doc = new XmlDocument();
XmlElement element = doc.CreateElement("root");
element.InnerText = strAux;
Console.WriteLine(element.OuterXml);
strAux = element.OuterXml.Replace("<root>", "").Replace("</root>", "");
return strAux; But it will repeat the `<root>` too
But nothing worked like I suposed. Is there any different ideias .Thanks
What you have is well-formed XML, so you can use the XML APIs to help you:
Using LINQ to XML (which is generally the better API):
var element = XElement.Parse(s);
element.Value = string.Concat(element.Nodes());
var result = element.ToString();
Or using the older XmlDocument API:
var doc = new XmlDocument();
doc.LoadXml(s);
var root = doc.DocumentElement;
root.InnerText = root.InnerXml;
var result = root.OuterXml;
The result for both is:
<root> something here <XMLElement>hello</XMLElement> somethig here too </root>
See this fiddle for a demo.
You should be using the XmlWriter class.
Sample from the documentation:
XmlWriterSettings settings = new XmlWriterSettings();
settings.OmitXmlDeclaration = true;
settings.ConformanceLevel = ConformanceLevel.Fragment;
settings.CloseOutput = false;
// Create the XmlWriter object and write some content.
MemoryStream strm = new MemoryStream();
XmlWriter writer = XmlWriter.Create(strm, settings);
writer.WriteElementString("someNode", "someValue");
writer.Flush();
writer.Close();
https://msdn.microsoft.com/en-us/library/system.xml.xmlwriter(v=vs.110).aspx
It sounds like your input is well-formed XML, but you want to escape some of the tags. The issue here is that there's no way for the code to know which tags are valid and which aren't.
One way to do this is to create a list of valid tags.
List<string> validTags = new List<string>() { "root", "..." };
Then use regex to pick out all instances of <tag> or </tag>and replace them if they're not in the list.
Another way which is faster and easier, but requires more information up front, is to create a list of tags which aren't valid.
List<string> invalidTags = new List<string>() { "XMLElement", "..." };
Simple string manipulation will do, now.
string s = GetYourXMLString();
invalidTags.ForEach(t => s = s.Replace($"</{t}>",$"<{t}>")
.Replace($"<{t}>",$"</{t}>"));
The second way should really only be used if you know which foreign tags are making (or will ever make) an appearance. If not the first approach should be used. One clever possibility is to dynamically create the list of valid tags using reflection or a data contract so that changes to the XML spec will be automatically reflected in your code.
For example, if each element is a property of an object, you might get the list like this:
var validTags = typeof(MyObjectType).GetProperties()
.Select(p => p.PropertyName)
.ToList();
Of course, the property names likely won't be the actual tag names, AND often you'll want to only include certain properties. So you make an attribute class to designate the desired properties (let's call it XMLTagName) and then you can do this:
var validTags = typeof(MyObjectType).GetProperties()
.Select(p => p.GetCustomAttribute<XMLTagName>()?.TagName)
.Where(tagName => tagName != null) //gets rid of properties that aren't tagged
.ToList();
Even with all that, you'll still committing the crime of string manipulation on raw XML. After all, the best real solution here is to figure out how to fix the incoming XML to actually contain the data you want. But if that's not a possibility, the above should do the job.

Modifying element of Json string (in C#)

I'm trying to modify an attribute of an XML string using Json in C#. Currently I'm doing the following:
XmlDocument serializedFormXml = new XmlDocument();
serializedFormXml.LoadXml(mySerializedForm);
string formJsonString = JsonConvert.SerializeXmlNode(serializedFormXml, Newtonsoft.Json.Formatting.None, true);
JObject formJsonObj = JObject.Parse(formJsonString);
formJsonObj["#code"] = "myNewValue";
var xml = JsonConvert.DeserializeXmlNode(formJsonObj.ToString()).ToString();
When I do this I get get an exception on the last line:
Unable to cast object of type 'Newtonsoft.Json.Converters.XmlDocumentWrapper' to type 'Newtonsoft.Json.Converters.IXmlElement'
Any ideas what I'm doing wrong and how I can fix modify my form attribute "code"?
This is the XML I'm using:
<Form code="XYZ">
<Info>Data</Info>
.....
Thanks!
That's going to be way, way easier with Linq-to-XML:
var doc = XDocument.Parse(mySerializedForm);
doc.Root.SetAttributeValue(doc.Root.Name.Namespace + "code", "myNewValue");
var xml = doc.ToString();
This drops the XML declaration. If you need the XML declaration included, you can use the following extension method:
public static class XObjectExtensions
{
public static string ToXml(this XDocument xDoc)
{
using (var writer = new StringWriter())
{
xDoc.Save(writer);
return writer.ToString();
}
}
}
And then write:
var xml = doc.ToXml();
If specifically you need to make the encoding string say "UTF-8", use Utf8StringWriter from this answer.
Update
The reason you code fails is that you stripped the XML root element name away when you converted to json by passing true here:
string formJsonString = JsonConvert.SerializeXmlNode(serializedFormXml, Newtonsoft.Json.Formatting.None, true);
Thus you need to add it back when converting back:
var xml = JsonConvert.DeserializeXmlNode(formJsonObj.ToString(), serializedFormXml.DocumentElement.Name).ToString();

XML Document SelectSingleNode returns null

I am trying to read XML from stream reader and am also getting response XML. But when i try to read its nodes it is always returning null.
var request = (HttpWebRequest) WebRequest.Create(address);
var response = (HttpWebResponse) request.GetResponse();
var stream = response.GetResponseStream();
if(stream != null)
{
var xmlReader = new XmlTextReader(stream);
var xmlDocument = new XmlDocument();
xmlDocument.Load(xmlReader);
var node = xmlDocument.SelectSingleNode("RateQuote");
}
XML Document
<RateQuoteResponse xmlns="http://ratequote.usfnet.usfc.com/v2/x1">
<STATUS>
<CODE>0</CODE>
<VIEW>SECURED</VIEW>
<VERSION>...</VERSION>
</STATUS>
<RateQuote>
<ORIGIN>
<NAME>KNOXVILLE</NAME>
<CARRIER>USF Holland, Inc</CARRIER>
<ADDRESS>5409 N NATIONAL DR</ADDRESS>
<CITY>KNOXVILLE</CITY>
<STATE>TN</STATE>
<ZIP>37914</ZIP>
<PHONE>8664655263</PHONE>
<PHONE_TOLLFREE>8006545963</PHONE_TOLLFREE>
<FAX>8656379999</FAX>
</ORIGIN>
<DESTINATION>
<NAME>KNOXVILLE</NAME>
<CARRIER>USF Holland, Inc</CARRIER>
<ADDRESS>5409 N NATIONAL DR</ADDRESS>
<CITY>KNOXVILLE</CITY>
<STATE>TN</STATE>
<ZIP>37914</ZIP>
<PHONE>8664655263</PHONE>
<PHONE_TOLLFREE>8006545963</PHONE_TOLLFREE>
<FAX>8656379999</FAX>
</DESTINATION>
<ORIGIN_ZIP>37914</ORIGIN_ZIP>
<DESTINATION_ZIP>37909</DESTINATION_ZIP>
<TOTAL_COST>99.24</TOTAL_COST>
<SERVICEDAYS>1</SERVICEDAYS>
<INDUSTRYDAYS>1.6</INDUSTRYDAYS>
<CLASSWEIGHT>
<CLASS>55</CLASS>
<ASCLASS>50</ASCLASS>
<WEIGHT>100</WEIGHT>
<CHARGES>0.0</CHARGES>
</CLASSWEIGHT>
</RateQuote>
</RateQuoteResponse>
The XML document uses the default namespace "http://ratequote.usfnet.usfc.com/v2/x1". You need to change the SelectSingleNode call to use this namespace.
You need to setup a namspace manager and then supply it to SelectSingleNode.
var nsmgr = new XmlNamespaceManager(doc.NameTable);
nsmgr.AddNamespace("rate", "http://ratequote.usfnet.usfc.com/v2/x1");
var node = xmlDocument.SelectSingleNode("//rate:RateQuote", nsmgr);
EDIT
The RateQuoteResponse element has a default namespace xmlns="...". This means that all elements use this namespace also, unless specifically overridden.
You can remove the namespace while reading the file, just disable the namespaces on the XmlTextReader:
var request = (HttpWebRequest) WebRequest.Create(address);
var response = (HttpWebResponse) request.GetResponse();
var stream = response.GetResponseStream();
if(stream != null)
{
var xmlReader = new XmlTextReader(stream);
xmlReader.Namespaces = false;
var xmlDocument = new XmlDocument();
xmlDocument.Load(xmlReader);
var node = xmlDocument.SelectSingleNode("RateQuote");
}
After that you don't have to care about the namespace while using XPath / LINQ on your XML-elements.
The problem is that you're asking for a RateQuote element without a namespace - whereas the RateQuote element is actually in the namespace with URI http://ratequote.usfnet.usfc.com/v2/x1.
You can either use an XmlNamespaceManager to address the namespace within your XPath, or use LINQ to XML which has very simple namespace handling:
var document = XDocument.Load(stream);
XNamespace ns = "http://ratequote.usfnet.usfc.com/v2/x1";
XElement rateQuote = document.Root.Element(ns + "RateQuote");
Personally I would use LINQ to XML if you possibly can - I find it much more pleasant to use than XmlDocument. You can still use XPath if you want of course, but I personally prefer to use the querying methods.
EDIT: Note that the namespace defaulting applies to child elements too. So to find the TOTAL_COST element you'd need:
XElement cost = document.Root
.Element(ns + "RateQuote")
.Element(ns + "TOTAL_COST");
You might want to set Namespaces to false in the XmlTextReader.
So, in your code, change:
var xmlReader = new XmlTextReader(stream);
to
var xmlReader = new XmlTextReader(stream) { Namespaces = false };
With this change, you should be able to get the node you want with SelectSingleNode without having to use namespaces.
You should also be able to do:
...
var node = xmlDocument["RateQuote"];
...
The VB syntax for that is:
...
Dim node as XmlNode = xmlDocument("RateQuote")
...

Having issue parsing xml with linq to xml

I am having a problem parsing xml that I receive from Web Service.
The xml looks very simple:
<Result xsi:schemaLocation="urn:yahoo:developer http://developer.yahooapis.com/TimeService/V1/GetTimeResponse.xsd" type="web"><Timestamp>1320677359</Timestamp></Result>
But when I try to parse it with following code I am getting no return results.
XDocument doc = XDocument.Load("http://developer.yahooapis.com/TimeService/V1/getTime?appid=StackSolution");
var datestamp = from ds in doc.Descendants("Result")
select new { currentstamp = ds.Element("Timestamp").Value };
Is there a solution or way to parse it?
Thanks you in advance
You have a couple issues: First, the Result node isn't a descendant. It's the root. Second, you ran into the most common issue when using LINQ to XML - you forgot the namespace. The following should give you what you need:
XElement doc = XElement.Load("http://developer.yahooapis.com/TimeService/V1/getTime?appid=StackSolution");
XNamespace ns = "urn:yahoo:developer";
var datestamp = from ds in doc.DescendantsAndSelf(ns + "Result")
select new { currentstamp = ds.Element(ns + "Timestamp").Value };
Note, this produces an IEnumerable. If you only want the datestamp, consider using FirstOrDefault instead. You may be able to make this simpler by just doing the following:
XElement doc = XElement.Load("http://developer.yahooapis.com/TimeService/V1/getTime?appid=StackSolution");
XNamespace ns = "urn:yahoo:developer";
var datestamp = doc.Element(ns + "Timestamp").Value;
This method avoids the namespace issue using LocalName (unqualified identifier).
var datestamp = doc.Root.Descendants().Where(c => c.Name.LocalName.Equals("Timestamp")).FirstOrDefault().FirstNode.ToString()

Categories

Resources