How to XML deserialize Dictionary, that hidden in other element? - c#

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

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
{
}
}}

How to deserialize a given xml document [duplicate]

How do I Deserialize this XML document:
<?xml version="1.0" encoding="utf-8"?>
<Cars>
<Car>
<StockNumber>1020</StockNumber>
<Make>Nissan</Make>
<Model>Sentra</Model>
</Car>
<Car>
<StockNumber>1010</StockNumber>
<Make>Toyota</Make>
<Model>Corolla</Model>
</Car>
<Car>
<StockNumber>1111</StockNumber>
<Make>Honda</Make>
<Model>Accord</Model>
</Car>
</Cars>
I have this:
[Serializable()]
public class Car
{
[System.Xml.Serialization.XmlElementAttribute("StockNumber")]
public string StockNumber{ get; set; }
[System.Xml.Serialization.XmlElementAttribute("Make")]
public string Make{ get; set; }
[System.Xml.Serialization.XmlElementAttribute("Model")]
public string Model{ get; set; }
}
.
[System.Xml.Serialization.XmlRootAttribute("Cars", Namespace = "", IsNullable = false)]
public class Cars
{
[XmlArrayItem(typeof(Car))]
public Car[] Car { get; set; }
}
.
public class CarSerializer
{
public Cars Deserialize()
{
Cars[] cars = null;
string path = HttpContext.Current.ApplicationInstance.Server.MapPath("~/App_Data/") + "cars.xml";
XmlSerializer serializer = new XmlSerializer(typeof(Cars[]));
StreamReader reader = new StreamReader(path);
reader.ReadToEnd();
cars = (Cars[])serializer.Deserialize(reader);
reader.Close();
return cars;
}
}
that don't seem to work :-(
How about you just save the xml to a file, and use xsd to generate C# classes?
Write the file to disk (I named it foo.xml)
Generate the xsd: xsd foo.xml
Generate the C#: xsd foo.xsd /classes
Et voila - and C# code file that should be able to read the data via XmlSerializer:
XmlSerializer ser = new XmlSerializer(typeof(Cars));
Cars cars;
using (XmlReader reader = XmlReader.Create(path))
{
cars = (Cars) ser.Deserialize(reader);
}
(include the generated foo.cs in the project)
Here's a working version. I changed the XmlElementAttribute labels to XmlElement because in the xml the StockNumber, Make and Model values are elements, not attributes. Also I removed the reader.ReadToEnd(); (that function reads the whole stream and returns a string, so the Deserialize() function couldn't use the reader anymore...the position was at the end of the stream). I also took a few liberties with the naming :).
Here are the classes:
[Serializable()]
public class Car
{
[System.Xml.Serialization.XmlElement("StockNumber")]
public string StockNumber { get; set; }
[System.Xml.Serialization.XmlElement("Make")]
public string Make { get; set; }
[System.Xml.Serialization.XmlElement("Model")]
public string Model { get; set; }
}
[Serializable()]
[System.Xml.Serialization.XmlRoot("CarCollection")]
public class CarCollection
{
[XmlArray("Cars")]
[XmlArrayItem("Car", typeof(Car))]
public Car[] Car { get; set; }
}
The Deserialize function:
CarCollection cars = null;
string path = "cars.xml";
XmlSerializer serializer = new XmlSerializer(typeof(CarCollection));
StreamReader reader = new StreamReader(path);
cars = (CarCollection)serializer.Deserialize(reader);
reader.Close();
And the slightly tweaked xml (I needed to add a new element to wrap <Cars>...Net is picky about deserializing arrays):
<?xml version="1.0" encoding="utf-8"?>
<CarCollection>
<Cars>
<Car>
<StockNumber>1020</StockNumber>
<Make>Nissan</Make>
<Model>Sentra</Model>
</Car>
<Car>
<StockNumber>1010</StockNumber>
<Make>Toyota</Make>
<Model>Corolla</Model>
</Car>
<Car>
<StockNumber>1111</StockNumber>
<Make>Honda</Make>
<Model>Accord</Model>
</Car>
</Cars>
</CarCollection>
You have two possibilities.
Method 1. XSD tool
Suppose that you have your XML file in this location C:\path\to\xml\file.xml
Open Developer Command Prompt
You can find it in Start Menu > Programs > Microsoft Visual Studio 2012 > Visual Studio Tools
Or if you have Windows 8 can just start typing Developer Command Prompt in Start screen
Change location to your XML file directory by typing cd /D "C:\path\to\xml"
Create XSD file from your xml file by typing xsd file.xml
Create C# classes by typing xsd /c file.xsd
And that's it! You have generated C# classes from xml file in C:\path\to\xml\file.cs
Method 2 - Paste special
Required Visual Studio 2012+
Copy content of your XML file to clipboard
Add to your solution new, empty class file (Shift+Alt+C)
Open that file and in menu click Edit > Paste special > Paste XML As Classes
And that's it!
Usage
Usage is very simple with this helper class:
using System;
using System.IO;
using System.Web.Script.Serialization; // Add reference: System.Web.Extensions
using System.Xml;
using System.Xml.Serialization;
namespace Helpers
{
internal static class ParseHelpers
{
private static JavaScriptSerializer json;
private static JavaScriptSerializer JSON { get { return json ?? (json = new JavaScriptSerializer()); } }
public static Stream ToStream(this string #this)
{
var stream = new MemoryStream();
var writer = new StreamWriter(stream);
writer.Write(#this);
writer.Flush();
stream.Position = 0;
return stream;
}
public static T ParseXML<T>(this string #this) where T : class
{
var reader = XmlReader.Create(#this.Trim().ToStream(), new XmlReaderSettings() { ConformanceLevel = ConformanceLevel.Document });
return new XmlSerializer(typeof(T)).Deserialize(reader) as T;
}
public static T ParseJSON<T>(this string #this) where T : class
{
return JSON.Deserialize<T>(#this.Trim());
}
}
}
All you have to do now, is:
public class JSONRoot
{
public catalog catalog { get; set; }
}
// ...
string xml = File.ReadAllText(#"D:\file.xml");
var catalog1 = xml.ParseXML<catalog>();
string json = File.ReadAllText(#"D:\file.json");
var catalog2 = json.ParseJSON<JSONRoot>();
The following snippet should do the trick (and you can ignore most of the serialization attributes):
public class Car
{
public string StockNumber { get; set; }
public string Make { get; set; }
public string Model { get; set; }
}
[XmlRootAttribute("Cars")]
public class CarCollection
{
[XmlElement("Car")]
public Car[] Cars { get; set; }
}
...
using (TextReader reader = new StreamReader(path))
{
XmlSerializer serializer = new XmlSerializer(typeof(CarCollection));
return (CarCollection) serializer.Deserialize(reader);
}
See if this helps:
[Serializable()]
[System.Xml.Serialization.XmlRootAttribute("Cars", Namespace = "", IsNullable = false)]
public class Cars
{
[XmlArrayItem(typeof(Car))]
public Car[] Car { get; set; }
}
.
[Serializable()]
public class Car
{
[System.Xml.Serialization.XmlElement()]
public string StockNumber{ get; set; }
[System.Xml.Serialization.XmlElement()]
public string Make{ get; set; }
[System.Xml.Serialization.XmlElement()]
public string Model{ get; set; }
}
And failing that use the xsd.exe program that comes with visual studio to create a schema document based on that xml file, and then use it again to create a class based on the schema document.
I don't think .net is 'picky about deserializing arrays'. The first xml document is not well formed.
There is no root element, although it looks like there is. The canonical xml document has a root and at least 1 element (if at all). In your example:
<Root> <-- well, the root
<Cars> <-- an element (not a root), it being an array
<Car> <-- an element, it being an array item
...
</Car>
</Cars>
</Root>
try this block of code if your .xml file has been generated somewhere in disk and if you have used List<T>:
//deserialization
XmlSerializer xmlser = new XmlSerializer(typeof(List<Item>));
StreamReader srdr = new StreamReader(#"C:\serialize.xml");
List<Item> p = (List<Item>)xmlser.Deserialize(srdr);
srdr.Close();`
Note: C:\serialize.xml is my .xml file's path. You can change it for your needs.
For Beginners
I found the answers here to be very helpful, that said I still struggled (just a bit) to get this working. So, in case it helps someone I'll spell out the working solution:
XML from Original Question. The xml is in a file Class1.xml, a path to this file is used in the code to locate this xml file.
I used the answer by #erymski to get this working, so created a file called Car.cs and added the following:
using System.Xml.Serialization; // Added
public class Car
{
public string StockNumber { get; set; }
public string Make { get; set; }
public string Model { get; set; }
}
[XmlRootAttribute("Cars")]
public class CarCollection
{
[XmlElement("Car")]
public Car[] Cars { get; set; }
}
The other bit of code provided by #erymski ...
using (TextReader reader = new StreamReader(path))
{
XmlSerializer serializer = new XmlSerializer(typeof(CarCollection));
return (CarCollection) serializer.Deserialize(reader);
}
... goes into your main program (Program.cs), in static CarCollection XCar() like this:
using System;
using System.IO;
using System.Xml.Serialization;
namespace ConsoleApp2
{
class Program
{
public static void Main()
{
var c = new CarCollection();
c = XCar();
foreach (var k in c.Cars)
{
Console.WriteLine(k.Make + " " + k.Model + " " + k.StockNumber);
}
c = null;
Console.ReadLine();
}
static CarCollection XCar()
{
using (TextReader reader = new StreamReader(#"C:\Users\SlowLearner\source\repos\ConsoleApp2\ConsoleApp2\Class1.xml"))
{
XmlSerializer serializer = new XmlSerializer(typeof(CarCollection));
return (CarCollection)serializer.Deserialize(reader);
}
}
}
}
Hope it helps :-)
Kevin's anser is good, aside from the fact, that in the real world, you are often not able to alter the original XML to suit your needs.
There's a simple solution for the original XML, too:
[XmlRoot("Cars")]
public class XmlData
{
[XmlElement("Car")]
public List<Car> Cars{ get; set; }
}
public class Car
{
public string StockNumber { get; set; }
public string Make { get; set; }
public string Model { get; set; }
}
And then you can simply call:
var ser = new XmlSerializer(typeof(XmlData));
var data = (XmlData)ser.Deserialize(XmlReader.Create(PathToCarsXml));
One liner:
var object = (Cars)new XmlSerializer(typeof(Cars)).Deserialize(new StringReader(xmlString));
Try this Generic Class For Xml Serialization & Deserialization.
public class SerializeConfig<T> where T : class
{
public static void Serialize(string path, T type)
{
var serializer = new XmlSerializer(type.GetType());
using (var writer = new FileStream(path, FileMode.Create))
{
serializer.Serialize(writer, type);
}
}
public static T DeSerialize(string path)
{
T type;
var serializer = new XmlSerializer(typeof(T));
using (var reader = XmlReader.Create(path))
{
type = serializer.Deserialize(reader) as T;
}
return type;
}
}
How about a generic class to deserialize an XML document
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// Generic class to load any xml into a class
// used like this ...
// YourClassTypeHere InfoList = LoadXMLFileIntoClass<YourClassTypeHere>(xmlFile);
using System.IO;
using System.Xml.Serialization;
public static T LoadXMLFileIntoClass<T>(string xmlFile)
{
T returnThis;
XmlSerializer serializer = new XmlSerializer(typeof(T));
if (!FileAndIO.FileExists(xmlFile))
{
Console.WriteLine("FileDoesNotExistError {0}", xmlFile);
}
returnThis = (T)serializer.Deserialize(new StreamReader(xmlFile));
return (T)returnThis;
}
This part may, or may not be necessary. Open the XML document in Visual Studio, right click on the XML, choose properties. Then choose your schema file.
The idea is to have all level being handled for deserialization
Please see a sample solution that solved my similar issue
<?xml version="1.0" ?>
<TRANSACTION_RESPONSE>
<TRANSACTION>
<TRANSACTION_ID>25429</TRANSACTION_ID>
<MERCHANT_ACC_NO>02700701354375000964</MERCHANT_ACC_NO>
<TXN_STATUS>F</TXN_STATUS>
<TXN_SIGNATURE>a16af68d4c3e2280e44bd7c2c23f2af6cb1f0e5a28c266ea741608e72b1a5e4224da5b975909cc43c53b6c0f7f1bbf0820269caa3e350dd1812484edc499b279</TXN_SIGNATURE>
<TXN_SIGNATURE2>B1684258EA112C8B5BA51F73CDA9864D1BB98E04F5A78B67A3E539BEF96CCF4D16CFF6B9E04818B50E855E0783BB075309D112CA596BDC49F9738C4BF3AA1FB4</TXN_SIGNATURE2>
<TRAN_DATE>29-09-2015 07:36:59</TRAN_DATE>
<MERCHANT_TRANID>150929093703RUDZMX4</MERCHANT_TRANID>
<RESPONSE_CODE>9967</RESPONSE_CODE>
<RESPONSE_DESC>Bank rejected transaction!</RESPONSE_DESC>
<CUSTOMER_ID>RUDZMX</CUSTOMER_ID>
<AUTH_ID />
<AUTH_DATE />
<CAPTURE_DATE />
<SALES_DATE />
<VOID_REV_DATE />
<REFUND_DATE />
<REFUND_AMOUNT>0.00</REFUND_AMOUNT>
</TRANSACTION>
</TRANSACTION_RESPONSE>
The above XML is handled in two level
[XmlType("TRANSACTION_RESPONSE")]
public class TransactionResponse
{
[XmlElement("TRANSACTION")]
public BankQueryResponse Response { get; set; }
}
The Inner level
public class BankQueryResponse
{
[XmlElement("TRANSACTION_ID")]
public string TransactionId { get; set; }
[XmlElement("MERCHANT_ACC_NO")]
public string MerchantAccNo { get; set; }
[XmlElement("TXN_SIGNATURE")]
public string TxnSignature { get; set; }
[XmlElement("TRAN_DATE")]
public DateTime TranDate { get; set; }
[XmlElement("TXN_STATUS")]
public string TxnStatus { get; set; }
[XmlElement("REFUND_DATE")]
public DateTime RefundDate { get; set; }
[XmlElement("RESPONSE_CODE")]
public string ResponseCode { get; set; }
[XmlElement("RESPONSE_DESC")]
public string ResponseDesc { get; set; }
[XmlAttribute("MERCHANT_TRANID")]
public string MerchantTranId { get; set; }
}
Same Way you need multiple level with car as array
Check this example for multilevel deserialization
If you're getting errors using xsd.exe to create your xsd file, then use the XmlSchemaInference class as mentioned on msdn. Here's a unit test to demonstrate:
using System.Xml;
using System.Xml.Schema;
[TestMethod]
public void GenerateXsdFromXmlTest()
{
string folder = #"C:\mydir\mydata\xmlToCSharp";
XmlReader reader = XmlReader.Create(folder + "\some_xml.xml");
XmlSchemaSet schemaSet = new XmlSchemaSet();
XmlSchemaInference schema = new XmlSchemaInference();
schemaSet = schema.InferSchema(reader);
foreach (XmlSchema s in schemaSet.Schemas())
{
XmlWriter xsdFile = new XmlTextWriter(folder + "\some_xsd.xsd", System.Text.Encoding.UTF8);
s.Write(xsdFile);
xsdFile.Close();
}
}
// now from the visual studio command line type: xsd some_xsd.xsd /classes
You can just change one attribute for you Cars car property from XmlArrayItem to XmlElment. That is, from
[System.Xml.Serialization.XmlRootAttribute("Cars", Namespace = "", IsNullable = false)]
public class Cars
{
[XmlArrayItem(typeof(Car))]
public Car[] Car { get; set; }
}
to
[System.Xml.Serialization.XmlRootAttribute("Cars", Namespace = "", IsNullable = false)]
public class Cars
{
[XmlElement("Car")]
public Car[] Car { get; set; }
}
My solution:
Use Edit > Past Special > Paste XML As Classes to get the class in your code
Try something like this: create a list of that class (List<class1>), then use the XmlSerializer to serialize that list to a xml file.
Now you just replace the body of that file with your data and try to deserialize it.
Code:
StreamReader sr = new StreamReader(#"C:\Users\duongngh\Desktop\Newfolder\abc.txt");
XmlSerializer xml = new XmlSerializer(typeof(Class1[]));
var a = xml.Deserialize(sr);
sr.Close();
NOTE: you must pay attention to the root name, don't change it. Mine is "ArrayOfClass1"

C# Parsing Complex XML LINQ

I have been trying to parse the following XML file with some success.
<?xml version="1.0"?>
<Settings>
<Tasks>
<Task ID="1" Name="task_1" Active="1" NextEID="25" AR="0" CacheNames="random">
<Schedules>
<Schedule OnlyUntilFirstSuccess="0" FailIfNoSuccessInSched="0" RunEvenIfNotif="0">
<Days>
<DayOfWeek>Monday</DayOfWeek>
<DayOfWeek>Tuesday</DayOfWeek>
<DayOfWeek>Wednesday</DayOfWeek>
<DayOfWeek>Thursday</DayOfWeek>
<DayOfWeek>Friday</DayOfWeek>
<DayOfWeek>Saturday</DayOfWeek>
<DayOfWeek>Sunday</DayOfWeek>
</Days>
<Frequency>
<Interval StartTime="09:45" EndTime="09:45" EveryMinutes="0"></Interval>
</Frequency>
</Schedule>
</Schedules>
</Task>
<Task ID="2" Name="task_2" Active="1" NextEID="25" AR="0" CacheNames="random">
<Schedules>
<Schedule OnlyUntilFirstSuccess="0" FailIfNoSuccessInSched="0" RunEvenIfNotif="0">
<Days>
<DayOfWeek>Monday</DayOfWeek>
<DayOfWeek>Tuesday</DayOfWeek>
<DayOfWeek>Wednesday</DayOfWeek>
<DayOfWeek>Thursday</DayOfWeek>
<DayOfWeek>Friday</DayOfWeek>
<DayOfWeek>Saturday</DayOfWeek>
<DayOfWeek>Sunday</DayOfWeek>
</Days>
<Frequency>
<Interval StartTime="12:45" EndTime="09:45" EveryMinutes="0"></Interval>
</Frequency>
</Schedule>
</Schedules>
</Task>
</Tasks>
</Settings>
Using several tutorials online I have pieced together the following C# code :
public static void ParseXml()
{
string strFile = "File.xml";
XDocument xdoc = XDocument.Load(strFile);
var tasks = from s in xdoc.Descendants("Tasks")
select new
{
taskID = s.Element("Task").Attribute("ID").Value,
taskName = s.Element("Task").Attribute("Name").Value,
taskActive = s.Element("Task").Attribute("Active").Value,
taskSchedule = s.Element("Task").Element("Schedules").Element("Schedule").Element("Days")
};
foreach (var t in tasks)
{
Console.WriteLine("Task Id :: {0}", t.taskID);
Console.WriteLine("Task Name :: {0}", t.taskName);
Console.WriteLine("Task Status :: {0}", t.taskActive);
Console.WriteLine("Task Schedule :: {0}", t.taskSchedule);
}
Console.ReadKey();
}
My goals is to parse out each task, task ID, Name, Days, Frequency. I'd like to ultimately be able to use these values to chart the TaskID and StartTime on a scatter plot graph. I tried creating the following classes thinking it might help me deal with it the file but I don't know how to tie it all together.
public class Task
{
public static int taskId { get; set; }
public static string taskName { get; set; }
}
public class Schedule
{
public enum DayOfWeek
{
Monday,
Tuesday,
Wednsday,
Thursday,
Friday,
Saturday,
Sunday
}
}
public class Frequency
{
public static DateTime startTime { get; set; }
public static DateTime endTime { get; set; }
public static int everyMins { get; set; }
}
Any help / direction would be appreciated. Please just don't post the solution if you have one. Please explain it so that I can learn from it and contribute to this community in the future.
Thanks!
Gabe
Firstly, you'll need to structure your classes as per the source XML. So a Settings class within which you have Tasks and so on. Something like this. I haven't put in all the XML attributes of course. But you should get the gist. You need the [XMLAttribute] attribute decorating your properties representing XML attributes. Also all the property names are case sensitive. So be sure they match (ID instead of Id). There are ways around this using attributes though:
public class Settings
{
public List<Task> Tasks { get; set; }
}
public class Task
{
[XmlAttribute]
public int ID { get; set; }
[XmlAttribute]
public string Name { get; set; }
public List<Schedule> Schedules { get; set; }
}
public class Schedule
{
[XmlAttribute]
public string OnlyUntilFirstSuccess { get; set; }
public List<DayOfWeek> Days { get; set; }
public Frequency Frequency { get; set; }
}
public class Frequency
{
public Interval Interval { get; set; }
}
public class Interval
{
[XmlAttribute]
public string StartTime { get; set; }
[XmlAttribute]
public string EndTime { get; set; }
[XmlAttribute]
public int EveryMins { get; set; }
}
Then you can use an XmlSerializer to serialize the XML to your class. So your ParseXml() method should look something like this:
public static void ParseXml()
{
string fileName = "File.xml";
XDocument xdoc = XDocument.Load(fileName);
using (TextReader reader = new StringReader(xdoc.ToString()))
{
try
{
var settings = (Settings)new XmlSerializer(typeof(Settings)).Deserialize(reader);
}
catch (Exception ex)
{
// Handle exceptions as you see fit
}
}
}
Other things to note: don't name variable strxxxxx to denote they are strings and standard convention is that public variable names start with capital letters.
Also, you'll need the following:
using System;
using System.Collections.Generic;
using System.IO;
using System.Xml.Linq;
using System.Xml.Serialization;

Have both DataType and additional Attribute during Xml Serialization

I am attempting to create an XML document through the application of attributes on fields/properties ([XmlAttribute], [XmlElement], etc.) My problem is that I have a requirement that I attach an additional attribute to a primitive datatype in the style of:
<document xmlns:dt="urn:schemas-microsoft-com:datatypes" >
<binary addAttribute="X" dt:dt="bin.base64">
[... binary ...]
</binary>
</document>
I'm making use of code like the following:
[Serializable]
public class Document {
[XmlElement]
public BinaryObject Binary { get; set; }
}
[Serializable]
public class BinaryObject {
[XmlText(DataType = "base64Binary")]
public byte[] Binary { get; set; }
[XmlAttribute]
public int AddAttribute { get; set; }
}
public class XmlExample {
public static void Main(string[] args)
{
Document document = new Document();
document.Binary = new BinaryObject();
document.Binary.Binary = File.ReadAllBytes(#"FileName");
document.Binary.AddAttribute = 0;
XmlSerializer serializer = new XmlSerializer(typeof(Document));
serializer.Serialize(Console.Out, document);
Console.ReadLine();
}
}
This, however, provides the following output:
<document>
<binary addAttribute="X">
[... binary ...]
</binary>
</document>
If I attempt to move the byte[] Binary to the Document class instead I can get the xmlns:dt="..." as expected but I cannot attach the arbitrary addAttribute when I do so (unless I missed something obvious.) This was incorrect; I misread something in the XML that I was getting out of the XML. The xmlns:dt element was not added in this case.
The question is: Can I do this (have both the DataType and the addAttribute) exclusively through C# attributes?
The answer to this question came partially from here: XmlSerializer attribute namespace for element type dt:dt namespace. The DataType = "base64Binary" does not apply the xmlns:dt="urn:schemas-microsoft-com:datatypes" dt:dt="bin.base64" to the element that it is attached to. An attribute has to be added to the BinaryObject that provides the dt:dt = "bin.base64" with the correct name space.
Final Code
[Serializable]
public class Document {
public BinaryObject Binary { get; set; }
}
[Serializable]
public class BinaryObject {
[XmlText]
public byte[] Binary { get; set; }
[XmlAttribute]
public int AddAttribute { get; set; }
// Adds the dt:dt object to the correct name space.
[XmlAttribute("dt", Namespace = "urn:schemas-microsoft-com:datatypes")]
public string DataType { get; set; }
public BinaryObject() { DataType = "bin.base64"; }
}
public class XmlExample {
public static void Main(string[] args)
{
XmlSerializerNamespaces namespaces = new XmlSerializerNamespaces();
// Adds the needed namespace to the document.
namespaces.Add("dt", "urn:schemas-microsoft-com:datatypes");
Document document = new Document();
document.Binary = new BinaryObject();
document.Binary.Binary = new byte[]{0,1,2,3,4,5,6,7,8,9};
document.Binary.AddAttribute = 0;
XmlSerializer serializer = new XmlSerializer(typeof(Document));
serializer.Serialize(Console.Out, document, namespaces);
Console.ReadLine();
}
}
Final Output
<Document xmlns:dt="urn:schemas-microsoft-com:datatypes">
<Binary AddAttribute="0" dt:dt="bin.base64">AAECAwQFBgcICQ==</Binary>
</Document>
Try this
using System.Linq;
using System.Text;
using System.IO;
using System.Xml;
using System.Xml.Serialization;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
}
}
[XmlRoot("Document")]
public class Document
{
[XmlText(DataType = "base64Binary")]
public byte[] Binary { get; set; }
[XmlAttribute]
public int AddAttribute { get; set; }
}
}
​

Deserialize XML to object (need to return a list of objects)

Started practicing with XML and C# and I have an error message of "There is an error in XML document (3,2)". After looking at the file, I can't see anything wrong with it (Mind you, I probably missed something since I'm a noob). I'm using a Console Application for C# right now. I'm trying to return a list of Adventurers and just a side note, the GEAR element is optional. Here is what I have so far:
XML File - Test1
<?xml version="1.0" encoding="utf-8"?>
<Catalog>
<Adventurer>
<ID>001</ID>
<Name>John Smith</Name>
<Address>123 Fake Street</Address>
<Phone>123-456-7890</Phone>
<Gear>
<Attack>
<Item>
<IName>Sword</IName>
<IPrice>15.00</IPrice>
</Item>
<Item>
<IName>Wand</IName>
<IPrice>20.00</IPrice>
</Item>
</Attack>
<Defense>
<Item>
<IName>Shield</IName>
<IPrice>5.00</IPrice>
</Item>
</Defense>
</Gear>
</Adventurer>
<Adventurer>
<ID>002</ID>
<Name>Guy noone likes</Name>
<Address>Some Big House</Address>
<Phone>666-666-6666</Phone>
<Gear></Gear>
</Adventurer>
</Catalog>
C# Classes
public class Catalog
{
List<Adventurer> Adventurers { get; set; }
}
public class Adventurer
{
public int ID { get; set; }
public string Name { get; set; }
public string Address { get; set; }
public string Phone { get; set; }
public Gear Gear { get; set; }
}
public class Gear
{
public List<Item> Attack { get; set; }
public List<Item> Defense { get; set; }
}
public class Item
{
public string IName { get; set; }
public decimal IPrice { get; set; }
}
Serialize Function - Where the Problem Occurs at Line 5
Catalog obj = null;
string path = #"C:\Users\Blah\Desktop\test1.xml";
XmlSerializer serializer = new XmlSerializer(typeof(Catalog));
StreamReader reader = new StreamReader(path);
obj = (Catalog)serializer.Deserialize(reader);
reader.Close();
Console.ReadLine();
The issue is the list of Adventurers in Catalog:
<?xml version="1.0" encoding="utf-8"?>
<Catalog>
<Adventurers> <!-- you're missing this -->
<Adventurer>
</Adventurer>
...
<Adventurer>
</Adventurer>
</Adventurers> <!-- and missing this -->
</Catalog>
You don't have the wrapping element for the Adventurers collection.
EDIT: By the way, I find the easiest way to build the XML structure and make sure it's compatible is to create the object(s) in C#, then run through the built-in XmlSerializer and use its XML output as a basis for any XML I create rather than forming it by hand.
First, "Adventurers" property is not public, it's inaccessible, I think that the best way to find the error is to serialize your object and then compare the result with your xml file.
Your XML doesn't quite line up with your objects... namely these two...
public string City { get; set; }
and
<Address>123 Fake Street</Address>
Change City to Address or vice versa and it should fix the problem.
Edit: Got this to work in a test project, combination of all our answers...
Add <Adventurers> tag after <Catalog> (and </Adventurers> before </Catalog>) and change
List<Adventurer> Adventurers { get; set; }
to
public List<Adventurer> Adventurers { get; set; }
and it works properly for me.
I was able to get your xml to deserialize with a couple minor changes (namely the public qualifier on Adventurer).
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Xml.Serialization;
namespace TempSandbox
{
[XmlRoot]
public class Catalog
{
[XmlElement("Adventurer")]
public List<Adventurer> Adventurers;
private readonly static Type[] myTypes = new Type[] { typeof(Adventurer), typeof(Gear), typeof(Item) };
private readonly static XmlSerializer mySerializer = new XmlSerializer(typeof(Catalog), myTypes);
public static Catalog Deserialize(string xml)
{
return (Catalog)Utils.Deserialize(mySerializer, xml, Encoding.UTF8);
}
}
[XmlRoot]
public class Adventurer
{
public int ID;
public string Name;
public string Address;
public string Phone;
[XmlElement(IsNullable = true)]
public Gear Gear;
}
[XmlRoot]
public class Gear
{
public List<Item> Attack;
public List<Item> Defense;
}
[XmlRoot]
public class Item
{
public string IName;
public decimal IPrice;
}
}
I'm using [XmlElement("Adventurer")] because the xml element names don't exactly match the class property names.
NOTE: I'm using a generic deserialization utility i already had on hand .

Categories