The XML below always comes in this format, but the elements under the <Hit> node are dynamic, the name or number of items could be different each time. Is it possible to get the elements under the <Hit> node dynamically.
<SearchResponse>
<Response>
<Qtime>3</Qtime>
<HitsPerPage>10</HitsPerPage>
</Response>
<HitsCount>
<total>33</total>
<start>0</start>
<end>10</end>
</HitsCount>
<Hits>
<Hit>
<id type=''>123</id>
<eid type=''>456</eid>
<Title type='t'>
<![CDATA[title goes here]]>
</Title>
</Hit>
<Hit>
<id type=''>123</id>
<oid type=''>456</oid>
<Title type='t'>
<![CDATA[title goes here]]>
</Title>
<Description type='s'>
<![CDATA[Description goes here]]>
</Description>
</Hit>
</Hits>
</SearchResponse>
Edit: here's the C# code, it's working fine and get the id from the <Hit> node since I already defined the property, but I need to get all of them dynamic.
[XmlRoot("SearchResponse")]
public sealed class SearchResponse {
[XmlElement("Response", Type = typeof(Response))]
public Response[] Responses { get; set; }
[XmlElement("HitsCount", Type = typeof(HitsCount))]
public HitsCount[] HitsCount { get; set; }
[XmlElement("Hits", Type = typeof(Hits))]
public Hits[] Hits { get; set; }
public static SearchResponse GetSearchResponseObject(string xmlString) {
try {
var reader = new StringReader(xmlString);
var serializer = new XmlSerializer(typeof(SearchResponse));
var instance = (SearchResponse)serializer.Deserialize(reader);
return instance;
} catch (Exception ex) {
var asd = ex.Message;
return null;
}
}
}
[Serializable]
public class Response {
[XmlElement("Qtime")]
public string Qtime { get; set; }
[XmlElement("HitsPerPage")]
public string HitsPerPage { get; set; }
}
[Serializable]
public class HitsCount {
[XmlElement("total")]
public string Total { get; set; }
[XmlElement("start")]
public string Start { get; set; }
[XmlElement("end")]
public string End { get; set; }
}
[Serializable]
public class Hits {
[XmlElement("Hit")]
public Hit[] Hit { get; set; }
}
[Serializable]
public class Hit {
[XmlElement("id")]
public string Id { get; set; }
}
Edit 2: // Hit class code
public class Hit {
// Since "id" is expected in the XML, deserialize it explicitly.
[XmlElement("id")]
public string Id { get; set; }
private readonly List<XElement> _elements = new List<XElement>();
[XmlAnyElement]
public List<XElement> Elements { get { return _elements; } }
}
Since you don't know what elements might be present in your Hit class, you can add a List<XElement> property to you class and attach the [XmlAnyElement] attribute to it. It will then capture any and all unknown elements in the XML for the class. Once the elements are deserialized, you can add API properties to query for elements with specific names, for instance:
public class Hit
{
// Since "id" is expected in the XML, deserialize it explicitly.
[XmlElement("id")]
public string Id { get; set; }
private readonly List<XElement> elements = new List<XElement>();
[XmlAnyElement]
public List<XElement> Elements { get { return elements; } }
#region convenience methods
public string this[XName name]
{
get
{
return Elements.Where(e => e.Name == name).Select(e => e.Value).FirstOrDefault();
}
set
{
var element = Elements.Where(e => e.Name == name).FirstOrDefault();
if (element == null)
Elements.Add(element = new XElement(name));
element.Value = value;
}
}
const string title = "Title";
[XmlIgnore]
public string Title
{
get
{
return this[title];
}
set
{
this[title] = value;
}
}
#endregion
}
Incidentally, you can eliminate your Hits class if you mark the Hits array with [XmlArray] rather than [XmlElement], like so:
[XmlRoot("SearchResponse")]
public sealed class SearchResponse
{
[XmlElement("Response", Type = typeof(Response))]
public Response[] Responses { get; set; }
[XmlElement("HitsCount", Type = typeof(HitsCount))]
public HitsCount[] HitsCount { get; set; }
[XmlArray("Hits")] // Indicates that the hits will be serialized with an outer container element named "Hits".
[XmlArrayItem("Hit")] // Indicates that each inner entry element will be named "Hit".
public Hit [] Hits { get; set; }
}
Update
The
public string this[XName name] { get; set; }
Is an indexer. See Using Indexers (C# Programming Guide). I added it so it would be easy to do things like:
var description = hit["Description"];
var title = hit["Title"];
The indexer looks for the first XML element with the specified name, and returns its text value. If you don't want it, you can leave it out -- it's just for convenience.
Related
I am trying to serialize a C# object into XML so that it could be used as the body of API call. They are very particular about the input they need. I have built the following class to hold the data I need to send over to them. Including attributes and all properties as Elements instead of attributes. They also require that lists include the type="array" I though that creating my own class the implements a List would be the easiest since all lists I give them must have the same attribute. When serialization occurs it serializes the base class of List items but it doesn't include the attribute I want from the derived class.
public class CustomArray<T> : List<T>
{
[XmlAttribute]
public string type { get; set; } = "array";
}
[XmlRoot("message")]
public class MessageBody
{
[XmlArray("Checks"), XmlArrayItem("CheckItem")]
public CustomArray<Check> CheckList { get; set; }
}
public class Check
{
[XmlElement("C_CHECK_NUMBER")]
public string CheckNumber { get; set; }
[XmlElement("C_CHECK_AMOUNT")]
public decimal Amount { get; set; }
[XmlArray("InvoiceList"), XmlArrayItem("Invoice")]
public CustomArray<Invoice> InvoiceList { get; set; }
}
public class Invoice
{
[XmlElement("C_INVOICE_ID")]
public long ID { get; set; }
[XmlElement("C_INVOICE_NUM")]
public string InvoiceNum { get; set; }
}
I then run this code:
// Create a sample object
var message = new MessageBody()
{
CheckList = new CustomArray<Check>
{
new Check
{
CheckNumber = "111",
Amount = 1.00M
},
new Check
{
CheckNumber = "112",
Amount = 2.00M,
InvoiceList = new CustomArray<Invoice>
{
new Invoice
{
ID = 1,
InvoiceNum = "1"
}
}
}
}
};
// Create custom settings
var settings = new XmlWriterSettings
{
OmitXmlDeclaration = true,
Indent = true
};
// Serialize item and print it to console
using (var sw = new StringWriter())
using (var writer = XmlWriter.Create(sw, settings))
{
var serializer = new XmlSerializer(message.GetType());
serializer.Serialize(writer, message, new XmlSerializerNamespaces(new[] { XmlQualifiedName.Empty }));
Console.WriteLine(sw.ToString());
}
I get this written to the console:
<message>
<Checks>
<CheckItem>
<C_CHECK_NUMBER>111</C_CHECK_NUMBER>
<C_CHECK_AMOUNT>1.00</C_CHECK_AMOUNT>
</CheckItem>
<CheckItem>
<C_CHECK_NUMBER>112</C_CHECK_NUMBER>
<C_CHECK_AMOUNT>2.00</C_CHECK_AMOUNT>
<InvoiceList>
<Invoice>
<C_INVOICE_ID>1</C_INVOICE_ID>
<C_INVOICE_NUM>1</C_INVOICE_NUM>
</Invoice>
</InvoiceList>
</CheckItem>
</Checks>
</message>
But I need to get this:
<message>
<Checks type="array">
<CheckItem>
<C_CHECK_NUMBER>111</C_CHECK_NUMBER>
<C_CHECK_AMOUNT>1.00</C_CHECK_AMOUNT>
</CheckItem>
<CheckItem>
<C_CHECK_NUMBER>112</C_CHECK_NUMBER>
<C_CHECK_AMOUNT>2.00</C_CHECK_AMOUNT>
<InvoiceList type="array">
<Invoice>
<C_INVOICE_ID>1</C_INVOICE_ID>
<C_INVOICE_NUM>1</C_INVOICE_NUM>
</Invoice>
</InvoiceList>
</CheckItem>
</Checks>
</message>
Thank you for your help!
Here is a dotnetfiddle that I made to show it off. It's not exact but it has the same idea. https://dotnetfiddle.net/ALCX5H
Try following :
[XmlRoot("message")]
public class MessageBody
{
[XmlElement("Checks")]
public Checks Checks { get; set; }
}
public class Checks
{
[XmlAttribute]
public string type { get; set; }
[XmlElement("Checks")]
public List<Check> Checks { get; set; }
}
public class Check
{
[XmlElement("C_CHECK_NUMBER")]
public string CheckNumber { get; set; }
[XmlElement("C_CHECK_AMOUNT")]
public decimal Amount { get; set; }
}
I have to serialize my objects to a XML document in a certain order.
<?xml version="1.0"?>
<__Root __version="1.1" __encryption="2">
<__service __serviceType="Test">
<__inputData>
<BaseToChange>
<__name>TestName</__name>
</BaseToChange>
</__inputData>
<__perform>
<__eventName>Event</__eventName>
</__perform>
<__inputData>
<ObjectChanges>
<Name>Test</Name>
</ObjectChanges>
</__inputData>
<__execute />
<__requestData>
<CompletionMsg />
</__requestData>
</__service>
</__Root>
The problem I have now is that I'm not able to serialize my List<InputData> with the Element Perform in between.
public class Service
{
[XmlAttribute("__serviceType")]
public string ServiceType { get; set; }
[XmlElement("__perform")]
public Perform Perform { get; set; }
[XmlElement("__inputData")]
public List<InputData> InputData{ get; set; }
[XmlElement("__execute")]
public Execute Execute { get; set; }
[XmlElement("__requestData")]
public RequestData RequestData{ get; set; }
public Service() { }
}
The order has to be as shown. So first <__inputData>, then <__perform>, followed by any remaining <__inputData>.
I already tried to separate the Properties and therefore the XmlElements but as soon I want to serialize with two elements having the same name I get an error.
Does anyone have an idea how to accomplish that?
You can take advantage of the polymorphic element functionality [XmlElement(Type = typeof(TElement))] to create a surrogate object array that contains both the Perform and InputData objects, in the correct sequence:
public class Service
{
[XmlAttribute("__serviceType")]
public string ServiceType { get; set; }
[XmlIgnore]
public Perform Perform { get; set; }
[XmlIgnore]
public List<InputData> InputData { get; set; }
[XmlElement("__perform", Type = typeof(Perform))]
[XmlElement("__inputData", Type = typeof(InputData))]
public object[] XmlInputAndPerformance
{
get
{
var inputData = (InputData ?? Enumerable.Empty<InputData>()).Cast<object>();
var performData = Perform == null ? Enumerable.Empty<object>() : new object[] { Perform };
return inputData.Take(1)
.Concat(performData)
.Concat(inputData.Skip(1))
.ToArray();
}
set
{
if (value == null)
return;
var newInputs = value.OfType<InputData>().ToList();
var newPerform = value.OfType<Perform>().ToList();
if (newInputs.Count + newPerform.Count != value.Length)
throw new ArgumentException("Unknown type.");
if (newPerform.Count > 1)
throw new ArgumentException("Too many Perform objects.");
if (newPerform.Count > 0)
Perform = newPerform[0];
(InputData = InputData ?? new List<InputData>()).AddRange(newInputs);
}
}
[XmlElement("__execute")]
public Execute Execute { get; set; }
[XmlElement("__requestData")]
public RequestData RequestData { get; set; }
public Service() { }
}
Note that the original InputData and Perform properties have been marked with [XmlIgnore] and that two [XmlElement(name, Type = typeof(TElement))] attributes have been added, one for InputData and one for Perform.
Sample fiddle.
I have two classes, "company" derived from Treenode, and "document".
[Serializable]
[XmlRoot("Company")]
public class Company : TreeNode, IXmlSerializable
{
private string _x;
private string _y;
public Company() { }
[XmlElement("X")]
public string X { get; set; }
[XmlElement("Y")]
public string Y { get; set; }
public System.Xml.Schema.XmlSchema GetSchema()
{
return null;
}
public void ReadXml(XmlReader reader)
{
if (reader.MoveToContent() == XmlNodeType.Element && reader.LocalName == "Company")
{
x = reader["X"].ToString;
y = reader["Y"].ToString;
}
}
public void WriteXml(XmlWriter writer)
{
writer.WriteElementString("X", this.X.ToString());
writer.WriteElementString("Y", this.Y.ToString());
}
}
public class Document
{
private int _id;
private string _name;
private Company _company;
public Document() { }
[XmlElement("ID")]
public int ID { get; set; }
[XmlElement("Name")]
public string Name { get; set; }
[XmlElement("Company")]
public Company Comp { get; set; }
}
When I try to serialize document, then save to file, its work. But when I deserialize, reader parameter always null. Any solutions ?
This is my code for deserialize. "sr" is variable that hold xml text.
var sr = new StreamReader(ms);
var myStr = sr.ReadToEnd();
XmlSerializer serializer = new XmlSerializer(typeof(List<Document>));
using (TextReader tr = new StringReader(myStr))
{
List<Document> docu = (List<Document>)serializer.Deserialize(tr);
}
I try to implement ISerialization and debug but never fired, and try to overide serialize and deserialize method, and no luck.
I am using .NET Framework 3.5
As explained in this article, implementing IXmlSerializable correctly is in fact quite tricky. Your implementation of ReadXml() appears to violate the requirement that it consume the wrapper element itself as well as all the contents, like so:
public void ReadXml(System.Xml.XmlReader reader)
{
reader.MoveToContent();
// Read attributes
Boolean isEmptyElement = reader.IsEmptyElement; // (1)
reader.ReadStartElement();
if (!isEmptyElement) // (1)
{
// Read Child elements X and Y
// Consume the end of the wrapper element
reader.ReadEndElement();
}
}
Furthermore, reader["X"] returns the value of the XML attribute named "X", as explained in the docs. In your WriteXml() you wrote the values of X and Y as nested XML elements. This explains the NullReferenceException. You need to fix your read and write methods to be consistent.
However, I'd suggest an alternative to implementing IXmlSerializable, which is to introduce a surrogate type for your Company. First, extract all the non-TreeNode properties of Company into an interface:
public interface ICompany
{
string X { get; set; }
string Y { get; set; }
}
public class Company : TreeNode, ICompany
{
public Company() { }
public string X { get; set; }
public string Y { get; set; }
}
This is optional but makes the code clearer. Next, introduce a surrogate POCO that implements the same interface but does not inherit from TreeNode:
public class CompanySurrogate : ICompany
{
public string X { get; set; }
public string Y { get; set; }
public static implicit operator CompanySurrogate(Company company)
{
if (company == null)
return null;
// For more complex types, use AutoMapper
return new CompanySurrogate { X = company.X, Y = company.Y };
}
public static implicit operator Company(CompanySurrogate surrogate)
{
if (surrogate == null)
return null;
// For more complex types, use AutoMapper
return new Company { X = surrogate.X, Y = surrogate.Y };
}
}
Notice that the surrogate can be implicitly converted to your original Company type? Now you can use the surrogate in XML serialization by setting the XmlElementAttribute.Type attribute property to be that of the surrogate:
public class Document
{
public Document() { }
[XmlElement("ID")]
public int ID { get; set; }
[XmlElement("Name")]
public string Name { get; set; }
[XmlElement("Company", Type = typeof(CompanySurrogate))]
public Company Comp { get; set; }
}
This avoids all possibilities of error in implementing IXmlSerializable. Given the following input list:
var list = new List<Document>
{
new Document { Name = "my name", ID = 101, Comp = new Company { X = "foo", Y = "bar", NodeFont = new System.Drawing.Font("Arial", 10) } },
new Document { Name = "2nd name", ID = 222, Comp = new Company { X = "tlon", Y = "ukbar" } },
};
The following XML can will be generated, and can be deserialized successfully:
<ArrayOfDocument xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Document>
<ID>101</ID>
<Name>my name</Name>
<Company>
<X>foo</X>
<Y>bar</Y>
</Company>
</Document>
<Document>
<ID>222</ID>
<Name>2nd name</Name>
<Company>
<X>tlon</X>
<Y>ukbar</Y>
</Company>
</Document>
</ArrayOfDocument>
That being said, I don't really recommend this design. Your UI should present your data model, it should not be your data model. See for instance How does one implement UI independent applications?. Replacing Company with ICompany whenever possible could be a first step to changing your design; you may find it becomes easier to replace your existing architecture with a TreeNode that looks like this:
public class CompanyNode : TreeNode
{
public ICompany Company { get; set; }
}
I have multiple methods which reads data from XML having the following format:
<?xml version = "1.0" encoding="UTF-8" standalone="yes"?>
<document>
<employee>
<name>
<lastname>Sample</lastname>
<firstname>Test</firstname>
</name>
<professionalDetails>
<hiredate>October 15, 2016</hiredate>
<designation>Clerk</designation>
</professionalDetails>
<projects>
<project>
<product>Printer</product>
<id>111</id>
<price>$111.00</price>
</project>
<project>
<product>Laptop</product>
<id>222</id>
<price>$989.00</price>
</project>
</projects>
</employee>
</document>
To read the above data, I have the following methods with their respective classes.
Please note that I have my own implementation of "GetElementAsString" method. Bear with me for not providing the implementation of that.
Methods:
private static NameDetails GetsNameDetails(XNode content)
{
var element = content.XPathSelectElement("document/employee/name");
return new NameDetails
{
FirstName = element.GetElementAsString("firstName"),
LastName = element.GetElementAsString("lastName")
};
}
private static ProfessionalDetails GetsProfessionalDetailsDetails(XNode content)
{
var element = content.XPathSelectElement("document/employee/professionalDetails");
return new ProfessionalDetails
{
HireDate = element.GetElementAsString("hiredate"),
Designation = element.GetElementAsString("designation")
};
}
private static Projects GetsProjectDetails(XNode content)
{
var element = content.XPathSelectElement("document/employee/projects/project");
return new Projects
{
Id = element.GetElementAsString("id"),
Price = element.GetElementAsString("price"),
Product = element.GetElementAsString("product")
};
}
}
internal class Projects
{
public int Id { get; set; }
public string Product { get; set; }
public string Price { get; set; }
}
internal class ProfessionalDetails
{
public DateTime HireDate { get; set; }
public string Designation { get; set; }
}
internal class NameDetails
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
The underlying logic is the same for both methods. The initializing type is the only thing that gets changed. The return value of the method and the properties/fields to be initialized change, but the parameter remains the same.
How do I have one single Generic method for the below methods and decide the type to be initialised at run-time?
You could write something like this:
private static T ThingFiller<T>(
XNode context,
string elementStr,
params Action<Func<string, string>, T>[] setters
)
where T : new()
{
var element = context.XPathSelectElement(elementStr);
var t = new T();
foreach (var setter in setters)
setter(element.GetElementAsString, t);
return t;
}
private static NameDetails GetsNameDetails(XNode content)
{
return ThingFiller<NameDetails>(content, "document/employee/name",
(func, nd) => nd.FirstName = func("firstName"),
(func, nd) => nd.LastName = func("lastName")
);
}
Alternatively, you could use an XML library and simply define how to fill the fields via attributes and mapping.
You could add a generic type and then depending on the type decide how you want to parse the node:
private static T GetsDetails<T>(XNode content)
{
if(typeof(T) == typeof(ProjectDetails)){
var element = content.XPathSelectElement("document/employee/projects/project");
return (T)new Projects
{
Id = element.GetElementAsString("id"),
Price = element.GetElementAsString("price"),
Product = element.GetElementAsString("product")
};
}
else if(typeof(T) == typeof(ProfessionalDetails){ ... }
else if (...) {...}
}
I wouldn't recommend this approach though: you still have to do type-checking and you gain very little. The initial implementation you have is far simpler.
You could create an attribute which determines which XPath expression should be used. Example:
[XPathSelector("document/employee/projects/project")]
internal class Projects
{
public int Id { get; set; }
public string Product { get; set; }
public string Price { get; set; }
}
and then you could use this via reflection (code is untested, no c# compiler available here):
private static T GetNode<T>(XNode content)
: where T : new()
{
var selector = typeof(T).GetCustomProperty<XPathSelector>();
var element = content.XPathSelectElement(selector.XPathSelector);
var ret = new T();
foreach (var child in element.Elements) {
var property = typeof(T).GetProperties().Single(p => p.Name.ToLower() == child.Name.ToLower());
property.SetValue(ret, child.GetElementAsString(child.Name));
}
return ret;
}
I have this class which represent a node TestCase in my XML :
public class TestCase
{
[XmlAttribute("name")]
public string name { get; set; }
public string version { get; set; }
public string verdict { get; set; }
public string objective { get; set; }
public string criteria { get; set; }
public string issue { get; set; }
public string clientcomments { get; set; }
public string authoritycomments { get; set; }
public string sdk { get; set; }
}
I use XmlNode.SelectSingleNode to fetch a specific node in my XML. For info, there are no duplicate nodes (no nodes with the same name attribute) if it matters.
So far, I have this code :
public static TestCase FetchNode(string NodeName, string Path)
{
TestCase testcase = new TestCase();
string[] attr = { "name", "version", "verdict", "objective", "criteria"
, "issue", "clientcomments", "authoritycomments", "sdk" };
string[] attrval = { null, null,null,null,null,null,null,null,null};
XmlDocument doc = new XmlDocument();
doc.Load(Path);
XmlNode node = doc.SelectSingleNode("/TestsList/TestCase[#name='" + NodeName + "']");
for (var i = 0; i == attr.Length - 1;i++)
{
attrval[i] = node[attr[i]].InnerText;
}
testcase.name = attrval[0];
testcase.version = attrval[1];
testcase.verdict = attrval[2];
testcase.objective = attrval[3];
testcase.criteria = attrval[4];
testcase.issue = attrval[5];
testcase.clientcomments = attrval[6];
testcase.authoritycomments = attrval[7];
testcase.sdk = attrval[8];
return testcase;
}
However, this code is not scalable at all, if I change my class structure, I would need to change the function because each element of the class are hardcoded in it.
This is a wide request, but how could I write this function so if I add or remove a string in the class definition of TestCase, I don`t have to change the function FetchNode.
Thank you for your time.
You could use XmlSerializer.Deserialize
Example:
using System;
using System.IO;
using System.Xml;
using System.Xml.Serialization;
public class TestCase
{
[XmlAttribute("name")]
public string name { get; set; }
public string version { get; set; }
public string verdict { get; set; }
public string objective { get; set; }
public string criteria { get; set; }
public string issue { get; set; }
public string clientcomments { get; set; }
public string authoritycomments { get; set; }
public string sdk { get; set; }
}
public class Program
{
public const string XML = #"
<TestCase name='TicketName'>
<name>Jon Nameson</name>
<version>10.1</version>
<verdict>High</verdict>
</TestCase>
";
public static void Main()
{
var doc = new XmlDocument();
doc.LoadXml(XML);
var node = doc.SelectSingleNode("/TestCase");
var serializer = new XmlSerializer(typeof(TestCase));
var testcase = serializer.Deserialize(new StringReader(node.OuterXml)) as TestCase;
Console.WriteLine(testcase.name);
Console.WriteLine(testcase.version);
Console.WriteLine(testcase.verdict);
}
}
DotNetFiddle
You can deserialize directly from your selected XmlNode by combining XmlSerializer with XmlNodeReader using the following extension method:
public static class XmlNodeExtensions
{
public static T Deserialize<T>(this XmlNode element, XmlSerializer serializer = null)
{
using (var reader = new ProperXmlNodeReader(element))
return (T)(serializer ?? new XmlSerializer(typeof(T))).Deserialize(reader);
}
class ProperXmlNodeReader : XmlNodeReader
{
// Bug fix from https://stackoverflow.com/questions/30102275/deserialize-object-property-with-stringreader-vs-xmlnodereader
public ProperXmlNodeReader(XmlNode node)
: base(node)
{
}
public override string LookupNamespace(string prefix)
{
return NameTable.Add(base.LookupNamespace(prefix));
}
}
}
This adds an extension method to XmlNode which invokes XmlSerializer to deserialize the selected node to an instance of the generic type T.
Then do:
var testcase = node.Deserialize<TestCase>();
which is identical to:
var testcase = XmlNodeExtensions.Deserialize<TestCase>(node);
In your case the expected root element name of your TestCase class, namely <TestCase>, matches the actual node name. If the node name does not match the expected root element name, you can tell XmlSerializer to expect a different root name by following the instructions in XmlSerializer Performance Issue when Specifying XmlRootAttribute.