I am trying to deserialize this xml structure.
<?xml version="1.0"?>
<DietPlan>
<Health>
<Fruit>Test</Fruit>
<Fruit>Test</Fruit>
<Veggie>Test</Veggie>
<Veggie>Test</Veggie>
</Health>
</DietPlan>
And I tried:
[Serializable]
[XmlRoot(ElementName = "DietPlan")]
public class TestSerialization
{
[XmlArray("Health")]
[XmlArrayItem("Fruit")]
public string[] Fruits { get; set; }
[XmlArray("Health")]
[XmlArrayItem("Veggie")]
public string[] Veggie { get; set; }
}
But this throws an exception "The XML element is already present in the current scope. Use XML attributes to specify another XML name or namespace for the element."
Thanks in adv.
You need a common type to be able to deserialize your XML, and with that you can define with the [XmlElement] namespace what type to instantiate depending on the name of the element, as shown below.
public class StackOverflow_15907357
{
const string XML = #"<?xml version=""1.0""?>
<DietPlan>
<Health>
<Fruit>Test</Fruit>
<Fruit>Test</Fruit>
<Veggie>Test</Veggie>
<Veggie>Test</Veggie>
</Health>
</DietPlan>";
[XmlRoot(ElementName = "DietPlan")]
public class TestSerialization
{
[XmlArray("Health")]
[XmlArrayItem("Fruit", Type = typeof(Fruit))]
[XmlArrayItem("Veggie", Type = typeof(Veggie))]
public Food[] Foods { get; set; }
}
[XmlInclude(typeof(Fruit))]
[XmlInclude(typeof(Veggie))]
public class Food
{
[XmlText]
public string Text { get; set; }
}
public class Fruit : Food { }
public class Veggie : Food { }
public static void Test()
{
MemoryStream ms = new MemoryStream(Encoding.UTF8.GetBytes(XML));
XmlSerializer xs = new XmlSerializer(typeof(TestSerialization));
TestSerialization obj = (TestSerialization)xs.Deserialize(ms);
foreach (var food in obj.Foods)
{
Console.WriteLine("{0}: {1}", food.GetType().Name, food.Text);
}
}
}
Related
I have the following XML;
<?xml version="1.0" encoding="UTF-8" ?>
<feedback>
<report_metadata>
<org_name>example.com</org_name>
</report_metadata>
</feedback>
and the following Feedback.cs class;
[XmlRoot("feedback", Namespace = "", IsNullable = false)]
public class Feedback
{
[XmlElement("report_metadata")]
public MetaData MetaData { get; set; }
}
[XmlType("report_metadata")]
public class MetaData
{
[XmlAttribute("org_name")]
public string Organisation { get; set; }
}
When I attempt to deserialize, the value for Organisation is null.
var xml = System.IO.File.ReadAllText("example.xml");
var serializer = new XmlSerializer(typeof(Feedback));
using (var reader = new StringReader(input))
{
var feedback = (Feedback)serializer.Deserialize(reader);
}
Yet, when I change Feedback.cs to the following, it works (obviously the property name has changed).
[XmlType("report_metadata")]
public class MetaData
{
//[XmlAttribute("org_name")]
public string org_name { get; set; }
}
I want the property to be Organisation, not org_name.
In the example XML file org_name is an XML element, not an XML attribute. Changing
[XmlAttribute("org_name")] to [XmlElement("org_name")] at the Organisation property will deserialize it as an element:
[XmlElement("org_name")]
public string Organisation { get; set; }
probably just typo
[XmlAttribute("org_name")]
public string Organisation { get; set; }
was supposed to be
[XmlElement("org_name")]
public string Organisation { get; set; }
Try to modify your Xml classes like
[XmlRoot(ElementName = "report_metadata")]
public class MetaData
{
[XmlElement(ElementName = "org_name")]
public string Organisation { get; set; }
}
[XmlRoot(ElementName = "feedback")]
public class Feedback
{
[XmlElement(ElementName = "report_metadata")]
public MetaData MetaData { get; set; }
}
Then you will get your desired output like
class Program
{
static void Main(string[] args)
{
Feedback feedback = new Feedback();
var xml = System.IO.File.ReadAllText(#"C:\Users\Nullplex6\source\repos\ConsoleApp4\ConsoleApp4\Files\XMLFile1.xml");
var serializer = new XmlSerializer(typeof(Feedback));
using (var reader = new StringReader(xml))
{
feedback = (Feedback)serializer.Deserialize(reader);
}
Console.WriteLine($"Organization: {feedback.MetaData.Organisation}");
Console.ReadLine();
}
}
Output:
I store the items displayed on my homepage as an XML string (in the settings).
<?xml version=""1.0""?>
<HomePageItemList>
<PlantHomePageItem>
<Name>Plant1</Name>
</PlantHomePageItem>
<PlantHomePageItem>
<Name>Plant2</Name>
</PlantHomePageItem>
<AdminHomePageItem>
<Name>Admin1</Name>
</AdminHomePageItem>
</HomePageItemList>
Some items represent a plant PlantHomePageItem, others an admin tool AdminHomePageItem. Both inherit the same base class HomePageItem
[XmlRoot("HomePageItemList")]
public class TestSerialization
{
[XmlArray("HomePageItemList")]
[XmlArrayItem("PlantHomePageItem", Type = typeof(PlantHomePageItem))]
[XmlArrayItem("AdminHomePageItem", Type = typeof(AdminHomePageItem))]
public List<HomePageItem> HomePageItemList { get; set; }
}
[XmlInclude(typeof(PlantHomePageItem))]
[XmlInclude(typeof(AdminHomePageItem))]
public class HomePageItem
{
[XmlElement("Name")]
public string Name { get; set; }
}
public class PlantHomePageItem : HomePageItem { }
public class AdminHomePageItem : HomePageItem { }
When I try an deserialize it, I have no runtime error, just an empty object...
MemoryStream ms = new MemoryStream(Encoding.UTF8.GetBytes(XML));
XmlSerializer xs = new XmlSerializer(typeof(TestSerialization));
TestSerialization obj = (TestSerialization)xs.Deserialize(ms);
Console.WriteLine(obj.HomePageItemList.Count);
foreach (var item in obj.HomePageItemList)
{
Console.WriteLine("{0}: {1}", item.GetType().Name, item.Name);
}
returns
0
A .NET Fiddle with the entire thing is available: https://dotnetfiddle.net/f51b0K
Change your TestSerialization class to this...
[XmlRoot("HomePageItemList")]
public class TestSerialization
{
[XmlElement("PlantHomePageItem", Type = typeof(PlantHomePageItem))]
[XmlElement("AdminHomePageItem", Type = typeof(AdminHomePageItem))]
public List<HomePageItem> HomePageItemList { get; set; }
}
Your modified example...
I am serialising a List of US States into XML, and while I can control the names of most of the output elements with attributes, the root node is always called "ArrayOfStates". Is there a way to change this so it is just "States"?
Code:
public class Program
{
[XmlArray("States")]
public static List<State> States;
public static void Main(string[] args)
{
PopulateListOfStates();
var xml = new XmlSerializer(typeof(List<State>));
xml.Serialize(new XmlTextWriter(#"C:\output.xml",Encoding.Default), States);
}
}
public struct State
{
[XmlAttribute]
public string Name;
[XmlArray("Neighbours")]
[XmlArrayItem("Neighbour")]
public List<string> Neighbours;
}
Output:
<?xml version="1.0" encoding="Windows-1252"?>
<ArrayOfState xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<State Name="AL">
<Neighbours>
<Neighbour>FL</Neighbour>
<Neighbour>GA</Neighbour>
<Neighbour>MS</Neighbour>
<Neighbour>TN</Neighbour>
</Neighbours>
</State>
<State Name="FL">
<Neighbours>
<Neighbour>AL</Neighbour>
<Neighbour>GA</Neighbour>
</Neighbours>
</State>
<State Name="GA">
<Neighbours>
<Neighbour>AL</Neighbour>
<Neighbour>FL</Neighbour>
<Neighbour>NC</Neighbour>
<Neighbour>SC</Neighbour>
<Neighbour>TN</Neighbour>
</Neighbours>
</State>
...
</ArrayOfState>
As an aside, is it also possible to have the contents of the "Neighbour" elements as attributes of those elements (i.e. <Neighbour name="XX"/>)?
[Serializable]
public class Worksheet
{
[XmlRoot(ElementName = "XML")]
public class XML
{
[XmlArray("States")]
public List<State> States { get; set; }
}
public class State
{
[XmlAttribute]
public string Name { get; set; }
[XmlArray("Neighbours")]
[XmlArrayItem("Neighbour")]
public List<Neighbour> Neighbours { get; set; }
}
public class Neighbour
{
[XmlAttribute]
public string Name { get; set; }
}
}
public static void Main(string[] args)
{
Worksheet.XML xml = PopulateListOfStates();
XmlSerializer serializer = new XmlSerializer(typeof(Worksheet.XML));
using (StreamWriter writer = new StreamWriter(#"C:\output.xml", false))
{
serializer.Serialize(writer, xml);
}
}
I know there are several posts out there with this topic, but I can't seem to figure out what is the problem here. I have serialized and deserialized xml several times, and never had this error.
The exception message is:
There is an error in XML document (1, 2).
With InnerException:
<InvoiceChangeRequest xmlns=''> was not expected.
XML file I want to deserialize:
<ns1:InvoiceChangeRequest xmlns:ns1="http://kmd.dk/fie/external_invoiceDistribution">
<CONTROL_FIELDS>
<STRUCTURID>0000000001</STRUCTURID>
<OPERA>GET</OPERA>
<WIID>000050371220</WIID>
</CONTROL_FIELDS>
<HEADER_IN>
<MANDT>751</MANDT>
<BELNR>1234567890</BELNR>
</HEADER_IN>
<ITEMS>
<ITEM_FIELDS_IN>
<BUZEI>001</BUZEI>
<BUKRS>0020</BUKRS>
</ITEM_FIELDS_IN>
</ITEMS>
</ns1:InvoiceChangeRequest>
Class I'm trying to deserialize to:
[XmlRoot(Namespace = "http://kmd.dk/fie/external_invoiceDistribution", IsNullable = false)]
public class InvoiceChangeRequest
{
[XmlElement("CONTROL_FIELDS")] public ControlFields Styrefelter;
[XmlElement("HEADER_IN")] public HeaderIn HeaderfelterInd;
[XmlElement("ITEMS")] public Items Linjer;
}
public class HeaderIn
{
[XmlElement("MANDT")] public string Kommunenummer;
[XmlElement("BELNR")] public string RegnskabsbilagsNummer;
}
public class Items
{
[XmlElement("ITEM_FIELDS_IN")] public Itemfield[] ItemfelterInd;
}
public class Itemfield
{
[XmlElement("BUZEI")] public string Linjenummer;
[XmlElement("BUKRS")] public string Firmakode;
}
Deserialization code:
XmlSerializer serializer = new XmlSerializer(typeof(InvoiceChangeRequest));
var request = serializer.Deserialize(new StringReader(output)) as InvoiceChangeRequest;
In your XML file your root element is the namespace http://kmd.dk/fie/external_invoiceDistribution with prefix ns1.
The element <CONTROL_FIELDS> isn't because it isn't prefixed. Your serialization class doesn't take this into account though. That means that it expects that <CONTROL_FIELDS> and the other elements are ALSO in the ns1 namespace.
To get the serializer parse the elements correctly add the Namespace to the elements, setting it to an empty string:
[XmlRoot(Namespace = "http://kmd.dk/fie/external_invoiceDistribution", IsNullable = false)]
public class InvoiceChangeRequest
{
[XmlElement("CONTROL_FIELDS", Namespace = "")]
public ControlFields Styrefelter { get; set; }
[XmlElement("HEADER_IN", Namespace = "")]
public HeaderIn HeaderfelterInd { get; set; }
[XmlElement("ITEMS", Namespace = "")]
public Items Linjer { get; set; }
}
This will de-serialize the given XML as intended.
In case of de-serialization issues I often create the classes in memory and then serialize that so I can inspect the resulting XML. That often gives clues on what is missing or being added compared to the input document:
var ms = new MemoryStream();
serializer.Serialize(ms, new InvoiceChangeRequest {
Styrefelter = new ControlFields { Opera="test"}
});
var s = Encoding.UTF8.GetString(ms.ToArray());
And then inspect s for differences.
You can replace 'ns1:' with string.Empty.
Below classes should serialize.
public class Item
{
[XmlElement("BUZEI")]
public string Buzei { get; set; }
[XmlElement("BUKRS")]
public string Bukrs { get; set; }
}
public class Header
{
[XmlElement("MANDT")]
public string Mandt { get; set; }
[XmlElement("BELNR")]
public string Belnr { get; set; }
}
public class ControlFields
{
[XmlElement("STRUCTURID")]
public string StructuredId { get; set; }
[XmlElement("OPERA")]
public string Opera { get; set; }
[XmlElement("WIID")]
public string Wild { get; set; }
}
public class InvoiceChangeRequest
{
[XmlElement("CONTROL_FIELDS")]
public ControlFields ControlFields { get; set; }
[XmlElement("HEADER_IN")]
public Header Header { get; set; }
[XmlArray("ITEMS")]
[XmlArrayItem("ITEM_FIELDS_IN")]
public List<Item> Items { get; set; }
}
I am having an issue with serializing and object, I can get it to create all the correct outputs except for where i have an Element that needs a value and an attribute. Here is the required output:
<Root>
<Method>Retrieve</Method>
<Options>
<Filter>
<Times>
<TimeFrom>2009-06-17</TimeFrom>
</Times>
<Document type="word">document name</Document>
</Filter>
</Options>
</AdCourierAPI>
I can build all of it but can not find a way to set the Document type attribute, here is a segment of the object class
[XmlRoot("Root"), Serializable]
public class Root
{
[XmlElement("Method")]
public string method="RetrieveApplications";
[XmlElement("Options")]
public _Options Options;
}
public class _Options
{
[XmlElement("Filter")]
public _Filter Filter;
}
public class _Filter
{
[XmlElement("Times")]
public _Times Times;
[XmlElement("Documents")]
public string Documents;
}
which gives me:
<Document>document name</Document>
rather than:
<Document type="word">document name</Document>
but I can not find a way to correct this, please advise.
Thanks
Where do you have the type stored?
Normally you could have something like:
class Document {
[XmlAttribute("type")]
public string Type { get; set; }
[XmlText]
public string Name { get; set; }
}
public class _Filter
{
[XmlElement("Times")]
public _Times Times;
[XmlElement("Document")]
public Document Document;
}
The string class doesn't have a type property, so you can't use it to create the desired output. You should create a Document class instead :
public class Document
{
[XmlText]
public string Name;
[XmlAttribute("type")]
public string Type;
}
And you should change the Document property to type Document
It sounds like you need an extra class:
public class Document
{
[XmlAttribute("type")]
public string Type { get; set; }
[XmlText]
public string Name { get; set; }
}
Where an instance (in the example) would have Type = "word" and Name = "document name"; documents would be a List<Document>.
By the way - public fields are rarely a good idea...
You can use XmlWriter instead XmlSerialization to get this effect.
It is more complex but if you have a lot of strings in model it will be cleaner solution.
Create your own CustomeAttribute, for example:
[System.AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public class MyCustomAttribute : System.Attribute
{
public MyCustomAttribute (string type)
{
MyType = type;
}
public string MyType { get; set; }
}
Then in model add it, like that:
public class MyModel
{
[MyCustom("word")]
public string Document { get; set; }
[MyCustom("time")]
public string Time { get; set; }
}
The last part is to create xml with this arguments.
You can do it likes that:
var doc = new XmlDocument();
MyModel myModel = new MyModel();//or get it from somewhere else
using (Stream s = new MemoryStream())
{
var settings = new XmlWriterSettings();
settings.Async = true;
settings.Indent = true;
var writer = XmlTextWriter.Create(s, settings);
await writer.WriteStartDocumentAsync();
await writer.WriteStartElementAsync(null,"Root", null);
myModel.GetType().GetProperties().ToList().ForEach(async p =>
{
dynamic value = p.GetValue(myModel);
writer.WriteStartElement(p.Name);
var myCustomAttribute = p.GetCustomAttributes(typeof(MyCustomAttribute), false).FirstOrDefault() as MyCustomAttribute;
if(myCustomAttribute != null)
{
await writer.WriteAttributeStringAsync(null, "MyType", null, myCustomAttribute.MyType );
}
writer.WriteValue(value);
await writer.WriteEndElementAsync();
});
await writer.WriteEndElementAsync();
await writer.FlushAsync();
s.Position = 0;
doc.Load(s);
writer.Close();
}
string myXml = doc.OuterXml
In myXml should be something like that:
(values are examples)
<?xml version="1.0" encoding="utf-8"?>
<Root>
<Document MyType="word">something</Document>
<Time MyType="time">11:31:29</Time>
</Root>
You can do it in other way, of course.
Here you have some docs which helped me:
https://learn.microsoft.com/en-us/dotnet/api/system.xml.xmlwriter?view=netframework-4.8#writing_elements