Serializing ArrayList outputs ArrayOfAnyType - c#

I do have a problem with serializing a ArrayList. Most propably I use wrong XML Attributes for it since I when I changed them it would not even serialize it and got errors like 'The type may not be used in this context.'
I need to use a non generic ArrayList. On adding [XmlArray("LineDetails")] made this code to run but the output is not correct, it should give me the LineDetails structure. Any idea how to fix this?
This is a part of a whole xml like Document > Header > LineCollection > LineDeatails.
The problem is only with this details if I use a standard string field it is ok but the range of the colletion if changing with every document.
[XmlType(TypeName = "LineCollection")]
public class LineCollection
{
public String LineCount{ get; set; }
// [XmlElement(ElementName = "LineDetails")]
[XmlArray("LineDetails")]
public ArrayList LineDetails{ get; set; }
}
public class LineDetails: ArrayList
{
public String LineNum{ get; set; }
public String ItemId{ get; set; }
public String ItemName{ get; set; }
//... only strings
}
public class Utf8StringWriter : StringWriter
{
public override Encoding Encoding => new UTF8Encoding(false);
}
public string Serialize()
{
// var xmlserializer = new XmlSerializer(this.GetType());
var xmlserializer = new XmlSerializer(this.GetType(), new Type[] { typeof(LineDetails) });
var Utf8StringWriter = new Utf8StringWriter();
var xns = new XmlSerializerNamespaces();
xns.Add(string.Empty, string.Empty);
using (var writer = XmlWriter.Create(Utf8StringWriter))
{
xmlserializer.Serialize(writer, this, xns);
return Utf8StringWriter.ToString();
}
}
And the incorrect output of this...
<LineColletion>
<LineDetails>
<anyType xmlns:p5="http://www.w3.org/2001/XMLSchema-instance" p5:type="ArrayOfAnyType"/>
<anyType xmlns:p5="http://www.w3.org/2001/XMLSchema-instance" p5:type="ArrayOfAnyType"/>
<anyType xmlns:p5="http://www.w3.org/2001/XMLSchema-instance" p5:type="ArrayOfAnyType"/>
</LineDetails>
</LineColletion>
it should be like this
<LineColletion>
<LineDetails>
<LineNum>1</LineNum>
<ItemId>Item_2321</ItemId>
<ItemName>TheItemName</ItemName>
</LineDetails>
<LineDetails>
<LineNum>2</LineNum>
<ItemId>Item_232100000</ItemId>
<ItemName>TheItemName0</ItemName>
</LineDetails>
<LineDetails>
<LineNum>3</LineNum>
<ItemId>Item_23217777</ItemId>
<ItemName>TheItemName7</ItemName>
</LineDetails>
</LineColletion>
Now the wrong xml looks like this...
<LineDetails>
<anyType xmlns:p5="http://www.w3.org/2001/XMLSchema-instance" p5:type="LineDetails">
<LineNum>1</LineNum>
<ItemId>Item_2321</ItemId>
<ItemName>TheItemName</ItemName>
</anyType>
<anyType xmlns:p5="http://www.w3.org/2001/XMLSchema-instance" p5:type="LineDetails">
<LineNum>2</LineNum>
<ItemId>Item_2321</ItemId>
<ItemName>TheItemName</ItemName>
</anyType>
</LineDetails>

You may generate the required XML by modifying your data model as follows:
[XmlType(TypeName = "LineColletion")] // Fixed: TypeName. But do you want LineColletion (misspelled) or LineCollection (correctly spelled)? Your XML shows LineColletion but your code used LineCollection.
public class LineCollection
{
public String LineCount{ get; set; }
[XmlElement("LineDetails", typeof(LineDetails))] // Fixed -- specify type(s) of items in the ArrayList.
public ArrayList LineDetails{ get; set; }
}
public class LineDetails // Fixed: removed inheritance from ArrayList.
{
public String LineNum{ get; set; }
public String ItemId{ get; set; }
public String ItemName{ get; set; }
//... only strings
}
Notes:
Your model makes LineDetails inherit from ArrayList. XmlSerializer will never serialize collection properties, it will only serialize collection items. In order to serialize it correctly, I removed the inheritance since you don't seem to be using it anyway.
If you really need LineDetails to inherit from ArrayList, you will need to implement IXmlSerializable or replace it collection with a DTO.
Implementing IXmlSerializable is tedious and error-prone. I don't recommend it.
Your LineDetails collection is serialized without an outer wrapper element. To make the serializer do this, apply XmlElementAttribute to the property.
ArrayList is an untyped collection, so you must inform XmlSerializer of the possible types it might contain. You have two options:
Assigning a specific type to a specific element name by setting XmlElementAttribute.Type (or XmlArrayItemAttribute.Type), OR
Adding xsi:type attributes by informing the serializer of additional included types. You are doing this by passing them into the constructor, which is why you are seeing the p5:type="LineDetails" attribute.
Since you don't want the attributes, you need to set the element name by setting the type like so:
[XmlElement("LineDetails", typeof(LineDetails))]
The XML element corresponding to your LineCollection is named <LineColletion>. Note that the spelling is inconsistent. You will need to set [XmlType(TypeName = "LineColletion")] to the name you actually want.
Demo fiddle here.

Related

XML serialisation for class properties with additional meta data

I have an entity as below
public class Vehicle{
public int VehicleId {get;set;};
public string Make {get;set;};
public string Model{get;set;}
}
I wanted to serialize as below
<Vehicle>
<VehicleId AppliesTo="C1">1244</VehicleId>
<Make AppliesTo="Common" >HXV</Make>
<Model AppliesTo="C2">34-34</Model>
</Vehicle>
I have around 100 properties like this in Vehicle class, for each vehicle property I wanted to attach a metadata ApplieTo which will be helpful to downstream systems. AppliesTo attribute is static and its value is defined at the design time. Now How can I attach AppliesTo metadata to each property and inturn get serialized to XML?
You can use XElement from System.Xml.Linq to achieve this. As your data is static you can assign them easily. Sample code below -
XElement data= new XElement("Vehicle",
new XElement("VehicleId", new XAttribute("AppliesTo", "C1"),"1244"),
new XElement("Make", new XAttribute("AppliesTo", "Common"), "HXV"),
new XElement("Model", new XAttribute("AppliesTo", "C2"), "34 - 34")
);
//OUTPUT
<Vehicle>
<VehicleId AppliesTo="C1">1244</VehicleId>
<Make AppliesTo="Common">HXV</Make>
<Model AppliesTo="C2">34 - 34</Model>
</Vehicle>
If you are not interested in System.Xml.Linq then you have another option of XmlSerializer class. For that you need yo define separate classes for each property of vehicle. Below is the sample code and you can extend the same for Make and Model -
[XmlRoot(ElementName = "VehicleId")]
public class VehicleId
{
[XmlAttribute(AttributeName = "AppliesTo")]
public string AppliesTo { get; set; }
[XmlText]
public string Text { get; set; }
}
[XmlRoot(ElementName = "Vehicle")]
public class Vehicle
{
[XmlElement(ElementName = "VehicleId")]
public VehicleId VehicleId { get; set; }
//Add other properties here
}
Then create test data and use XmlSerializer class to construct XML -
Vehicle vehicle = new Vehicle
{
VehicleId = new VehicleId
{
Text = "1244",
AppliesTo = "C1",
}
};
XmlSerializer testData = new XmlSerializer(typeof(Vehicle));
var xml = "";
using (var sww = new StringWriter())
{
using (XmlWriter writer = XmlWriter.Create(sww))
{
testData.Serialize(writer, vehicle);
xml = sww.ToString(); // XML
}
}
It is not easy or ideal to use the default .NET XML serializer (System.Xml.Serialization.XmlSerializer) in the way you want, but it's possible. This answer shows how to create a class structure to hold both your main data and the metadata, then use XmlAttributeAttribute to mark a property so it gets serialized as an XML attribute.
Assumptions:
There are a number of unknowns about your intended implementation, such as:
The XML serializer you want to use (default for .NET?)
The mechanism to inject 'AppliesTo' (attribute?)
Do you care about deserialization?
This answer assumes the default .NET serializer, that deserialization matters, and that you don't care about the exact method of injecting your metadata.
Key concepts:
A generic class to hold both our main property value and the metadata (see PropertyWithAppliesTo<T>)
Using XmlAttributeAttribute on the generic class' metadata, so it is written as an XML attribute on the parent property
Using XmlTextAttribute on the generic class' main data, so it is written as the Xml text of the parent property (and not as a sub-property)
Including two properties on the main type being serialized (in this case Vehicle) for every value you want serialized: one of the new generic type that gets serialized with metadata, and one of the original type marked with XmlIgnoreAttribute that provides 'expected' access to the property's value
Using the XmlElementAttribute to change the name of the serialized property (so it matches the expected name)
Code:
using System;
using System.IO;
using System.Xml.Serialization;
namespace SomeNamespace
{
public class Program
{
static void Main()
{
var serializer = new XmlSerializer(typeof(Vehicle));
string s;
var vehicle = new Vehicle { VehicleId = 1244 };
//serialize
using (var writer = new StringWriter())
{
serializer.Serialize(writer, vehicle);
s = writer.ToString();
Console.WriteLine(s);
}
// edit the serialized string to test deserialization
s = s.Replace("Common", "C1");
//deserialize
using (var reader = new StringReader(s))
{
vehicle = (Vehicle)serializer.Deserialize(reader);
Console.WriteLine($"AppliesTo attribute for VehicleId: {vehicle.VehicleIdMeta.AppliesTo}");
}
}
}
public class Vehicle
{
[XmlElement(ElementName = "VehicleId")] // renames to remove the 'Meta' string
public PropertyWithAppliesTo<int> VehicleIdMeta { get; set; } = new PropertyWithAppliesTo<int>("Common");
[XmlIgnore] // this value isn't serialized, but the property here for easy syntax
public int VehicleId
{
get { return VehicleIdMeta.Value; }
set { VehicleIdMeta.Value = value; }
}
}
public class PropertyWithAppliesTo<T>
{
[XmlAttribute] // tells serializer this should be an attribute on this element, not a property
public string AppliesTo { get; set; } = string.Empty;
[XmlText] // tells serializer to not write this as a property, but as the main XML text
public T Value { get; set; } = default;
public PropertyWithAppliesTo() : this(string.Empty) { }
public PropertyWithAppliesTo(string appliesTo) : this(appliesTo, default) { }
public PropertyWithAppliesTo(string appliesTo, T initialValue)
{
AppliesTo = appliesTo;
Value = initialValue;
}
}
}
When run, the string s will look like:
<?xml version=\"1.0\" encoding=\"utf-16\"?>
<Vehicle xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\">
<VehicleId AppliesTo="Common">1244</VehicleId>
</Vehicle>
Other Notes:
You can see how to add more properties to Vehicle: add a property of type PropertyWithAppliesTo<T> marked with XmlElement to give it the name you want, and then a property of type T marked with XmlIgnore that wraps around the Value you want.
You can control the value of AppliesTo by changing the input to the constructor of PropertyWithAppliesTo<T> and giving it a different metadata string.
If you don't want consumers of your library to see the 'meta' properties in IntelliSense, you can use the EditorBrowsableAttribute. It won't hide things from you when using the source and a project reference; it's only hidden when referencing the compiled dll.
This is admittedly an annoying way to add properties to a class. But if you want to use the default .NET XML serializer, this is a way to achieve the XML you want.

Restsharp - how to serialize a list of enums to strings

I have an List<AnimalsEnum> Foo property in a class that I'm serializing to XML with RestSharp for the body of a request. I'd like the output to be:
<rootNode>
... existing content...
<Foo>Elephant</Foo>
<Foo>Tiger</Foo>
.... more content
Instead, for the relevant serialisation part, I have
<Foo>
<AnimalsEnum />
<AnimalsEnum />
</Foo>
I'd like to convert the enum values to strings and remove the container element that is automatically added. Is this possible with RestSharp? I thought it may be possible with attributes, but apparently not. Am I going to have to wrangle this output myself with a custom serialiser?
Code is difficult to post, but keeping with the example:
class Bar
{
public string Name{get;set;}
public List<AnimalsEnum> Foo{get;set;}
public enum AnimalsEnum {Tiger,Elephant,Monkey}
}
and to serialize into a request
var req = new RestSharp.RestRequest(RestSharp.Method.POST);
req.RequestFormat = RestSharp.DataFormat.Xml;
req.AddQueryParameter("REST-PAYLOAD", "");
req.AddXmlBody(myBar);
You can use the built-in DotNetXmlSerializer of RestSharp to make Microsoft's XmlSerializer do the actual serialization. Then you can use XML serialization attributes to specify that the List<AnimalsEnum> of Bar should be serialized without an outer container element by applying [XmlElement]:
public class Bar
{
public string Name { get; set; }
[System.Xml.Serialization.XmlElement]
public List<AnimalsEnum> Foo { get; set; }
public enum AnimalsEnum { Tiger, Elephant, Monkey }
}
Then, when making the request, do:
var req = new RestSharp.RestRequest(RestSharp.Method.POST);
// Use XmlSerializer to serialize Bar
req.XmlSerializer = new RestSharp.Serializers.DotNetXmlSerializer();
req.RequestFormat = RestSharp.DataFormat.Xml;
req.AddQueryParameter("REST-PAYLOAD", "");
req.AddXmlBody(myBar);
Note that Bar must be public because XmlSerializer can only serialize public types.

Deserialization XML to object with list in c#

I want to deserialize XML to object in C#, object has one string property and list of other objects.
There are classes which describe XML object, my code doesn't work (it is below, XML is at end of my post). My Deserialize code doesn't return any object.
I think I do something wrong with attributes, could you check it and give me some advice to fix it.
Thanks for your help.
[XmlRoot("shepherd")]
public class Shepherd
{
[XmlElement("name")]
public string Name { get; set; }
[XmlArray(ElementName = "sheeps", IsNullable = true)]
[XmlArrayItem(ElementName = "sheep")]
public List<Sheep> Sheeps { get; set; }
}
public class Sheep
{
[XmlElement("colour")]
public string colour { get; set; }
}
There is C# code to deserialize XML to objects
var rootNode = new XmlRootAttribute();
rootNode.ElementName = "createShepherdRequest";
rootNode.Namespace = "http://www.sheeps.pl/webapi/1_0";
rootNode.IsNullable = true;
Type deserializeType = typeof(Shepherd[]);
var serializer = new XmlSerializer(deserializeType, rootNode);
using (Stream xmlStream = new MemoryStream())
{
doc.Save(xmlStream);
var result = serializer.Deserialize(xmlStream);
return result as Shepherd[];
}
There is XML example which I want to deserialize
<?xml version="1.0" encoding="utf-8"?>
<createShepherdRequest xmlns="http://www.sheeps.pl/webapi/1_0">
<shepherd>
<name>name1</name>
<sheeps>
<sheep>
<colour>colour1</colour>
</sheep>
<sheep>
<colour>colour2</colour>
</sheep>
<sheep>
<colour>colour3</colour>
</sheep>
</sheeps>
</shepherd>
</createShepherdRequest>
XmlRootAttribute does not change the name of the tag when used as an item. The serializer expects <Shepherd>, but finds <shepherd> instead. (XmlAttributeOverrides does not seem to work on arrays either.) One way to to fix it, is by changing the case of the class-name itself:
public class shepherd
{
// ...
}
An easier alternative to juggling with attributes, is to create a proper wrapper class:
[XmlRoot("createShepherdRequest", Namespace = "http://www.sheeps.pl/webapi/1_0")]
public class CreateShepherdRequest
{
[XmlElement("shepherd")]
public Shepherd Shepherd { get; set; }
}

best way to deserialize xml in c#?

I have following xml:
<return_obj from_call_to="categories">
<categories>
<category>
<value>12341234</value>
<label>First</label>
</category>
<category>
<value>242234234</value>
<label>Another</label>
</category>
</categories>
</return_obj>
so marked up an object to serialize this into
[XmlRoot(ElementName = "return_obj")]
public class returnobject
{
[XmlElement]
public category[] categories { get; set; }
}
public class category
{
[XmlElement]
public string value { get; set; }
[XmlElement]
public string label { get; set; }
}
and tried using this to do it
var ser = new XmlSerializer(typeof (returnobject));
var obj = (returnobject)ser.Deserialize(File.OpenRead("test.xml"));
However, the categories collection always some ups null.
What am I doing wrong? Is there a better way?
Thanks
Make categories field public in class returnobject. That would help.
XmlSerializer only looks at public fields and properties, so you have to make categories public in your returnobject class.
Also you have to specify the name of the XML array container you want to use, in your case categories - this worked for me:
[XmlRoot(ElementName = "return_obj")]
public class returnobject
{
[XmlArray("categories")]
[XmlArrayItem("category")]
public category[] categories { get; set; }
}
FYI, XmlSerializer has to generate type information of the serialization types. This can take a while, so you might find serialization and deserialization taking several hundred milliseconds. You can get around this by running SGEN to pre-generate the serialization assemblies.
Alternatively, you can use XmlReader to read the XML and just code the serialization yourself. It's more code, but always performs well and isn't burdened with the extra assembly (generated or not).

DataContractSerializer not deserializing all variables

I'm trying to deserialize some xml without having the original class that was used to create the object in xml. The class is called ComOpcClientConfiguration.
It's succesfully setting the ServerUrl and ServerUrlHda values, but not rest of them...
So what I'm asking is: How can I make the rest of these values get set properly, and why aren't they working with my current code.
Here is my deserialization code:
conf is an XElement which represents the ComClientConfiguration xml
DataContractSerializer ser = new DataContractSerializer(typeof(ComClientConfiguration), new Type[] {typeof(ComClientConfiguration), typeof(ComOpcClientConfiguration) });
ComOpcClientConfiguration config = (ComOpcClientConfiguration)ser.ReadObject(conf.CreateReader());
I don't know why I have to have ComClientConfiguration and ComOpcClientConfiguration, there's probably a better way to do the known types hack I have. But for now it's what I have.
Here is the xml as it looks in the file.
<ComClientConfiguration xsi:type="ComOpcClientConfiguration" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<ServerUrl>The url</ServerUrl>
<ServerName>a server name </ServerName>
<ServerNamespaceUrl>a namespace url</ServerNamespaceUrl>
<MaxReconnectWait>5000</MaxReconnectWait>
<MaxReconnectAttempts>0</MaxReconnectAttempts>
<FastBrowsing>true</FastBrowsing>
<ItemIdEqualToName>true</ItemIdEqualToName>
<ServerUrlHda>hda url</ServerUrlHda>
</ComClientConfiguration>
Here is the class I built to deserialize into:
[DataContract(Name = "ComClientConfiguration", Namespace = "http://opcfoundation.org/UA/SDK/COMInterop")]
public class ComClientConfiguration
{
public ComClientConfiguration() { }
//Prog-ID for DA-connection
[DataMember(Name = "ServerUrl"), OptionalField]
public string ServerUrl;//url
[DataMember(Name = "ServerName")]
public string ServerName;
[DataMember(Name = "ServerNamespaceUrl")]
public string ServerNamespaceUrl;//url
[DataMember(Name = "MaxReconnectWait")]
public int MaxReconnectWait;
[DataMember(Name = "MaxReconnectAttempts")]
public int MaxReconnectAttempts;
[DataMember(Name = "FastBrowsing")]
public bool FastBrowsing;
[DataMember(Name = "ItemIdEqualToName")]
public bool ItemIdEqualToName;
//ProgID for DA-connection
[DataMember, OptionalField]
public string ServerUrlHda;//url
}
I additionally had to make this class, it's the same but with a different name. Used for known types in the Serializer because I don't know exactly how the whole type naming stuff works.
[DataContract(Name = "ComOpcClientConfiguration", Namespace = "http://opcfoundation.org/UA/SDK/COMInterop")]
public class ComOpcClientConfiguration
{
public ComOpcClientConfiguration() { }
... Same innards as ComClientConfiguration
}
Data-contract-serializer is... Fussy. In particular, I wonder if simply element order is the problem here. However, it is also not necessarily the best tool for working with XML. XmlSerializer is probably a more robust here - it can handle a much better range of XML. DCS simply isn't intended with that as it's primary goal.
With simple XML you often don't even need any attributes etc. You can even use xsd.exe on your existing XML to generate the matching c# classes (in two steps; XML-to-xsd; xsd-to-c#).
To get all values, try hardcoding the order (otherwise maybe it tries the alphabetical order):
[DataMember(Name = "ServerUrl", Order = 0)]
..
[DataMember(Name = "ServerName", Order = 1)]
..
[DataMember(Name = "ServerNamespaceUrl", Order = 2)]
..
[DataMember(Name = "MaxReconnectWait", Order = 3)]
..

Categories

Resources