C# XML Serialization exclude writing class name - c#

The layout I'm going for is like the below layout (this is part of a bigger structure):
<Products>
<R001 Retail=\"2.289\" Rank=\"1\" Code=\"001\" />
<R002 Retail=\"2.289\" Rank=\"2\" Code=\"002\" />
<R003 Retail=\"2.889\" Rank=\"3\" Code=\"003\" />
<R004 Retail=\"0\" Rank=\"4\" Code=\"0\" />
<R011 Retail=\"0\" Rank=\"7\" Code=\"0\" />
</Products>
So I have a class that has a property called Products. That property is a list of a class I call ProductExport. ProductExport has 3 properties that I mark with the XmlAttribute attribute and they are called Retail, Rank, Code. In ProductExport I implement IXmlSerializable so I can implement WriteXml() in which I make the tag names those R00X tags. That all works fine, however since ProductExport is it's own class the XmlSerializer writes a tag for each ProductExport in the list. I don't want a ProductExport tag. I would have thought by implementing IXmlSerializable on ProductExport I would control everything about how a ProductExport gets written, including NOT writing it's class name, but it seems to not be the case. How do I restrict writing it's class name?
public class StoresExport
{
public int ID { get; set; }
public int Name { get; set; }
public string State { get; set; }
public int DistrictID { get; set; }
public int? RegionID { get; set; }
public decimal Latitude { get; set; }
public decimal Longitude { get; set; }
public List<CompetitorLocation> RelatedLocations { get; set; }
public List<ProductExport> Products { get; set; }
}
public class ProductExport : IXmlSerializable
{
public float Retail { get; set; }
public int Rank { get; set; }
public string Code { get; set; }
public XmlSchema GetSchema()
{
return null;
}
public void ReadXml(XmlReader reader)
{
// we aren't ever reading just writing
}
public void WriteXml(XmlWriter writer)
{
writer.WriteStartElement("R" + Code);
writer.WriteAttributeString("Retail", Retail.ToString());
writer.WriteAttributeString("Rank", Rank.ToString());
writer.WriteAttributeString("Code", Code);
writer.WriteEndElement();
}
}
With that code this is the output which I don't want:
<Products>
<ProductExport>
<R001 Retail=\"0\" Rank=\"1\" Code=\"001\" />
</ProductExport>
<ProductExport>
<R002 Retail=\"0\" Rank=\"2\" Code=\"002\" />
</ProductExport>
<ProductExport>
<R003 Retail=\"0\" Rank=\"3\" Code=\"003\" />
</ProductExport>
<ProductExport>
<R004 Retail=\"0\" Rank=\"4\" Code=\"004\" />
</ProductExport>
<ProductExport>
<R011 Retail=\"0\" Rank=\"7\" Code=\"011\" />
</ProductExport>
</Products>

Per the docs, it's the parent element that will write the start and end elements for ProductExport:
The WriteXml implementation you provide should write out the XML representation of the object. The framework writes a wrapper element and positions the XML writer after its start. Your implementation may write its contents, including child elements. The framework then closes the wrapper element.
So to do this, you need to write the element name you want within Products. The simplest way to do this is to replace the List<ProductExport> with your own class that also implements IXmlSerializable.
When this is serialized, the framework will have already written the Product element (and will close it after), so, sticking with the contract, all you have to write is the start and end elements for each of your items and delegate the writing of their content to their implementation of IXmlSerializable:
public class Products : IXmlSerializable, IEnumerable<ProductExport>
{
private readonly List<ProductExport> _products = new List<ProductExport>();
public void Add(ProductExport product) => _products.Add(product);
public XmlSchema GetSchema() => null;
public void ReadXml(XmlReader reader)
{
throw new NotSupportedException();
}
public void WriteXml(XmlWriter writer)
{
foreach (var product in this)
{
writer.WriteStartElement("R" + product.Code);
product.WriteXml(writer);
writer.WriteEndElement();
}
}
public IEnumerator<ProductExport> GetEnumerator() => _products.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
See this fiddle for a working demo.

Related

XmlReader stops deserializing after first array property

I have a custom list used to have features of BindingList<T> while also being XML serializable. The SBindingList<T> class is as follows:
public class SBindingList<T> : BindingList<T>, IXmlSerializable
{
public XmlSchema GetSchema()
{
return null;
}
public void ReadXml(XmlReader reader)
{
reader.Read();
XmlSerializer serializer = new XmlSerializer(typeof(List<T>));
List<T> ts = (List<T>)serializer.Deserialize(reader);
foreach (T item in ts)
{
Add(item);
}
}
public void WriteXml(XmlWriter writer)
{
XmlSerializer serializer = new XmlSerializer(typeof(List<T>));
serializer.Serialize(writer, this.ToList());
}
}
The idea is to read and write the XML as if it was a List<T> behind the scenes but still works and acts like a BindingList<T>. The issue I'm having is that it starts reading <Games> then when it gets to Add(item); starts to deserialize <AvailableQuests>. Because <AvailableQuests> is empty it just falls out of ReadXml but goes immediately back finishing up adding <Games> and just falls out of ReadXml without even touching <Characters> or <ActiveQuests>. The <Games>, <AvailableQuests>, <Characters>, and <ActiveQuests> are all SBindingList<T>s. Also note that I redacted all the IDs for privacy reasons. Just replace the [blah] tags with any ulong.
<Games>
<ArrayOfGame xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Game GuildID="[GuildID]" BotChannel="0" QuestChannel="0">
<AvailableQuests>
<ArrayOfQuest />
</AvailableQuests>
<Characters>
<ArrayOfCharacter>
<Character Name="The Real Dirty Dan" Class="Meme" Level="17"
OwnerID="[Owner1]" Busy="false" />
<Character Name="Bob" Class="Builder" Level="2" OwnerID="[Owner2]"
Busy="false" />
</ArrayOfCharacter>
</Characters>
<ActiveQuests>
<ArrayOfQuest />
</ActiveQuests>
</Game>
</ArrayOfGame>
</Games>
Here is the object setup incase someone needs it:
public class Game
{
/// <summary>
/// Only for serialization
/// </summary>
public Game() { }
public Game(ulong guildID)
{
GuildID = guildID;
}
/// <summary>
/// What discord server is the game on
/// </summary>
[XmlAttribute]
public ulong GuildID { get; set; }
[XmlAttribute]
public ulong BotChannel { get; set; }
[XmlAttribute]
public ulong QuestChannel { get; set; }
public SBindingList<Quest> AvailableQuests { get; set; } = new SBindingList<Quest>();
public SBindingList<Character> Characters { get; set; } = new SBindingList<Character>();
public SBindingList<Quest> ActiveQuests { get; set; } = new SBindingList<Quest>();
public string Test { get; set; } // This gets ignored too
public Character GetCharacterByName(string name)
{
return (from c in Characters
where c.Name == name
select c).FirstOrDefault();
}
}
I don't really know where to start with this one. I've tried using a List<T> or just reading each T one at a time but both ways end up ignoring all other elements. My only guess is that I need to clean something up with the reader before I can let it fall out of ReadXml like reader.FinishedReading().
The answer to this lies in the documentation for ReadXml:
When this method is called, the reader is positioned on the start tag that wraps the information for your type. That is, directly on the start tag that indicates the beginning of a serialized object. When this method returns, it must have read the entire element from beginning to end, including all of its contents. Unlike the WriteXml method, the framework does not handle the wrapper element automatically.
You seem to have spotted this by calling reader.Read() at the start (to move past the start tag and onto the content), but you haven't at the end. The fix is to call reader.Read() again afterwards.
Perhaps a better approach would be to call reader.ReadStartElement() first and reader.ReadEndElement() at the end. This just calls reader.Read() internally, but it will throw an exception in the case the reader isn't in the expected state.
I'd also suggest sharing a static serialiser instance rather than creating a new one each time you read or write a list. So this should work:
public class SBindingList<T> : BindingList<T>, IXmlSerializable
{
private static readonly XmlSerializer Serializer = new XmlSerializer(typeof(List<T>));
public XmlSchema GetSchema()
{
return null;
}
public void ReadXml(XmlReader reader)
{
reader.ReadStartElement();
var items = (List<T>)Serializer.Deserialize(reader);
foreach (T item in items)
{
Add(item);
}
reader.ReadEndElement();
}
public void WriteXml(XmlWriter writer)
{
Serializer.Serialize(writer, this.ToList());
}
}
If reader is on any other element besides the next element, the reader gives up deserizing the rest of the object. In other words, the reader must be on the next element (In this case the Character element) to continue to deserialize the <Game> tag. In this case it can be fixed by using reader.Skip() then reader.ReadEndElement()
The reader needs to be at the next element (in this case <Characters>) in the original object otherwise it just returns if it is at a end element node. This solution isn't failproof or the cleanest but it gets the job done. If I find something better that confirms that it is at the next elemet before falling out I will edit this answer.
public void ReadXml(XmlReader reader)
{
reader.Read();
XmlSerializer serializer = new XmlSerializer(typeof(List<T>));
XmlReader xmlReader = reader.ReadSubtree();
xmlReader.Read();
List<T> ts = (List<T>)serializer.Deserialize(xmlReader);
foreach (T item in ts)
{
Add(item);
}
xmlReader.Close();
reader.Skip();
reader.ReadEndElement();
}
The key is reader.Skip() and reader.ReadEndElement() lines. The Skip() function moves the reader to </AvailableQuests> and the ReadEndElement() makes sure that it is an end element (Like it needs to be) and moves to <Characters>.
Edit: use https://stackoverflow.com/a/71969803/4771954
Try following :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Serialization;
namespace ConsoleApplication23
{
class Program
{
const string FILENAME = #"c:\temp\test.xml";
static void Main(string[] args)
{
XmlReader reader = XmlReader.Create(FILENAME);
XmlSerializer serializer = new XmlSerializer(typeof(Games));
Games games = (Games)serializer.Deserialize(reader);
}
}
public class Games
{
[XmlArray(ElementName = "ArrayOfGame")]
[XmlArrayItem(ElementName = "Game")]
public List<Game> game { get; set; }
}
public class Game
{
[XmlAttribute()]
public string GuildID { get; set; }
[XmlAttribute()]
public int BotChannel { get; set; }
[XmlAttribute()]
public int QuestChannel { get; set; }
[XmlArray(ElementName = "AvailableQuests")]
[XmlArrayItem(ElementName = "ArrayOfQuest")]
public List<Quest> availableQuest { get; set; }
[XmlElement(ElementName = "Characters")]
public Characters characters { get; set; }
[XmlArray(ElementName = "ActiveQuests")]
[XmlArrayItem(ElementName = "ArrayOfQuest")]
public List<Quest> activeQuest { get; set; }
public class Quest
{
}
public class Characters
{
[XmlArray(ElementName = "ArrayOfCharacter")]
[XmlArrayItem(ElementName = "Character")]
public List<Character> character { get; set; }
}
public class Character
{
[XmlAttribute()]
public string Name { get; set; }
[XmlAttribute()]
public string Class { get; set; }
[XmlAttribute()]
public int Level { get; set; }
[XmlAttribute()]
public string OwnerID { get; set; }
[XmlAttribute()]
public Boolean Busy{ get; set; }
}
public class ArrayOfQuest
{
}
}}

Disable emmiting empty list elements in Xml Serializer in C#

I would like to disable emmiting empty elements in Xml in List.
I know about PropertyNameSpecified pattern but I don't know how to apply it to the list.
I have list of elements and it is being serialized. Some of those elements are empty and are producing empty Xml elements in that list (what I don't want).
My sample code:
public class ConditionsCollectionModel
{
[XmlElement("forbidden")]
public List<ForbiddenModel> ForbiddenCollection { get; set; }
[XmlElement("required")]
public List<RequiredModel> RequiredCollection { get; set; }
}
public class ForbiddenModel : IXmlSerializable
{
public string Value { get; set; }
public XmlSchema GetSchema()
{
return null;
}
public void ReadXml(XmlReader reader)
{
Value = reader.ReadElementString("forbidden");
}
public void WriteXml(XmlWriter writer)
{
writer.WriteString(Value);
}
}
public class RuleModel
{
[XmlElement("name")]
public string Name { get; set; }
[XmlElement("conditions")]
public ConditionsCollectionModel Conditions { get; set; }
}
It produces Xml in form like:
<rule>
<name>SR</name>
<conditions>
<forbidden />
<forbidden />
<forbidden>Ftest</forbidden>
<required>test</required>
<required>test2</required>
</conditions>
</rule>
I don't want those empty elements in conditions list.
The question is rather old but I experienced the same problem today. So for anybody reading this topic, this is the solution that works for me.
Add "ShouldSerialize{PropertyName}"
public class ConditionsCollectionModel
{
[XmlElement("forbidden")]
public List<ForbiddenModel> ForbiddenCollection { get; set; }
[XmlElement("required")]
public List<RequiredModel> RequiredCollection { get; set; }
public bool ShouldSerializeForbiddenCollection(){
return (ForbiddenCollection !=null && ForbiddenCollection.Count>0);
}
see: MSDN
Since ForbiddenCollection is list of Forbidden Class, you can set value of an empty ForbiddenClass object to null while inserting into the List.

C# Deserialize XML to a list of ChildClasses

I have a XML from a 3rd party software that I have to deserialize, but I have the spec of the XML.
At a certain point I have a node that contains a bunch of different Nodes of different types. I have to deserialize this Node (Vehicles) as a list. Each of the child nodes is one subclass of the class Vehicle
public List<Vehicle> Vehicles = new List<Vehicles>();
the definition of the class is as following
public class Vehicle
{
public string Vehicletype { get; set; }
public virtual bool Drive() { return true; }
}
public class Car:Vehicle
{
public int NumberOfDoors { get; set; }
public override bool Drive() { return false; }
}
public class Boat:Vehicle
{
public int NumberOfSeats { get; set; }
}
The definition of the XML looks like this
<vehicles>
<car NumberOfDoors="4" />
<car NumberOfDoors="3" />
<boat NumberOfSeats="1024" />
<boat NumberOfSeats="20" />
<car NumberOfDoors="5" />
</vehicles>
At a certain point I have to loop through them and let them "Drive". I don't have the experience With XMLSchema definitions. Now I am using The vehicleType in a factory and some other things. It's basically becoming a mess.
I would need a suggestion how to let the Serializer do this instead of me. I doubt that I am the first one to have this issue.
Firstly, you have to mark NumberofSeats and NumberOfDoors with the XmlAttribute attribute to tell XmlSerializer that these properties should appear as XML attributes:
public class Car : Vehicle
{
[XmlAttribute]
public int NumberOfDoors { get; set; }
public override bool Drive() { return false; }
}
public class Boat : Vehicle
{
[XmlAttribute]
public int NumberOfSeats { get; set; }
}
Next, if <vehicles> appears as an XML Node inside some containing root element, you can make your public List<Vehicle> Vehicles appear as a member inside the corresponding container class, and apply an XmlArray attribute to it to indicate that the list should be serialized in two levels, and XmlArrayItem attributes to inform XmlSerializer of the possible of vehicles that might be encountered in the list along with the start tag to use for each:
public class VehiclesContainer // Your container class
{
[XmlArray("vehicles")]
[XmlArrayItem("vehicle", typeof(Vehicle))]
[XmlArrayItem("car", typeof(Car))]
[XmlArrayItem("boat", typeof(Boat))]
public List<Vehicle> Vehicles = new List<Vehicle>();
}
On the other hand, <vehicles> is the root node of the XML document and there is no containing node, you can introduce a root node class containing the list of vehicles, then apply multiple XmlElement attributes to indicate that the list should be serialized in one level rather than two along with the start tag to use for each possible vehicle type:
[XmlRoot("vehicles")]
public class VehicleList
{
[XmlElement("vehicle", typeof(Vehicle))]
[XmlElement("car", typeof(Car))]
[XmlElement("boat", typeof(Boat))]
public List<Vehicle> Vehicles = new List<Vehicle>();
}
Having done this, XmlSerializer will automatically deserialize your class hierarchy.
For more information, see here.

C# deserializing nested elements with only one item

I use C# to deserialize a XML file. My XML file has the format:
<Produced_by >
<Producing_Unit>
<Unit ID="" Name=""/>
</Producing_Unit>
</Produced_by>
When deserializing I want to remove the middleman Producing_Unit. Since Produced_by will always contain only one subelement Producing_Unit specifying the Unit.
My initial thoughts on how to implement doesn't work:
public class Unit
{
public string Name { get; set; }
public string ID { get; set; }
}
public class Produced_by
{
[XmlElement("Producing_Unit")]
[XmlElement("Unit")]
public Unit Unit { get; set; }
}
It could be soleved by using [XmlArray("Producing_Unit"), XmlArrayItem("Unit")]
and then having Produced_by contain: public List<Unit> {get;set;}. But that's not what I want.
As far as I know, it's not possible to use the XML with the "Producing_Unit" tag but ditch the matching Producing_Unit class with the standard attributes short of implementing the IXmlSerializable interface. Your best bet would be to separate your application/business logic from your serialization layer.
Keep your serialization data model simple and matching your XML schema (this means including the wrapping Producing_Unit class), then simply convert to/from that data model and a cleaner data model (without Producing_Unit) for the rest of your application to work with.
EDIT: Here's an implementation using the IXmlSerializable interface. I just whipped it off and honestly, don't know if it will work for all cases.
public class Unit
{
public string Name { get; set; }
public string ID { get; set; }
}
public class Produced_by : IXmlSerializable
{
public Unit Unit { get; set; }
public void WriteXml (XmlWriter writer)
{
writer.WriteStartElement("Produced_by");
writer.WriteStartElement("Producing_Unit");
writer.WriteStartElement("Unit");
writer.WriteAttributeString("ID", this.Unit.ID);
writer.WriteAttributeString("Name", this.Unit.Name);
writer.WriteEndElement();
writer.WriteEndElement();
writer.WriteEndElement();
}
public void ReadXml (XmlReader reader)
{
while (reader.Read())
{
if (reader.Name == "Unit")
{
this.Unit = new Unit()
{
Name = reader.GetAttribute("Name"),
ID = reader.GetAttribute("ID")
};
break;
}
}
}
public XmlSchema GetSchema()
{
return(null);
}
}
I suspect that I'm performing the reading in a poor fashion, but this works in my local test. I still recommend separating your application and serialization concerns though and avoiding having to write an implementation like this.
You could try this:
public class Unit
{
public string Name { get; set; }
public string ID { get; set; }
}
public class Producing_Unit
{
public Unit Unit { get; set; }
}
public class Produced_by
{
private Producing_Unit producing_unit;
public Producing_Unit Producing_Unit //This can't be auto-implemented since can write to properties of properties.
{
get { return producing_Unit; }
set { producing_Unit = value; }
}
[XmlIgnoreAttribute]
public Unit Unit
{
get { return producing_Unit.Unit; }
set { producing_Unit.Unit = value; }
}
}
Yes, it doesn't get rid of the 'middle-men,' but you can ignore them.

CollectionDataContract serialization not adding custom properties (DataMember)

We have a legacy system that needs to be fed (XML) data in a most unstructured format. Is the following even possible with the .NET DataContractSerializer?
Given the following DataContracts
[CollectionDataContract(Name = "Options", ItemName = "Option")]
public class OptionItemCollection : List<OptionItem>
{
[DataMember(Name = "Name")]
public string Name { get; set; }
public OptionItemCollection()
{
}
public OptionItemCollection(IEnumerable<OptionItem> items) : base(items)
{
}
}
[DataContract(Name = "Option")]
public class OptionItem
{
[DataMember]
public string Text { get; set; }
[DataMember]
public string Value { get; set; }
}
Is it possible to serialize this collection directly into the following XML representation:
<Options>
<Name>Juices</Name>
<Option Value="1">Orange Juice</Option>
<Option Value="2">Pineapple</Option>
<Option Value="3">Fruit Punch</Option>
</Options>
NOTE:
This is exactly how the legacy system expects the data to be submitted.
Or Even:
<Options>
<Name>Juices</Name>
<Option><Value>1</Value><Text>Orange Juice</Text></Option>
<Option><Value>2</Value><Text>Pineapple</Text></Option>
<Option><Value>3</Value><Text>Fruit Punch</Text></Option>
</Options>
Also NOTE that the emphasis is on the Name and Option element residing within the Options element.
Yes. Although the DataContractSerializer doesn't explicitly support XML attributes, you can hand-roll it. Try this:
[CollectionDataContract(Name = "Options", ItemName = "Option")]
public class OptionItemCollection : List<OptionItem>
{
[DataMember(Name = "Name")]
public string Name { get; set; }
public OptionItemCollection()
{
}
public OptionItemCollection(IEnumerable<OptionItem> items)
: base(items)
{
}
}
// note, remove attributes
public class OptionItem : IXmlSerializable
{
public string Text { get; set; }
public string Value { get; set; }
public void WriteXml(XmlWriter writer)
{
writer.WriteAttributeString("Value", Value);
writer.WriteElementString("Text", Text);
}
public void ReadXml(XmlReader reader)
{
// implement if necessary
throw new NotImplementedException();
}
public System.Xml.Schema.XmlSchema GetSchema()
{
throw new NotImplementedException();
}
}
No, this is not possible with the DataContractSerializer (DCS). The DCS doesn't allow unwrapped collection elements. So you cannot have this:
<a>
<b/>
<b/>
<b/>
<c/>
</a>
But you can have this:
<a>
<bb>
<b/>
<b/>
<b/>
</bb>
<c/>
</a>
In your scenario you'll need to use the XmlSerializer.

Categories