How to add attributes for C# XML Serialization - c#

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

Related

InvalidOperationException: <result xmlns=''> was not expected

I'm doing a request to server, which returns the following XML:
<?xml version="1.0" encoding="UTF-8"?>
<result><category>...
I am using the following code to deserialize:
private ListCategories DeserializeXML()
{
XmlSerializer xml = new XmlSerializer(typeof(ListCategories));
using (FileStream fs = new FileStream("CategoriesListXML.xml", FileMode.OpenOrCreate))
{
return (ListCategories)xml.Deserialize(fs);
}
}
And i have the following for my class Categories
[Serializable]
public class ListCategories
{
public List<Categories> CategoriesList { get; set; } = new List<Categories>();
}
[Serializable]
public class Categories
{
public int id { get; set; }
public int parent_id { get; set; }
public string name { get; set; }
public string image { get; set; }
public Categories() { }
public Categories(int id, int parent_id, string name, string image)
{
this.id = id;
this.parent_id = parent_id;
this.name = name;
this.image = image;
}
}
But when I deserialize the xml document I get a problem in this line:
return (ListCategories)xml.Deserialize(fs);
InvalidOperationException: <result xmlns=''> was not expected.
So can someone explain to me why the error is happening? As well as a possible solution?
Nothing in your defined model is result, so it makes sense that it is confused about what to do with <result>; the root element of ListCategories as defined is: <ListCategories>. If that isn't what you expect, you can use attributes to tell it what it should be, for example [XmlRoot("result")] on the class ListCategories. But: we'd need to see what you expect to advise on the exact attributes.
If I had to guess:
[XmlRoot("result")]
public class ListCategories
{
[XmlElement("category")]
public List<Categories> CategoriesList { get; set; } = new List<Categories>();
}
Note you can lose the [Serializable]. That has nothing to do with xml serialization. You can also probably lose the set on CategoriesList.

C# parse XML file to object

Using C#, is there a way to easily parse an XML file so that it can be used as an object?
Example XML:
<Config>
<Ui>
<Colour>black</Colour>
<Size>small</Size>
</Ui>
<Output>
<Mode>smb</Mode>
<Version>2</Version>
</Output>
</Config>
And then refer to the parameters in my application by
Config.Output.Mode
I've tried this method - How to Deserialize XML document
But when I try
var cfg = new Config();
cfg.Load(#"config.xml");
Console.WriteLine(cfg.Output.Mode);
visual studio indicates .Output.Mode is not valid.
Where Config.Load is
xmlData = File.ReadAllText(configPath);
var serializer = new XmlSerializer(typeof(Config));
using (var reader = new StringReader(xmlData))
{
Config result = (Config)serializer.Deserialize(reader);
}
You have to create the classes that match the definition in the xml file in order to deserialize the file into an instance of the class. Note that I've named the properties with the same name as we have in the xml file. If you want to use different property names, then you'd need to add an attribute above the property that specifies the xml element that should map to it (like for the Ui, you would add the attribute: [XmlElement("Ui")]).
Note that I've also overridden the ToString methods for the classes so we can output them to the console in a nice fashion:
public class Config
{
public UI Ui { get; set; }
public Output Output { get; set; }
public override string ToString()
{
return $"Config has properties:\n - Ui: {Ui}\n - Output: {Output}";
}
}
public class UI
{
public string Colour { get; set; }
public string Size { get; set; }
public override string ToString()
{
return $"(Colour: {Colour}, Size: {Size})";
}
}
public class Output
{
public string Mode { get; set; }
public int Version { get; set; }
public override string ToString()
{
return $"(Mode: {Mode}, Version: {Version})";
}
}
Now all we have to do is create a StreamReader, point it to our file path, and then use the XmlSerializer class to Deserialize the file (casting the output to the appropriate type) into an object:
static void Main(string[] args)
{
var filePath = #"f:\private\temp\temp2.txt";
// Declare this outside the 'using' block so we can access it later
Config config;
using (var reader = new StreamReader(filePath))
{
config = (Config) new XmlSerializer(typeof(Config)).Deserialize(reader);
}
Console.WriteLine(config);
GetKeyFromUser("\n\nDone! Press any key to exit...");
}
Output
Here are the classes:
public class Config
{
public UI UI { get; set; }
public Output Output { get; set; }
}
public struct UI
{
public string Colour { get; set; }
public string Size { get; set; }
}
public struct Output
{
public string Mode { get; set; }
public int Version { get; set; }
}
The Deserialize function:
public static T Deserialize<T>(string xmlString)
{
if (xmlString == null) return default;
var serializer = new XmlSerializer(typeof(T));
using (var reader = new StringReader(xmlString))
{
return (T) serializer.Deserialize(reader);
}
}
And here's a working version:
Config cfg = Deserialize<Config>(xmlString);
Console.WriteLine(cfg.Output.Mode);

Trouble deserializing XML into a List<T>

Here's the XML file I'm trying to deserialize:
<?xml version="1.0" encoding="utf-8"?>
<EntityType>
<Name>SomeName</Name>
<Components>
<ComponentAssembly>Some assembly name</ComponentAssembly>
</Components>
</EntityType>
And here is the Data contract I am using to deserialize it:
using System.Collections.Generic;
using System.Runtime.Serialization;
namespace GameUtilities.Entities.DataContracts
{
[DataContract(Name="EntityType",Namespace="")]
public class EntityTypeData
{
[DataMember(IsRequired=true,Order = 0)]
public string Name { get; private set; }
[DataMember(IsRequired=false,Order=1)]
public List<ComponentEntry> Components { get; private set; }
public EntityTypeData(string name, List<ComponentEntry> components = null)
{
Name = name;
if(components == null)
{
Components = new List<ComponentEntry>();
}
else
{
Components = components;
}
}
}
[DataContract]
public class ComponentEntry
{
[DataMember(IsRequired = true, Order = 0)]
public string ComponentAssembly { get; private set; }
public ComponentEntry(string componentAssembly)
{
ComponentAssembly = componentAssembly;
}
}
}
Deserializing it works correctly, but the Components list is always empty, no matter how many entrys I put inside the tags. I have tried marking the [DataMemeber] attribute for Components as "IsRequired=true", and deserialization still completes without error, but the List is not getting populated. Can you see any issues with my data contract that would make this fail?
EDIT: As a test, I ran an object using the Data Contract above through a serializer to see what XML got spat out. Here's what I saw:
<EntityType xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<Name>TESTNAME</Name>
<Components xmlns:a="http://schemas.datacontract.org/2004/07/GameUtilities.Entities.DataContracts">
<a:ComponentEntry>
<a:ComponentAssembly>ONE</a:ComponentAssembly>
</a:ComponentEntry>
<a:ComponentEntry>
<a:ComponentAssembly>TWO</a:ComponentAssembly>
</a:ComponentEntry>
<a:ComponentEntry>
<a:ComponentAssembly>THREE</a:ComponentAssembly>
</a:ComponentEntry>
</Components>
Here is the serialization code I used:
public static void SerializeObject<T>(string path, T obj)
{
FileStream fs = new FileStream(path,FileMode.Create);
XmlDictionaryWriter writer = XmlDictionaryWriter.CreateTextWriter(fs);
DataContractSerializer ser = new DataContractSerializer(typeof(T));
//Serialize the data to a file
ser.WriteObject(writer, obj);
writer.Close();
fs.Close();
}
As you can see, there is a separate ComponentEntry being created for each ComponentAssembly that is listed. Is there a way to get rid of that and just get the ComponentAssembly?
Easiest solution would be to define a collection data contract instead. I don't think there is a way to have the ComponentEntry as a separate type, when using DataContractSerializer.
[DataContract(Name="EntityType",Namespace="")]
public class EntityTypeData
{
[DataMember(IsRequired=true,Order=0)]
public string Name { get; private set; }
[DataMember(IsRequired=false,Order=1)]
public ComponentList Components { get; private set; }
public EntityTypeData(string name, IEnumerable<string> components = null)
{
Name = name;
Components = new ComponentList();
if(components != null)
{
Components.AddRange(components);
}
}
}
[CollectionDataContract(ItemName = "ComponentAssembly", Namespace="")]
public class ComponentList : List<string> {}
var ser = new DataContractSerializer(typeof(EntityTypeData));
var entity = (EntityTypeData) ser.ReadObject(stream);

Array deserialization only returning 1 element

As the title says when i deserialize the following file i only get the first (and always the first) element:
<?xml version="1.0"?>
<ServerConnections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Entries>
<ServerConnectionEntry>
<Name>Local</Name>
<Host>127.0.0.1</Host>
<Port>15556</Port>
<Username>TestUser</Username>
<AuthHash>
<base64Binary>u7a0NN4uOvCrb5t5UWVVEl14Ygo=</base64Binary>
</AuthHash>
</ServerConnectionEntry>
<ServerConnectionEntry>
<Name>Local2</Name>
<Host>127.0.0.1</Host>
<Port>15556</Port>
<Username>TestUser</Username>
<AuthHash>
<base64Binary>u7a0NN4uOvCrb5t5UWVVEl14Ygo=</base64Binary>
</AuthHash>
</ServerConnectionEntry>
</Entries>
</ServerConnections>
My code to deserialize:
var list = (ServerConnections)mSerializer.Deserialize(inputStream)).Entries;
and list.Count then is 1. ServerConnections looks like that:
public class ServerConnections
{
public ServerConnectionEntry[] Entries { get; set; }
}
There is no exception happening.
Edit:
The problem occurs when I include my class that does custom xml serialization (implements IXmlSerializable). What it does is the following:
void BigNumber::ReadXml(System::Xml::XmlReader^ reader) {
reader->ReadStartElement();
XmlSerializer^ serializer = gcnew XmlSerializer(cli::array<Byte>::typeid);
cli::array<Byte>^ data = (cli::array<Byte>^)serializer->Deserialize(reader);
pin_ptr<unsigned char> ptr(&data[0]);
BN_bin2bn(ptr, data->Length, mNumber);
}
void BigNumber::WriteXml(System::Xml::XmlWriter^ writer) {
XmlSerializer^ serializer = gcnew XmlSerializer(cli::array<Byte>::typeid);
serializer->Serialize(writer, ToByteArray());
}
While data contains the correct data after ReadXml the deserializer that works the whole list stops and does not read any additional elements.
Same here, this seems to work fine for me using code similar to yours.
public class Program
{
static void Main(string[] args)
{
XmlSerializer deserializer = new XmlSerializer(typeof(ServerConnections));
var reader = new StreamReader(#"../../Test.xml");
var entries = (ServerConnections)deserializer.Deserialize(reader);
reader.Close();
}
public class ServerConnections
{
public ServerConnectionEntry[] Entries { get; set; }
}
public class ServerConnectionEntry
{
public string Name { get; set; }
public string Host { get; set; }
public string Port { get; set; }
public string Username { get; set; }
public BinaryCode AuthHash { get; set; }
}
public class BinaryCode
{
[XmlElement("base64Binary")]
public string Code { get; set; }
}
}
I do not see any issue. I've even reproduced your scenario (complete test enclosed below) of your code, and it is doing its job correctly.
Try to search elsewhere (e.g. assure that the passed xml is the one you are expecting). But serialization is working correctly with your C# class mapping
EDIT: AuthHash class no does the conversion for you from byte[] to base64 and back
public class ServerConnections
{
public ServerConnectionEntry[] Entries { get; set; }
}
public class ServerConnectionEntry
{
public string Name { get; set; }
public AuthHash AuthHash { get; set; }
}
public class AuthHash
{
[XmlIgnore]
public byte[] Hash { get; set; }
public string base64Binary
{
get { return Convert.ToBase64String(Hash); }
set { Hash = Convert.FromBase64String(value); }
}
}
[TestClass]
public class DeserializationTest
{
public const string MyXml = #"<?xml version=""1.0""?>
<ServerConnections
xmlns:xsi=""http://www.w3.org/2001/XMLSchema-instance""
xmlns:xsd=""http://www.w3.org/2001/XMLSchema"">
<Entries>
<ServerConnectionEntry>
<Name>Local</Name>
<Host>127.0.0.1</Host>
<Port>15556</Port>
<Username>TestUser</Username>
<AuthHash>
<base64Binary>u7a0NN4uOvCrb5t5UWVVEl14Ygo=</base64Binary>
</AuthHash>
</ServerConnectionEntry>
<ServerConnectionEntry>
<Name>Local2</Name>
<Host>127.0.0.1</Host>
<Port>15556</Port>
<Username>TestUser</Username>
<AuthHash>
<base64Binary>u7a0NN4uOvCrb5t5UWVVEl14Ygo=</base64Binary>
</AuthHash>
</ServerConnectionEntry>
</Entries>
</ServerConnections>
";
[TestMethod]
public void Deserialization_Has_Two_Elements()
{
TextReader reader = new StringReader(MyXml);
var mySerializer = new XmlSerializer(typeof(ServerConnections));
var list = ((ServerConnections)mySerializer.Deserialize(reader)).Entries;
Assert.IsTrue(list.Count() == 2);
Assert.IsTrue(list.First().Name == "Local");
Assert.IsTrue(list.Last().Name == "Local2");
Assert.IsTrue(list.First().AuthHash.Hash.Length > 0);
Assert.IsTrue(list.Last().AuthHash.Hash.Length > 0);
}
}
Well fizzlesticks, the problem was that i forgot one tiny little line in the deserialization. It should be like that:
void BigNumber::ReadXml(System::Xml::XmlReader^ reader) {
reader->ReadStartElement();
XmlSerializer^ serializer = gcnew XmlSerializer(cli::array<Byte>::typeid);
cli::array<Byte>^ data = (cli::array<Byte>^)serializer->Deserialize(reader);
pin_ptr<unsigned char> ptr(&data[0]);
BN_bin2bn(ptr, data->Length, mNumber);
reader->ReadEndElement();
}
The ReadEndElement makes sure it advances to the next node. As i didn't do that the deserializer above had a problem but instead of throwing an exception it just stops parsing and returns what it got so far...

Deserialization of xml file by using XmlArray?

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);
}
}
}

Categories

Resources