I'm trying to deserialize a string of XML data. But I got back no data in the object returned by the deserialization function.
Here are the class definitions:
[XmlType("STATE-COUNTY-RECORDS")]
public class StateCountyRecords
{
[XmlElement("TOTAL")]
public int Total { get; set; }
[XmlElement("LIMIT")]
public int Limit { get; set; }
[XmlElement("OFFSET")]
public int Offset { get; set; }
[XmlArray("STATE-COUNTY-ARRAY"), XmlArrayItem("STATE-COUNTY")]
public StateCounty[] StateCountyArray { get; set; }
public StateCountyRecords() {}
public StateCountyRecords(int total, int limit, int offset, int[] id, string[] fips_code, string[] state, string[] name)
{
Total = total;
Limit = limit;
Offset = offset;
var count = id.Length;
StateCountyArray = new StateCounty[count];
for (int i = 0; i < count; i++)
{
StateCountyArray[i] = new StateCounty(id[i], fips_code[i], state[i], name[i]);
}
}
}
public class StateCounty
{
[XmlElement("ID")]
public int Id { get; set; }
[XmlElement("FIPS-CODE")]
public string Fips_Code { get; set; }
[XmlElement("STATE")]
public string State { get; set; }
[XmlElement("NAME")]
public string Name { get; set; }
public StateCounty(int id, string fips_code, string state, string name)
{
Id = id;
Fips_Code = fips_code;
State = state;
Name = name;
}
public StateCounty() { }
}
Here is the xml string data:
<?xml version=\"1.0\" encoding=\"UTF-8\"?>
<STATE-COUNTY-FILE xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">
<STATE-COUNTY-RECORDS>
<TOTAL>3314</TOTAL>
<LIMIT>10</LIMIT>
<OFFSET>0</OFFSET>
<STATE-COUNTY-ARRAY>
<STATE-COUNTY>
<ID>1</ID>
<FIPS-CODE>01000</FIPS-CODE>
<STATE>AL</STATE>
<NAME>Alabama</NAME>
</STATE-COUNTY>
<STATE-COUNTY>
<ID>2</ID>
<FIPS-CODE>01001</FIPS-CODE>
<STATE>AL</STATE>
<NAME>Autauga</NAME>
</STATE-COUNTY>
<STATE-COUNTY>
<ID>3</ID>
<FIPS-CODE>01003</FIPS-CODE>
<STATE>AL</STATE>
<NAME>Baldwin</NAME>
</STATE-COUNTY>
</STATE-COUNTY-ARRAY>
</STATE-COUNTY-RECORDS>
</STATE-COUNTY-FILE>
Here is the function to deserialize the xml string:
public static StateCountyRecords DeserializeStateCountyData(string xmlString)
{
XmlSerializer mySerializer = new XmlSerializer(typeof(StateCountyRecords), new XmlRootAttribute("STATE-COUNTY-FILE"));
using (XmlReader myXmlReader = XmlReader.Create(new StringReader(xmlString)))
{
StateCountyRecords rslt;
rslt = (StateCountyRecords)mySerializer.Deserialize(myXmlReader);
}
}
}
The rslt I got back contains no data with Total and Limit, and Offset all being 0 and StateCountyArray being null. I didn't get any error. I made sure that the xml is valid by:
if (myXmlReader.CanResolveEntity && myXmlReader.CanReadValueChunk && myXmlReader.CanReadBinaryContent)
rslt = (StateCountyRecords)mySerializer.Deserialize(myXmlReader);
I wonder if there is a mismatch between the XmlElement of my class definition and the XML file.
Your problem is that your XML has an outer element that is not accounted for in your data model, namely the <STATE-COUNTY-FILE> element:
<STATE-COUNTY-FILE xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<STATE-COUNTY-RECORDS>
<!-- Various elements under StateCountyRecords -->
</STATE-COUNTY-RECORDS>
</STATE-COUNTY-FILE>
Since the root of your data model is StateCountyRecords there is nothing that corresponds to this outermost element. You try to solve this by using new XmlRootAttribute("STATE-COUNTY-FILE"), however this will only rename the outer element, not add the extra level of nesting.
Instead, you need to introduce an outer container POCO for this purpose:
[XmlType("STATE-COUNTY-FILE")]
public class StateCountyFile
{
[XmlElement("STATE-COUNTY-RECORDS")]
public StateCountyRecords StateCountyRecords { get; set; }
}
And deserialize as follows:
public static StateCountyRecords DeserializeStateCountyData(string xmlString)
{
var mySerializer = new XmlSerializer(typeof(StateCountyFile));
using (var myXmlReader = XmlReader.Create(new StringReader(xmlString)))
{
var rslt = (StateCountyFile)mySerializer.Deserialize(myXmlReader);
return rslt.StateCountyRecords;
}
}
Alternatively, you could manually advance the XmlReader until encountering an element with the name <STATE-COUNTY-RECORDS>, and deserialize that:
public static StateCountyRecords DeserializeStateCountyData(string xmlString)
{
return XmlTypeExtensions.DeserializeNestedElement<StateCountyRecords>(xmlString);
}
Using the extension methods:
public static class XmlTypeExtensions
{
public static T DeserializeNestedElement<T>(string xmlString)
{
var mySerializer = new XmlSerializer(typeof(T));
var typeName = typeof(T).XmlTypeName();
var typeNamespace = typeof(T).XmlTypeNamespace();
using (var myXmlReader = XmlReader.Create(new StringReader(xmlString)))
{
while (myXmlReader.Read())
if (myXmlReader.NodeType == XmlNodeType.Element && myXmlReader.Name == typeName && myXmlReader.NamespaceURI == typeNamespace)
{
return (T)mySerializer.Deserialize(myXmlReader);
}
return default(T);
}
}
public static string XmlTypeName(this Type type)
{
var xmlType = type.GetCustomAttribute<XmlTypeAttribute>();
if (xmlType != null && !string.IsNullOrEmpty(xmlType.TypeName))
return xmlType.TypeName;
return type.Name;
}
public static string XmlTypeNamespace(this Type type)
{
var xmlType = type.GetCustomAttribute<XmlTypeAttribute>();
if (xmlType != null && !string.IsNullOrEmpty(xmlType.Namespace))
return xmlType.Namespace;
return string.Empty;
}
}
Incidentally, deserialization problems such as this are easily diagnosed by creating an instance of your class in memory, serializing it, then comparing the resulting XML with the input XML. Usually the problem is apparent.
Related
I see this question often enough, but nobody's title really seems to depict their question. I get a large response object back from a Web API that contains general response information, along with the data object I want to deserialize.
Full XML:
<?xml version="1.0"?>
<root>
<status>
<apiErrorCode>0</apiErrorCode>
<apiErrorMessage/>
<dbErrorCode>0</dbErrorCode>
<dbErrorMessage/>
<dbErrorList/>
</status>
<data>
<modelName>ReportXDTO</modelName>
<modelData>
<id>1780</id>
<reportTitle>Access Level (select) with Door Assignment</reportTitle>
<hasParameters>true</hasParameters>
<parameters>
<dataType>STRING</dataType>
<title>Access Level:</title>
<index>1</index>
<allowMulti>true</allowMulti>
<selectSql>SELECT DISTINCT [Name] FROM dbo.[Levels] WHERE [PrecisionFlag] = '0' ORDER BY [Name] </selectSql>
<values>
<value>Door 1</value>
<used>1</used>
</values>
<values>
<value>Door 2</value>
<used>1</used>
</values>
<values>
<value>Door 3</value>
<used>1</used>
</values>
</parameters>
<sourceSql>SELECT [Name], [SData] FROM [Schedules]</sourceSql>
<report/>
</modelData>
<itemReturned>1</itemReturned>
<itemTotal>1</itemTotal>
</data>
<listInfo>
<pageIdRequested>1</pageIdRequested>
<pageIdCurrent>1</pageIdCurrent>
<pageIdFirst>1</pageIdFirst>
<pageIdPrev>1</pageIdPrev>
<pageIdNext>1</pageIdNext>
<pageIdLast>1</pageIdLast>
<itemRequested>1</itemRequested>
<itemReturned>1</itemReturned>
<itemStart>1</itemStart>
<itemEnd>1</itemEnd>
<itemTotal>1</itemTotal>
</listInfo>
</root>
I only want to deserialize the modelData element. The modelData object type is dynamic, depending on the API call.
I deserialize xml in other applications, and created the following method, but don't know how to specifically ONLY get the modelData element:
public static T ConvertXmltoClass<T>(HttpResponseMessage http, string elementName) where T : new()
{
var newClass = new T();
try
{
var doc = JsonConvert.DeserializeXmlNode(http.Content.ReadAsStringAsync().Result, "root");
XmlReader reader = new XmlNodeReader(doc);
reader.ReadToFollowing(elementName);
//The xml needs to show the proper object name
var xml = reader.ReadOuterXml().Replace(elementName, newClass.GetType().Name);
using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(xml)))
{
var serializer = new XmlSerializer(typeof(T));
newClass = (T)serializer.Deserialize(stream);
}
}
catch (Exception e)
{
AppLog.LogException(System.Reflection.MethodBase.GetCurrentMethod().Name, e);
}
return newClass;
}
I have updated this thread multiple times now, to stay current. I started updating it with the first solution. But that solution by itself didn't solve the problem. With the code how it is right now, I get no exceptions, but don't get the xml deserialized to my object. Instead I get a new, blank object. Thoughts?
THOUGH the object type can change, here is my current object I am dealing with: (PLEASE NOTE, that I deserialize the exact xml in modelData, in the Web API)
namespace WebApiCommon.DataObjects
{
[Serializable]
public class ReportXDto
{
public ReportXDto()
{
Parameters = new List<ReportParameterXDto>();
}
public int Id { get; set; }
public string ReportTitle { get; set; }
public bool HasParameters { get; set; } = false;
public List<ReportParameterXDto> Parameters { get; set; }
public string SourceSql { get; set; }
public DataTable Report { get; set; }
}
[Serializable]
public class ReportXDto
{
public ReportXDto()
{
Parameters = new List<ReportParameterXDto>();
}
public int Id { get; set; }
public string ReportTitle { get; set; }
public bool HasParameters { get; set; } = false;
public List<ReportParameterXDto> Parameters { get; set; }
public string SourceSql { get; set; }
public DataTable Report { get; set; }
}
[Serializable]
public class ReportParameterValuesXDto
{
public string Value { get; set; } = "";
public bool Used { get; set; } = false;
}
}
Firstly, XmlSerializer is case sensitive. Thus your property names need to match the XML element names exactly -- unless overridden with an attribute that controls XML serialization such as [XmlElement(ElementName="id")]. To generate a data model with the correct casing I used http://xmltocsharp.azurewebsites.net/ which resulted in:
public class ReportParameterValuesXDto
{
[XmlElement(ElementName="value")]
public string Value { get; set; }
[XmlElement(ElementName="used")]
public string Used { get; set; }
}
public class ReportParametersXDto
{
[XmlElement(ElementName="dataType")]
public string DataType { get; set; }
[XmlElement(ElementName="title")]
public string Title { get; set; }
[XmlElement(ElementName="index")]
public string Index { get; set; }
[XmlElement(ElementName="allowMulti")]
public string AllowMulti { get; set; }
[XmlElement(ElementName="selectSql")]
public string SelectSql { get; set; }
[XmlElement(ElementName="values")]
public List<ReportParameterValuesXDto> Values { get; set; }
}
public class ReportXDto
{
[XmlElement(ElementName="id")]
public string Id { get; set; }
[XmlElement(ElementName="reportTitle")]
public string ReportTitle { get; set; }
[XmlElement(ElementName="hasParameters")]
public string HasParameters { get; set; }
[XmlElement(ElementName="parameters")]
public ReportParametersXDto Parameters { get; set; }
[XmlElement(ElementName="sourceSql")]
public string SourceSql { get; set; }
[XmlElement(ElementName="report")]
public string Report { get; set; }
}
(After generating the model, I modified the class names to match your naming convention.)
Given the correct data model, you can deserialize directly from a selected XmlNode using an XmlNodeReader as shown in How to deserialize a node in a large document using XmlSerializer without having to re-serialize to an intermediate XML string. The following extension method does the trick:
public static partial class XmlExtensions
{
public static IEnumerable<T> DeserializeElements<T>(this XmlNode root, string localName, string namespaceUri)
{
return new XmlNodeReader(root).DeserializeElements<T>(localName, namespaceUri);
}
public static IEnumerable<T> DeserializeElements<T>(this XmlReader reader, string localName, string namespaceUri)
{
var serializer = XmlSerializerFactory.Create(typeof(T), localName, namespaceUri);
while (!reader.EOF)
{
if (!(reader.NodeType == XmlNodeType.Element && reader.LocalName == localName && reader.NamespaceURI == namespaceUri))
reader.ReadToFollowing(localName, namespaceUri);
if (!reader.EOF)
{
yield return (T)serializer.Deserialize(reader);
// Note that the serializer will advance the reader past the end of the node
}
}
}
}
public static class XmlSerializerFactory
{
// To avoid a memory leak the serializer must be cached.
// https://stackoverflow.com/questions/23897145/memory-leak-using-streamreader-and-xmlserializer
// This factory taken from
// https://stackoverflow.com/questions/34128757/wrap-properties-with-cdata-section-xml-serialization-c-sharp/34138648#34138648
readonly static Dictionary<Tuple<Type, string, string>, XmlSerializer> cache;
readonly static object padlock;
static XmlSerializerFactory()
{
padlock = new object();
cache = new Dictionary<Tuple<Type, string, string>, XmlSerializer>();
}
public static XmlSerializer Create(Type serializedType, string rootName, string rootNamespace)
{
if (serializedType == null)
throw new ArgumentNullException();
if (rootName == null && rootNamespace == null)
return new XmlSerializer(serializedType);
lock (padlock)
{
XmlSerializer serializer;
var key = Tuple.Create(serializedType, rootName, rootNamespace);
if (!cache.TryGetValue(key, out serializer))
cache[key] = serializer = new XmlSerializer(serializedType, new XmlRootAttribute { ElementName = rootName, Namespace = rootNamespace });
return serializer;
}
}
}
Then you would deserialize as follows:
var modelData = doc.DeserializeElements<ReportXDto>("modelData", "").FirstOrDefault();
Working sample .Net fiddle here.
For Huge xml files always use XmlReader so you do not get an out of memory issue. See code below to get the element as a string :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
namespace ConsoleApplication1
{
class Program
{
const string FILENAME = #"c:\temp\test.xml";
static void Main(string[] args)
{
//or Create(Stream)
XmlReader reader = XmlReader.Create(FILENAME);
reader.ReadToFollowing("modelData");
if (!reader.EOF)
{
string modelDataStr = reader.ReadOuterXml();
}
}
}
}
I know Json.net has an attribute [JsonRequired]; are there any XML methods that can do the same thing?.
I don't think there is, so I have made my own way of doing so, but I have stopped at how to handle List and T in reflection.
It's a great help if someone will tell me, thanks.
class Program
{
static void Main(string[] args)
{
string strin = "<TestXML><Fir>f</Fir><TestXML1><TestXML3><For>444</For></TestXML3></TestXML1></TestXML>";
TestXML ttt1 = XmlToModel<TestXML>(strin);
Console.ReadKey();
}
public static T XmlToModel<T>(string xml)
{
StringReader xmlReader = new StringReader(xml);
XmlSerializer xmlSer = new XmlSerializer(typeof(T));
T t = (T)xmlSer.Deserialize(xmlReader);
Type typeT = typeof(T);
IfIsClass<T>(typeT, t);
return t;
}
private static void IfIsClass<T>(Type typeT, T t)
{
foreach (PropertyInfo p in typeT.GetProperties())
{
//here I don't konw how to handle List<T> and T
//if(is List<T> or T)
// IfisClass<T>(typeT,t);
IfIsNull<T>(p, t);
}
}
private static void IfIsNull<T>(PropertyInfo p, T t)
{
var at = p.GetCustomAttribute(typeof(NotNullAttribute));
var pvalue = p.GetValue(t);
if (at != null && string.IsNullOrEmpty(pvalue == null ? "" : pvalue.ToString()))
{
throw new Exception(string.Format("Field {0} not allow null or empty", p.Name));
}
}
}
public class TestXML
{
public string Fir { get; set; }
[NotNull]
public string Sec { get; set; }
[XmlElement("")]
public List<TestXML2> TestXML1 { get; set; }
}
public class TestXML2
{
public string Thir { get; set; }
public TestXML3 TestXML3 { get; set; }
}
public class TestXML3
{
[NotNull]
public string For { get; set; }
}
public class NotNullAttribute : Attribute
{
}
I don't 100% know what you're trying to accomplish with what you're doing right now, and I also don't know which values you don't want to be null, but I do see that there is a much easier way of deserialzing XML into objects (see here for some reference):
[XmlRoot("TestXML")]
public class TestXML
{
[XmlElement("Fir")]
public string Fir { get; set; }
[XmlArray("TestXML1")]
[XmlArrayItem("TestXML3", IsNullable = false)]
public TestXML3[] testxml3 { get; set; }
}
public class TestXML3
{
[XmlElement("For")]
public int For { get; set; }
}
public class Order
{
[XmlElement("number")]
public string Number { get; set; }
}
Once you have that, you can deserialize the xml wherever you want in the same file with:
string xml = #"<TestXML><Fir>f</Fir><TestXML1><TestXML3><For>444</For></TestXML3></TestXML1></TestXML>";
StringReader reader = new StringReader(xml);
XmlSerializer ser = new XmlSerializer(typeof(TestXML));
var data = (TestXML)ser.Deserialize(reader);
Null Checking
Using XML
Basically, you just add IsNullable = false inside the parenthesis of a [XmlArrayItem], for example, to make sure a specific value will not return null (It will skip over it). NOTE: I have only tested this on [XmlArrayItem], I do not know for sure if it will work on other Xml tags...
Using C#
If you really wanted to use C# and throw and exception if it's null (which sort of ruins the point of using [XmlElement] in the first place), you can do something like this instead (see here):
... //same code as before
var data = (TestXML)ser.Deserialize(reader);
//haven't tested this
foreach(obj dataobj in data){
if(dataobj == null) throw new NullReferenceException();
}
Using JSON
If you really want to use something like [JsonRequired], then why not just use it! You can convert the XML data to JSON data using Json.Net (see here):
string xml = #"<TestXML><Fir>f</Fir><TestXML1><TestXML3><For>444</For></TestXML3></TestXML1></TestXML>";
XmlDocument Test = new XMLDocument();
Test.loadXml(xml);
string json = JsonConvert.SerializeXmlNode(Test);
Now, you have your xml data in json format in string json, and you can do whatever you want to it, including setting up a map like I did with the xml and adding [JsonRequired]
someone told me,here is the answer.
public static T XmlToModel<T>(string xml)
{
StringReader xmlReader = new StringReader(xml);
XmlSerializer xmlSer = new XmlSerializer(typeof(T));
T t = (T)xmlSer.Deserialize(xmlReader);
Type typeT = typeof(T);
IfIsClass(t);
return t;
}
private static void IfIsClass(object t)
{
var typeT = t.GetType();
foreach (PropertyInfo p in typeT.GetProperties())
{
if (typeof(System.Collections.ICollection).IsAssignableFrom(p.PropertyType))
{
var vValue = p.GetValue(t, null) as System.Collections.ICollection;
foreach(var item in vValue )
{
IfIsClass(item);
}
}
else
{
IfIsNull(p, p.GetValue(t, null));
}
}
}
private static void IfIsNull(PropertyInfo p, object pvalue)
{
var at = p.GetCustomAttribute(typeof(NotNullAttribute));
if (at != null && string.IsNullOrEmpty(pvalue == null ? "" : pvalue.ToString()))
{
throw new Exception(string.Format("field[{0}]not allow null or empty", p.Name));
}
}
Add a constructor
public TestXML()
{
Sec = "";
}
I need to deserialize xml file and its structured this way:
<NPCs>
<LabAssistant1>
<Questions>
<Question>
<Type>CheckBox</Type>
<Points>10</Points>
<Text>Q1</Text>
<Answers>
<Answer>
<Correct>False</Correct>
<Text>A1</Text>
</Answer>
<Answer>
<Correct>True</Correct>
<Text>A2</Text>
</Answer>
<Answer>
<Correct>False</Correct>
<Text>A3</Text>
</Answer>
</Answers>
</Question>
</Questions>
</LabAssistant1>
<LabAssistant2>
<Questions>
...
</Questions>
</LabAssistant2>
</NPCs>
So as you can see am having root node NPCs and my goal is to read questions separately by LabAssistant1 name or any tag name in NPCs.
String questionsPath = path+"/questions.xml";
XmlReader reader=XmlReader.Create(new StreamReader(questionsPath));
XmlRootAttribute xmlRoot = new XmlRootAttribute();
xmlRoot.ElementName = npc;
reader.ReadToDescendant(npc);
XmlSerializer se = new XmlSerializer(typeof(Question[]),xmlRoot);
Question[] qs=se.Deserialize(reader) as Question[];
Console.WriteLine(qs.Length.ToString()); // Always 0
Above code should output 2 objects of Question as array, but it doesn't
Here are the classes Question and Answer, anything is wrong with my attached attributes?
public class Question
{
[XmlElement(ElementName="Text")]
public String Text { get; set; }
[XmlArray(ElementName = "Answers")]
public Answer[] Answers { get; set; }
[XmlElement(ElementName = "Type")]
public QuestionType Type { get; set; }
[XmlElement(ElementName = "Points")]
public int Points { get; set; }
public Question()
{
}
public Question(String text, Answer[] answers, QuestionType type,int points)
{
this.Text = text;
this.Answers = answers;
this.Type = type;
this.Points = points;
}
}
public class Answer
{
[XmlElement(ElementName="Text")]
public String Text { get; set; }
[XmlElement(ElementName = "Correct")]
public bool Correct { get; set; }
public Answer()
{
}
public Answer(String text, bool correct)
{
this.Text = text;
this.Correct = correct;
}
}
You could use the UnknownElement event of XmlSerializer to load all the lab assistants into memory, like so:
public class LabAssistant
{
static XmlSerializer listSerializer;
static LabAssistant()
{
// This must be cached to prevent memory & resource leaks.
// See http://msdn.microsoft.com/en-us/library/System.Xml.Serialization.XmlSerializer%28v=vs.110%29.aspx
listSerializer = new XmlSerializer(typeof(List<Question>), new XmlRootAttribute("Questions"));
}
public List<Question> Questions { get; set; }
public static bool TryDeserializeFromXml(XmlElement element, out string name, out LabAssistant assistant)
{
name = element.Name;
var child = element.ChildNodes.OfType<XmlElement>().Where(el => el.Name == "Questions").FirstOrDefault();
if (child != null)
{
var list = child.OuterXml.LoadFromXML<List<Question>>(listSerializer);
if (list != null)
{
assistant = new LabAssistant() { Questions = list };
return true;
}
}
assistant = null;
return false;
}
}
public class NPCs
{
public NPCs()
{
this.LabAssistants = new Dictionary<string, LabAssistant>();
}
public static XmlSerializer CreateXmlSerializer()
{
// No need to cache this.
var serializer = new XmlSerializer(typeof(NPCs));
serializer.UnknownElement += new XmlElementEventHandler(NPCs.XmlSerializer_LoadLabAssistants);
return serializer;
}
[XmlIgnore]
public Dictionary<string, LabAssistant> LabAssistants { get; set; }
public static void XmlSerializer_LoadLabAssistants(object sender, XmlElementEventArgs e)
{
var obj = e.ObjectBeingDeserialized;
var element = e.Element;
if (obj is NPCs)
{
var npcs = (NPCs)obj;
string name;
LabAssistant assistant;
if (LabAssistant.TryDeserializeFromXml(element, out name, out assistant))
npcs.LabAssistants[name] = assistant;
}
}
}
Using the following helper methods:
public static class XmlSerializationHelper
{
public static T LoadFromXML<T>(this string xmlString)
{
return xmlString.LoadFromXML<T>(new XmlSerializer(typeof(T)));
}
public static T LoadFromXML<T>(this string xmlString, XmlSerializer serial)
{
T returnValue = default(T);
using (StringReader reader = new StringReader(xmlString))
{
object result = serial.Deserialize(reader);
if (result is T)
{
returnValue = (T)result;
}
}
return returnValue;
}
}
Having done this, you now have a dictionary of lab assistants by name.
While this code will deserialize your data correctly, it won't reserialize it. Custom code to serialize the dictionary would be required.
One final note - XmlSerializer will choke on the XML you provided because it requires that Boolean values be in lowercase. Thus the following will throw an exception:
<Correct>False</Correct>
If you did not mistype the XML and it really contains Booleans in this format, you will need to manually handle these fields.
I needed to create QuestionCollection class to hold the array of questions (having typeof(Question[]) throws <TagName xmlns="> was not expected, probably because the deserializer is not smart enough).
What i do next is first reading to tag LabAssistant or any tag name, next reading to its child Questions tag and after that i deserialize the questions into QuestionCollection, so with ReadToDescendant I can access any child elements of the NPCs
String questionsPath = Application.dataPath + "/Resources/questions.xml";
XmlReader reader=XmlReader.Create(new StreamReader(questionsPath));
reader.ReadToDescendant("LabAssistant");
reader.ReadToDescendant("Questions");
XmlSerializer se = new XmlSerializer(typeof(QuestionCollection));
QuestionCollection qc=(QuestionCollection)se.Deserialize(reader);
QuestionCollection class:
[XmlType("Questions")]
public class QuestionCollection
{
[XmlElement("Question")]
public Question[] Questions { get; set; }
public QuestionCollection() { }
}
Question class
[XmlType("Question")]
public class Question
{
[XmlElement("Text")]
public String Text { get; set; }
[XmlArray("Answers")]
public Answer[] Answers { get; set; }
[XmlElement("Type")]
public QuestionType Type { get; set; }
[XmlElement("Points")]
public int Points { get; set; }
public Question() { }
}
Answer class:
[XmlType("Answer")]
public class Answer
{
[XmlElement("Text")]
public String Text { get; set; }
[XmlElement("Correct")]
public bool Correct { get; set; }
public Answer() { }
}
I'm trying to serialize object for import to another Software and the issue is, that elements in XML that is to be imported contain ":" (p.e.: < ftr:filter>).
I declared classes overriding those names with [XmlAttribute("ftr:filter")] and [XMLElement(ftr:differentFilter")], but serializer products different nodes. I bet it has something to do with encoding, but I'm not able to change the result (thought I changed encoding).
Example of classes:
public class ListPrijemkaRequest
{
[XmlAttribute("version")]
public string Version { get; set; }
[XmlAttribute("prijemkaVersion")]
public string PrijemkaVersion { get; set; }
[XmlElement("lst:requestPrijemka")]
public List<RequestPrijemka> Requests { get; set; }
}
public class RequestPrijemka
{
[XmlElement("ftr:filter")]
public RequestDateFilter Filter { get; set; }
}
Desired ooutput:
< lst:listPrijemkaRequest version="2.0" prijemkaVersion="2.0">
< lst:requestPrijemka>
< ftr:filter>
< ftr:dateFrom>2013-01-10</ftr:dateFrom>
< ftr:dateTill>2013-03-30</ftr:dateTill>
< /ftr:filter>
< /lst:requestPrijemka>
< /lst:listPrijemkaRequest>
Obtained output:
< lst_x003A_listPrijemkaRequest version="2.0" prijemkaVersion="2.0">
< lst_x003A_requestPrijemka>
< ftr_x003A_filter>
< ftr_x003A_dateFrom>2013-01-10</ftr_x003A_dateFrom>
< ftr_x003A_dateTill>2013-03-30</ftr_x003A_dateTill>
< /ftr_x003A_filter>
< /lst_x003A_requestPrijemka>
< /lst_x003A_listPrijemkaRequest>
If those "tags" ftr / lst are namespaces, there is no need to "hard-code" them, you can setup the serializer to use those namespaces.
http://msdn.microsoft.com/en-us/library/ms163161%28v=vs.110%29.aspx
Example (taken from XML Serialization and namespace prefixes)
[XmlRoot("Node", Namespace="http://your.companies.namespace")]
public class ListPrijemkaRequest {
[XmlElement("requestPrijemka")]
public List<RequestPrijemka> Requests { get; set; }
}
static class Program
{
static void Main()
{
XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
ns.Add("lst", "http://your.companies.namespace");
XmlSerializer xser = new XmlSerializer(typeof(ListPrijemkaRequest));
xser.Serialize(Console.Out, new ListPrijemkaRequest(), ns);
}
}
If not, I don't think it's possible with "default" serialization.
Other options:
Use custom serialization (IXmlSerializable), see: http://www.codeproject.com/Articles/43237/How-to-Implement-IXmlSerializable-Correctly
Do post editing of the serialized file and do a string replace with the desired node names
But like I said in my comment, it is not recommended to use : in node names in the first place!
[XmlRoot("listPrijemkaRequest", Namespace = "http://your.companies.namespace/lst")]
public class ListPrijemkaRequest {
[XmlAttribute("version")]
public string Version { get; set; }
[XmlAttribute("prijemkaVersion")]
public string PrijemkaVersion { get; set; }
[XmlElement("requestPrijemka")]
public List<RequestPrijemka> Requests { get; set; }
}
public class RequestDateFilter
{
[XmlElement(ElementName = "dateFrom")]
public DateTime DateFrom { get; set; }
[XmlElement(ElementName = "dateTill")]
public DateTime DateTill { get; set; }
}
public class RequestPrijemka {
[XmlElement("filter", Namespace = "http://your.companies.namespace/ftr")]
public RequestDateFilter Filter { get; set; }
}
static class Program {
static void Main() {
var ns = new XmlSerializerNamespaces();
ns.Add("lst", "http://your.companies.namespace/lst");
ns.Add("ftr", "http://your.companies.namespace/ftr");
var xser = new XmlSerializer(typeof(ListPrijemkaRequest));
var obj = new ListPrijemkaRequest
{
Version = "2.0",
PrijemkaVersion = "2.0",
Requests = new List<RequestPrijemka>
{
new RequestPrijemka
{
Filter = new RequestDateFilter {DateFrom = DateTime.Now, DateTill = DateTime.Now}
}
}
};
xser.Serialize(Console.Out, obj, ns);
Console.ReadLine();
}
}
Produce this xml:
<?xml version="1.0" encoding="cp866"?>
<lst:listPrijemkaRequest xmlns:ftr="http://your.companies.namespace/ftr" version="2.0" prijemkaVersion="2.0" xmlns:lst="http://your.companies.namespace/lst">
<lst:requestPrijemka>
<ftr:filter>
<ftr:dateFrom>2014-07-17T16:17:47.0601039+03:00</ftr:dateFrom>
<ftr:dateTill>2014-07-17T16:17:47.061104+03:00</ftr:dateTill>
</ftr:filter>
</lst:requestPrijemka>
</lst:listPrijemkaRequest>
Looks similar with what you need.
I am trying to create a function to parse an XML file like this:
<?xml version="1.0" encoding="utf-8"?>
<list name="Grocery List" author="Ian" desc="Saturday grocery list">
<item color="black" done="false">Milk</item>
<item color="black" done="false">Eggs</item>
<item color="blue" done="false">Water</item>
</list>
It parses the attributes correctly, but it fails to return the values of the list items. Here is the function and class it uses:
class List
{
public string[] listItems;
public string[] colorArray;
public string[] doneArray;
public string listName;
public string listAuthor;
public string listDesc;
public string err;
}
Reader definition:
class ListReader
{
public List doListParse(string filename)
{
List l = new List();
int arrayCount = 0;
try
{
XmlReader r = XmlReader.Create(filename);
while (r.Read())
{
if (r.NodeType == XmlNodeType.Element && r.Name == "list")
{
//Get the attributes of the list
l.listName = r.GetAttribute("name");
l.listAuthor = r.GetAttribute("author");
l.listDesc = r.GetAttribute("desc");
while (r.NodeType != XmlNodeType.EndElement)
{
r.Read();
if (r.Name == "item")
{
r.Read();
if (r.NodeType == XmlNodeType.Text)
{
//Get The Attributes
l.colorArray[arrayCount] = r.GetAttribute("color");
l.doneArray[arrayCount] = r.GetAttribute("done");
//Get The Content
l.listItems[arrayCount] = r.Value.ToString();
arrayCount++;
}
r.Read();
}
}
}
}
}
catch (Exception e)
{
l.err = e.ToString();
}
return l;
}
}
When I execute the program, it gives this exception:
System.NullReferenceException: Object reference not set to an instance of an object.
What is going on here?
I'd recommend you using a serializer. The XmlSerializer class is pretty decent. It will simplify your code.
So start by defining the models that will map to this XML structure:
[XmlRoot("list")]
public class GroceryList
{
[XmlAttribute("name")]
public string Name { get; set; }
[XmlAttribute("author")]
public string Author { get; set; }
[XmlAttribute("desc")]
public string Description { get; set; }
[XmlElement("item")]
public Item[] Items { get; set; }
}
public class Item
{
[XmlAttribute("color")]
public string Color { get; set; }
[XmlAttribute("done")]
public bool Done { get; set; }
[XmlText]
public string Value { get; set; }
}
and then simply deserialize the XML:
class Program
{
static void Main()
{
var serializer = new XmlSerializer(typeof(GroceryList));
using (var reader = XmlReader.Create("groceriesList.xml"))
{
var list = (GroceryList)serializer.Deserialize(reader);
// you could access the list items here
}
}
}
You can use Linq To Xml.
var xElem = XDocument.Parse(xml).Element("list"); //or XDocument.Load(filename)
var list = new
{
Name = xElem.Attribute("name").Value,
Author = xElem.Attribute("author").Value,
Desc = xElem.Attribute("desc").Value,
Items = xElem.Elements("item")
.Select(e => new{
Color = e.Attribute("color").Value,
Done = (bool)e.Attribute("done"),
Value = e.Value,
})
.ToList()
};