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
{
}
}}
Related
ACTUAL code and xml file
Code of program
class Program
{
static void Main(string[] args)
{
string path = AppDomain.CurrentDomain.BaseDirectory + "file.xml";
//string path2 = #"F:\fd.xml";
FileStream fs = new FileStream(path, FileMode.Open);
XmlReader reader = XmlReader.Create(fs);
SaveGame sav = new XmlSerializer(typeof(SaveGame)).Deserialize(reader) as SaveGame;
Console.WriteLine(sav.player.friendshipData[0].key);
Console.WriteLine(sav.player.friendshipData[0].value.Points);
fs.Close();
Console.ReadKey();
}
}
public class SaveGame
{
public Player player { get; set; }
}
public class Player
{
public item[] friendshipData { get; set; }
}
public class item
{
public string key { get; set; }
public Friendship value { get; set; }
}
public class Friendship
{
public int Points {get;set;}
}
}
XML File to work with:
<SaveGame>
<player>
<friendshipData>
<item>
<key>
<string>Name1</string>
</key>
<value>
<Friendship>
<Points>324</Points>
</Friendship>
</value>
</item>
<item>
<key>
<string>Name2</string>
</key>
<value>
<Friendship>
<Points>98</Points>
</Friendship>
</value>
</item>
</friendshipData>
</player>
</SaveGame>
I tried other posts, and that's not working, cause all readen values are null.
Please, how to deserialize this document? And with explanation, please.
If i set {get;set;} to variables, it won't read next item, if i set {get;} it read every item, but every item have null value.
Just in case, i can't edit XML File for some reasons. XML File is alright.
Your data-structure doesn´t really fit your xml well. If you have a simple data-type such as int or string you can serialize and de-serialize directly into an xml-node. If you have some more complex data-structure like your Firnedship-node you need a nested node.
Having said this your data-structure should be similar to thie following:
public class SaveGame
{
public Player player { get; set; }
}
public class Player
{
public item[] friendshipData { get; set; }
}
public class item
{
public Key key { get; set; }
public Friendship value { get; set; }
}
// add this class with a single string-field
public class Key
{
public string #string { get; set; }
}
public class Friendship
{
public int Points { get; set; }
}
As an aside consider following naming-conventions, which is giving classes and members of those classes PascaleCase-names, e.g. FriendshipData, Item, and Key. This however assumes you have some mapping from those names to your names within the xml. This can be done by using XmlElementAttribute:
public class Player
{
[XmlElement("friendshipData ")] // see here how to change the name within the xml
public item[] FriendshipData { get; set; }
}
Since you only need two values from Xml I would not use serialization. You can get a dictionary with one linq instruction.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
namespace ConsoleApplication51
{
class Program
{
const string FILENAME = #"c:\temp\test.xml";
static void Main(string[] args)
{
XDocument doc = XDocument.Load(FILENAME);
Dictionary<string, int> players = doc.Descendants("item")
.GroupBy(x => (string)x.Descendants("string").FirstOrDefault(), y => (int)y.Descendants("Points").FirstOrDefault())
.ToDictionary(x => x.Key, y => y.FirstOrDefault());
}
}
}
I have small problem - XML deserialization completely ignores items, which are out of alphabetic order. In example object (description in end of question), Birthday node is after FirstName node, and it is ignored and assigned default value after deserialization. Same for any other types and names (I had node CaseId of Guid type after node Patient of PatientInfo type, and after deserialization it had default value).
I'm serializing it in one application, using next code:
public static string SerializeToString(object data)
{
if (data == null) return null;
XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
ns.Add("", "");
// what should the XmlWriter do?
var settings = new XmlWriterSettings
{
OmitXmlDeclaration = true,
NewLineChars = ""
};
using (var stringwriter = new System.IO.StringWriter())
{
// Use an XmlWriter to wrap the StringWriter
using (var xmlWriter = XmlWriter.Create(stringwriter, settings))
{
var serializer = new XmlSerializer(data.GetType(), "");
// serialize to the XmlWriter instance
serializer.Serialize(xmlWriter, data, ns);
return stringwriter.ToString();
}
}
}
Such approach was used to get proper result as argument for WebMethod (full problem described here). Results are something like this:
<PatientInfo><FirstName>Foo</FirstName><Birthday>2015-12-19T16:21:48.4009949+01:00</Birthday><RequestedClientID>00000000-0000-0000-0000-000000000000</RequestedClientID>00000000-0000-0000-0000-000000000000</patientId></PatientInfo>
Also I'm deserializing it in another application in simple manner
public static T Deserialize<T>(string xmlText)
{
if (String.IsNullOrEmpty(xmlText)) return default(T);
using (var stringReader = new StringReader(xmlText))
{
var serializer = new XmlSerializer(typeof(T));
return (T)serializer.Deserialize(stringReader);
}
}
Example object:
[XmlRoot("PatientInfo")]
public class PatientInfo
{
[XmlElement("FirstName")]
public string FirstName { get; set; }
[XmlElement("LastName")]
public string LastName { get; set; }
[XmlElement("SSN")]
public string SSN { get; set; }
[XmlElement("Birthday")]
public DateTime? Birthday { get; set; }
[XmlElement("RequestedClientID")]
public Guid RequestedClientID { get; set; }
[XmlElement("patientId")]
public Guid patientId { get; set; }
}
So, I'd like to have answer for one of two questions - 1) How can I adjust my serialization to have all items in alphabetical order? 2) How can I adjust my deserialization, so it won't ignore items out of alphabetical order?
Any help is appreciated.
Update:
Just figured out, that deserialization method I'm using is not actually used at all in my problem, since I'm using serialized info as data with WebMethod, and it is deserialized with some internal mechanism of WCF.
WCF uses DataContractSerializer. This serializer is sensitive to XML element order, see Data Member Order. There's no quick way to disable this, instead you need to replace the serializer with XmlSerializer.
To do this, see Using the XmlSerializer Class, then and apply [XmlSerializerFormat] to your service, for instance:
[ServiceContract]
[XmlSerializerFormat]
public interface IPatientInfoService
{
[OperationContract]
public void ProcessPatientInfo(PatientInfo patient)
{
// Code not shown.
}
}
[XmlRoot("PatientInfo")]
public class PatientInfo
{
[XmlElement("FirstName")]
public string FirstName { get; set; }
[XmlElement("LastName")]
public string LastName { get; set; }
[XmlElement("SSN")]
public string SSN { get; set; }
[XmlElement("Birthday")]
public DateTime? Birthday { get; set; }
[XmlElement("RequestedClientID")]
public Guid RequestedClientID { get; set; }
[XmlElement("patientId")]
public Guid patientId { get; set; }
}
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.
yet another XML Deserialization question.
I have checked several other threads and tried most of the solutions there, but to no avail.
The XML I receive can't be modded (or at least not easily) here it is:
<?xml version=\"1.0\" encoding=\"UTF-8\"?>
<ActueleVertrekTijden>
<VertrekkendeTrein>
<RitNummer>37047</RitNummer>
<VertrekTijd>2012-11-13T15:40:00+0100</VertrekTijd>
<EindBestemming>Sneek</EindBestemming>
<TreinSoort>Stoptrein</TreinSoort>
<Vervoerder>Arriva</Vervoerder>
<VertrekSpoor wijziging=\"false\">3</VertrekSpoor>
</VertrekkendeTrein>
<VertrekkendeTrein>
<RitNummer>10558</RitNummer>
<VertrekTijd>2012-11-13T15:46:00+0100</VertrekTijd>
<EindBestemming>Rotterdam Centraal</EindBestemming>
<TreinSoort>Intercity</TreinSoort>
<RouteTekst>Heerenveen, Steenwijk, Utrecht C</RouteTekst>
<Vervoerder>NS</Vervoerder>
<VertrekSpoor wijziging=\"false\">4</VertrekSpoor>
</VertrekkendeTrein>
<VertrekkendeTrein>
<RitNummer>37349</RitNummer>
<VertrekTijd>2012-11-13T15:59:00+0100</VertrekTijd>
<EindBestemming>Groningen</EindBestemming>
<TreinSoort>Sneltrein</TreinSoort>
<RouteTekst>Buitenpost</RouteTekst>
<Vervoerder>Arriva</Vervoerder>
<VertrekSpoor wijziging=\"false\">5b</VertrekSpoor>
</VertrekkendeTrein>
</ActueleVertrekTijden>
There are more elements (always a minumum of 10)
Now these are the classes I am deserializing too:
[Serializable, XmlRoot(ElementName="ActueleVertrekTijden", DataType="VertrekkendeTrein", IsNullable=false)]
public class ActueleVertrekTijden
{
[XmlArray("ActueleVertrekTijden")]
public VertrekkendeTrein[] VertrekLijst { get; set; }
}
[Serializable]
public class VertrekkendeTrein
{
[XmlElement("RitNummer")]
public string RitNummer { get; set; }
[XmlElement("VertrekTijd")]
public string VertrekTijd { get; set; }
[XmlElement("EindBestemming")]
public string EindBestemming { get; set; }
[XmlElement("Vervoerder")]
public string Vervoerder { get; set; }
[XmlElement("VertrekSpoor")]
public string VertrekSpoor { get; set; }
}
I omitted the others for the time being. The XmlRoot part I added because I got a "xmlsn="-error. So had to set the XmlRoot.
Now the Deserializer:
public ActueleVertrekTijden Deserialize<ActueleVertrekTijden>(string s)
{
var ser = new XmlSerializer(typeof(ActueleVertrekTijden));
ActueleVertrekTijden list = (ActueleVertrekTijden)ser.Deserialize(new StringReader(s));
return list;
}
It does return a ActueleVertrekTijden class but the VertrekLijst array remains null
You need to omit the wrapper namespace, because your array elements are appearing directly below the container ActueleVertrekTijden class, without any collection wrapper element. i.e. change
[XmlArray("ActueleVertrekTijden")]
public VertrekkendeTrein[] VertrekLijst { get; set; }
to
[XmlElement("VertrekkendeTrein")]
public VertrekkendeTrein[] VertrekLijst { get; set; }
Reference here
I got the following code:
public class Alarm:IDisposable
{
MemoryStream _tmp;
readonly XmlDocument _doc;
private readonly XmlSerializerNamespaces _ns;
private readonly XmlSerializer _x = new XmlSerializer(typeof(Alarm));
public int? ID { get; set; }
public string SourceSystem { get; set; }
public string SensorName { get; set; }
public string ModelName { get; set; }
public int? Severity { get; set; }
public int? Duration { get; set; }
public bool? Status { get; set; }
public DateTime? StartTime { get; set; }
public DateTime? EndTime { get; set; }
public Alarm()
{
_tmp = new MemoryStream();
_doc = new XmlDocument();
_ns = new XmlSerializerNamespaces();
_ns.Add("", "");
}
public string OuterXml()
{
//Add an empty namespace and empty value
_x.Serialize(_tmp, this, _ns);
_tmp.Position = 0;
_doc.Load(_tmp);
return _doc.OuterXml;
}
public void Dispose()
{
if (_tmp!=null)
{
_tmp.Close();
_tmp = null;
}
}
}
I get as output"
<?xml version=\"1.0\"?>
<Alarm><ID>1</ID><SourceSystem>HMer</SourceSystem>
<SensorName>4</SensorName><Severity d2p1:nil=\"true\" xmlns:d2p1=\"http://www.w3.org/2001/XMLSchema-instance\" />
<Duration>500</Duration><Status>true</Status>
<StartTime>2011-07-19T12:29:51.171875+03:00</StartTime>
<EndTime d2p1:nil=\"true\"
xmlns:d2p1=\"http://www.w3.org/2001/XMLSchema-instance\" /></Alarm>
I wanna get:
<Alarm><ID>1</ID><SourceSystem>HMer</SourceSystem><SensorName>4</SensorName>
<Duration>500</Duration><Status>true</Status>
<StartTime>2011-07-19T12:29:51.171875+03:00</StartTime></Alarm>
meaning no xmlns stuff, no tag where value is null.
please assist meh
Add the following properties to your Nullable fields:
[XmlIgnore]
public bool EndTimeSpecified { get { return EndTime.HasValue; } }
This lets XmlSerializer (which is a bit dumb about Nulllable fields) realize that no EndTime has been specified, and, thus, the field does not need to be serialized.
Here's the documentation of this feature:
Another option is to use a special pattern to create a Boolean field recognized by the XmlSerializer, and to apply the XmlIgnoreAttribute to the field. The pattern is created in the form of propertyNameSpecified. For example, if there is a field named "MyFirstName" you would also create a field named "MyFirstNameSpecified" that instructs the XmlSerializer whether to generate the XML element named "MyFirstName".
Here's a related SO question that offers more details and explanations:
How to make a value type nullable with .NET XmlSerializer?
About removing the XML processing instruction (i.e., the <?xml ... ?> part), there's also a SO question about that:
Omitting XML processing instruction when serializing an object