I have next code:
[XmlRoot(ElementName = "Container")]
public class Container {
[XmlArray("Items", IsNullable = false)]
[XmlArrayItem("Item")]
public List<BaseItem> Items { get; set; } = new List<BaseItem>();
}
public class BaseItem {
[XmlAttribute("SomeField")]
public string SomeField {get;set;}
}
public class DerivedItem : BaseItem {
[XmlAttribute("OtherField")]
public string OtherField {get;set;}
}
How can I deserialize:
<Container>
<Items>
<Item SomeField="Value"/>
<Item SomeField="Value" OtherField="OtherValue"/>
</Items>
</Container>
so, Items field in Container object can contain BaseItem and DerivedItem objects from XML above?
Well, you can't because when deserializing the XmlSerializer does not determine when to use BaseItem or DerivedItem. So imho you should not use inheritance here.
Now what you possibly needed is to know if OtherField is specified or not.
Fortunately this is something the XmlSerializer CAN do. For this you need to add to your class representing the item a bool property OtherFieldSpecified which indicates if OtherField is well... specified.
You should use
// Define other methods and classes here
[XmlRoot(ElementName = "Container")]
public class Container {
[XmlArray("Items", IsNullable = false)]
[XmlArrayItem("Item")]
public List<DerivedItem> Items { get; set; }
}
public class DerivedItem
{
[XmlAttribute("SomeField")]
public string SomeField {get;set;}
[XmlAttribute("OtherField")]
public string OtherField {get;set;}
public bool OtherFieldSpecified {get;set;}
}
So a little adjust working linqPad scribble gave me this code:
void Main()
{
var xml = #"
<Container>
<Items>
<Item SomeField=""Value""/>
<Item SomeField=""Value"" OtherField=""OtherValue""/>
</Items>
</Container>
";
var stream = new MemoryStream(Encoding.UTF8.GetBytes(xml));
var ser = new XmlSerializer(typeof(Container));
var container = (Container) ser.Deserialize(stream);
container.Dump();
}
Can we start with you not posting valid XML?
Elements are unique. Inheritance (derived types) MUST change the element name. THis is XML standard because the element name is how the XML Schema determines what elements exist.
YOu do that then you can add multiple XmlArrayItem entries - there is a second overload that takes the type of the included item.
Related
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.
I am receiving back from a Web Service XML that looks like this:
<RESULT>
<GRP ID="INP">
<FLD NAME="SORDNO" TYPE="Char"></FLD>
<FLD NAME="SITENO" TYPE="Char">999</FLD>
</GRP>
<TAB DIM="100" ID="POS" SIZE="0"/>
<TAB DIM="500" ID="ERR" SIZE="2">
<LIN NUM="1">
<FLD NAME="ERRORS" TYPE="Char"/>
</LIN>
<LIN NUM="2">
<FLD NAME="ERRORS" TYPE="Char">Site code 999 is not valid</FLD>
</LIN>
</TAB>
I want to codify the XML into an object so I created a class that seems to match the XML structure:
namespace DropShipmentFulfillment.SagePOCreateProcess
{
public class SagePOCreateResponse1
{
[XmlRoot("RESULT")]
public class RESULT
{
[XmlElement("GRP")]
public RESULTGRP GRP;
[XmlElement("TAB")]
public RESULTTAB[] TAB;
}
public partial class RESULTGRP
{
[XmlAttribute("ID")]
public string ID;
[XmlElement("FLD")]
public RESULTFLD[] FLD;
}
public partial class RESULTTAB
{
[XmlAttribute("DIM")]
public string DIM;
[XmlAttribute("ID")]
public string ID;
[XmlAttribute("SIZE")]
public string SIZE;
[XmlElement("LIN")]
public RESULTLIN[] LIN;
}
public partial class RESULTLIN
{
[XmlAttribute("NUM")]
public string NUM;
[XmlElement("FLD")]
public RESULTFLD FLD;
}
public partial class RESULTFLD
{
[XmlAttribute("NAME")]
public string NAME;
[XmlAttribute("TYPE")]
public string TYPE;
}
}
}
When I perform the serialization I execute this code:
private SagePOCreateResponse1.RESULT GetPOResponse(SageWeb.CAdxResultXml sageSvcResponse)
{
string xml = sageSvcResponse.resultXml;
XmlSerializer serializer = new XmlSerializer(typeof(SagePOCreateResponse1.RESULT));
StringReader rdr = new StringReader(xml);
SagePOCreateResponse1.RESULT resultingMessage = (SagePOCreateResponse1.RESULT)serializer.Deserialize(rdr);
return resultingMessage;
}
When I execute the program I do not get an error indicating that the class structure does not match the XML structure, but specifically, the FLD element does not contain a value. For example, if I stop to take a look at a specific field (FLD), in GRP I get the attributes values but not the value of the element:
?objectResponse.GRP.FLD[0]
{DropShipmentFulfillment.SagePOCreateProcess.SagePOCreateResponse1.RESULTFLD}
NAME: "SORDNO"
TYPE: "Char"
It seems like the serializer is not putting the element value in place though I think I have named it right and I get no errors.
What am I missing? Why would it resolve the attributes and not the element values?
I did try doing the XSD creation to C# Class route, but when I had to deal with dynamic lines (LIN) I tried creating my own class.
Thanks to jdweng I was able to find a solution. Keeping all things the same from my question I changed one class:
public partial class RESULTFLD
{
[XmlAttribute("NAME")]
public string NAME;
[XmlAttribute("TYPE")]
public string TYPE;
[System.Xml.Serialization.XmlTextAttribute()]
public string Value;
}
I needed this line "[System.Xml.Serialization.XmlTextAttribute()]" instead of just the "[XmlText]" line.
The class now deserilizes the XML so I get both attribute values and innerText values.
I have XML Serializable class with property Name
[Serializable]
public class Item
{
[XmlElement("name")]
public string Name { get; set; }
}
and I want it to be able to deserialize XML file that I have in two ways:
<item>
<name>Name</name>
</item>
and
<item>
<name value="Name" />
</item>
The first works fine but what should I do to be able to deserialize the second also with the same class?
XML Serialization attributes work both with serialization and deserialization. If we'll assume that it might be possible to use attributes for deserializing instance of Item from two different xml structures, then how serialization should work - should it serialize instance name to element value, or to attribute? Or to both? That's why you cannot deserialize two different xml structures into single class. Use two different classes or deserialize it manually without usage of XML Serialization attributes.
I found another way to solve my problem using only one class maybe someone will find this useful
[Serializable]
public class Item
{
[XmlElement("name")]
public NameElement NameElement { get; set; }
}
public class NameElement
{
[XmlAttribute("value")]
public string Value { get; set; }
[XmlText]
public string Text { get; set; }
[XmlIgnore]
public string Name
{
get { return String.IsNullOrEmpty(this.Value) ? this.Text : this.Value; }
set { this.Value = value; }
}
}
Maybe it's not super elegant but it works in both cases and uses the same class.
Since you have mentioned that XML data is coming from external sources, so obviously you don't have control over that.
Therefore you can follow any of the option as below:
Create separate class per XML data structure, because as far I know there is no way to control XML Deserialization when using XmlSerializer
You can use XDocument to read the XML by self, to overcome this limitation.
If going by second idea, I have created small Console Application to demonstrate that.
Main piece of code is as below:
MemoryStream xmlStream = new MemoryStream(Encoding.UTF8.GetBytes(xmlData));
XDocument doc = XDocument.Load(xmlStream);
var records = from record in doc.Descendants("item").Descendants()
select new Item(!record.IsEmpty ? record.Value : record.Attribute("value").Value);
Here I'm reading the element using LinqToXml and checking if the element is not empty, i.e. Value is not blank, then use Value otherwise read the value from element's Attribute.
Console application (Complete code):
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using System.Xml.Serialization;
namespace Console.TestApp
{
class Program
{
static string xmltypeFirst = #"<item>
<name>John</name>
</item>";
static string xmltypeSecond = #"<item>
<name value='Smith' />
</item>";
static void Main(string[] args)
{
var data = xmltypeFirst;
var result = Deserialize(data).ToList();
Console.WriteLine("Name: " + result[0].Name);
data = xmltypeSecond;
result = Deserialize(data).ToList();
Console.WriteLine("Name: " + result[0].Name);
Console.WriteLine("Press any to key to exit..");
Console.ReadLine();
}
private static IEnumerable<Item> Deserialize(string xmlData)
{
MemoryStream xmlStream = new MemoryStream(Encoding.UTF8.GetBytes(xmlData));
XDocument doc = XDocument.Load(xmlStream);
var records = from record in doc.Descendants("item").Descendants()
select new Item(!record.IsEmpty ? record.Value : record.Attribute("value").Value);
return records;
}
}
[Serializable]
public class Item
{
public Item(string name)
{
this.Name = name;
}
[XmlElement("name")]
public string Name { get; set; }
}
}
Note: To run this you will need to add reference to System.Xml.Linq.dll in your project.
Reference: here
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; }
}
i want to serialize the following class as child of another class.
[XmlInclude(typeof(string)), XmlInclude(typeof(XML_Variable))]
public class XMLTagDataSection
{
[XmlElement("Offset")]
public int XML_Offset { get; set; }
[XmlElement("Type")]
public EnuDataType XML_type { get; set; }
[XmlElement("Value")]
public object XML_Value { get; set; }
[XmlElement("Info")]
public string XML_Info { get; set; }
[XmlElement("NumBytes")]
public int XML_NumBytes { get; set; }
}
public class XML_Variable
{
[XmlElement("Variable", IsNullable = false)]
public int Variable { get; set; }
}
this is my actual output:
<Data>
<Offset>0</Offset>
<Type>ASCII</Type>
<Value xmlns:q1="http://www.w3.org/2001/XMLSchema" d7p1:type>test-string</Value>
<Info>some text</Info>
<NumBytes>11</NumBytes>
</Data>
<Data>
<Offset>11</Offset>
<Type>ASCII</Type>
<Value d7p1:type="XML_Variable" xmlns:d7p1="http://www.w3.org/2001/XMLSchema-instance">
<Variable>0</Variable>
</Value>
<Info>a variable</Info>
<NumBytes>5</NumBytes>
how can i get rid of the namespace of my XML_Value element?
to get the following output:
<Data>
<Offset>0</Offset>
<Type>ASCII</Type>
<Value>test-string</Value>
<Info>some text</Info>
<NumBytes>11</NumBytes>
</Data>
<Data>
<Offset>11</Offset>
<Type>ASCII</Type>
<Value>
<Variable>0</Variable>
</Value>
<Info>a variable</Info>
<NumBytes>5</NumBytes>
i use this part of code to serialize the parent element:
XmlSerializerNamespaces NameSpace = new XmlSerializerNamespaces();
NameSpace.Add("", "");
XmlSerializer xmlserializer = new XmlSerializer(typeof(XMLRoot));
FileStream str = new FileStream(fileName, FileMode.Create);
xmlserializer.Serialize(str, Root_Tag, NameSpace);
str.Close();
return true;
how can i get rid of the namespace of my XML_Value element? to get the following output:
Easy: it doesn't have one. The namespace of an element can either be specified with an alias prefix:
<foo:Value ...>
or via the reserved xmlns attribute:
<Value xmlns="...." ...>
What you are referring to is the namespace qualifiers for the additional information that XmlSerializer needs to deserialize that data. It only needs it because the type is object, and being dutiful, it wants to be able to understand that data later - otherwise it could never ever deserialize it. One option, then, is to declare your types correctly:
[XmlElement("Value")]
public XML_Variable XML_Value { get; set; }
which will then not need this additional information.
If none of these are possible, then XmlSerializer is not a good choice for you. Consider using XDocument (LINQ-to-XML) or XmlDocument instead.
The entire point of a serializer is to be able to transform the data in both directions, and that will never be possible for object without additional annotations.