Reading Multi Level XML File - c#

I have a XML file which has all information about a voyage and all details inside.
I want to read all records in XML file, after combining records I want to write it to SQL database.
So far I arranged getting header , company and voyage to array but getting details for all records to array I failed.
Here are my task to handle:
Select and read any XML Data to RAM by FileDialog (completed)
Create Arrays and Read XML data to Arrays (partly completed)
Write XML data to DataView (partly completed)
Create T-SQL INSERT Command (Partly completed)
Write Data to Database (Waiting to finish early steps)
While reading from XML to DataView I can get data to memory but could not seperated multi level data as requested.
The exact problem is trying to handle different levels of data in XML in every XML file I recieve.
foreach (var child in childElem.Elements("ManifestData"))
{
foreach(var x in child.Elements())
{
var checkName = x.Name.ToString();
switch (checkName)
{
case "Company":
Globals.Companys.Clear();
foreach (var y in x.Elements())
{
Globals.Companys.Add(y.Name.ToString(), y.Value.ToString());
}
break;
case "Voyage":
Globals.Voyages.Clear();
foreach (var y in x.Elements())
{
Globals.Voyages.Add(y.Name.ToString(), y.Value.ToString());
}
break;
case "BLs":
int recs = 0;
Globals.BL.Clear();
textBox2.Clear();
foreach (var y in x.Elements())
{
foreach (var z in x.Elements("units"))
{
Globals.Units.Add(y.Element("number").Value.ToString(), z.Value.ToString());
}
Globals.BL.Add(y.Element("number").Value.ToString(), y.Value.ToString());
recs = recs + 1;
textBox2.AppendText("\n" + y.ToString());
string output = string.Join("\n", Globals.BL);
MessageBox.Show(output);
}
break;
default:
break;
}
}
}
In my example XML you see that there is 3 BLs and all BL data has different levels.There can be hundreds of BLs with different levels of Goods & Dangerous Goods.
I am having trouble handling multi level XML data here.
I 'll be glad if you help me solve this very basic problem. I am hoping to learn and leave it for the people to figure out to understand making desktop XML reader application for their own DBs.
Here is the XML Data example
You can find all sources here : Project Reading XMLbyC#

The xml processing part can be made simple by deserializing your xml into c# classes which you can then use to do whatever you want.
[XmlRoot(ElementName = "ManifestMessage")]
public class ManifestMessage
{
[XmlElement(ElementName = "Header")]
public Header Header { get; set; }
[XmlElement(ElementName = "ManifestData")]
public ManifestData ManifestData { get; set; }
}
[XmlRoot(ElementName = "Header")]
public class Header
{
[XmlElement(ElementName = "sender")]
public string Sender { get; set; }
[XmlElement(ElementName = "reciever")]
public string Reciever { get; set; }
[XmlElement(ElementName = "timeOfDocument")]
public string TimeOfDocument { get; set; }
[XmlElement(ElementName = "typeOfMessage")]
public string TypeOfMessage { get; set; }
}
// Then when you want to get the xml deserialized into your class hierarchy
var xmlSerializer = new XmlSerializer(typeof(ManifestMessage));
var manifestMessage = xmlSerializer.Deserialize(data) as ManifestMessage;
// now you can use this object to drill down the whole hierarchy
Console.WriteLine(xmlData.Header.Sender);
Console.WriteLine(xmlData.ManifestData.Company.ComanyName);
Console.WriteLine(xmlData.ManifestData.Voyage.CrewNumber);
foreach (var bl in xmlData.ManifestData.BLs.BL)
{
Console.WriteLine(bl.Collect);
Console.WriteLine(bl.Consegnee.Name);
Console.WriteLine(bl.Customer.Name);
}
You can use https://xmltocsharp.azurewebsites.net/ site to generate the whole c# class hierarchy from your xml.
Console.WriteLine is just for demo purpose you can adopt it according to your needs.

Related

How can we have dynamic xml element name in C# class file?

This is my current XML Response tag
<Response_Data_1234 diffgr:id="Response_Data_1234" msdata:rowOrder="0" diffgr:hasChanges="inserted">
<Status>File received.</Status>
<Time>2022-01-25T09:44:15.73+08:00</Time>
<Location>HKG</Location>
</Response_Data_1234>
the number 1234 in <Response_Data_1234> is a Response Data id, which will be dynamic depending on the request.
Could someone please me create a C# class in this scenario so that I can map the response directly to the class. Thanks in advance
Actually you should not have any unique identifier attached to XML element name. It should be added as attribute.
To answer your question:
There is no direct class attribute to ignore class name while deserialisation of xml string to object.
Option1 (better): Create mapper extension method for XNode to class attribute mapping and reuse it.
Option2:
If your xml string is small you can try to update name of root element and use it to deserialise.
sample code:
var responseDataXDoc = XDocument.Parse(xml);
responseDataXDoc.Root.Name = "ResponseData";
var serializer = new XmlSerializer(typeof(ResponseData));
var responseData = serializer.Deserialize(new StringReader(responseDataXDoc.ToString(SaveOptions.DisableFormatting)));
With Cinchoo ETL - an open source library, you can deserialize such xml easily as below
using (var r = ChoXmlReader<ResponseData>.LoadText(xml)
.WithXPath("//")
.WithXmlNamespace("diffgr", "")
.WithXmlNamespace("msdata", "")
)
{
var rec = r.FirstOrDefault();
rec.Print();
}
public class ResponseData
{
public string Status { get; set; }
public DateTime Time { get; set; }
public string Location { get; set; }
}
Sample fiddle: https://dotnetfiddle.net/HOOPOg

Converge multiple list into a single json string

I need to send data from my game server to the client through json and getting a large sum of data from the database and sending it creates an issue
Currently I am creating multiple list in the call to the database to get the data and now I am stuck because I have the data but in multiple list and I can't figure out how to return the data.
I feel like I should be converting it to JSON here and then returning the json string but its all just really confusing
public static void GetLobbyList() {
string query = "SELECT * FROM que";
MySqlCommand cmd = new MySqlCommand(query, MySQL.mySQLSettings.connection);
MySqlDataReader reader = cmd.ExecuteReader();
List<int> ids = new List<int>();
List<string> uids = new List<string>();
List<int> bets = new List<int>();
List<string> games = new List<string>();
while (reader.Read()) {
ids.Add((int)reader["id"]);
uids.Add((string)reader["uid"]);
bets.Add((int)reader["bet"]);
games.Add((string)reader["game"]);
}
reader.Close();
}
So here I am reading the info from the database and for lack of experience I am adding each data point to a list(which is really ugly and I know there must be a better way)
So essentially I Grab the data->Parse to json->Send string to client
If It is possible assuming the data returned is from a table with
id | uid | bet | game
I would Like to return an array in json that looks like
{
{
"id" : 1,
"uid" : "erjfh4982y9hf",
"bet" : 3,
"game" : "Snake"
}
{
"id" : 2,
"uid" : "gsegt34t",
"bet" : 2,
"game" : "Snake"
}
}
Im not too familiar with json and how it works but I do know that it is the only way to send large packets of data from my server to my client because everything has to be converted to bytes before it can be sent and my framework does not support converting list to bytes
To achieve what you are aiming for, you should create a class to hold the data you have retrieved from the database. In my case I called it GameObject and it's defined as follows.
public class GameObject
{
public int Id { get; set; }
public string Uid { get; set; }
public int Bet { get; set; }
public string Game { get; set; }
}
After retrieving the information from the database, you'll need to run code similar to the following.
var items = new List<GameObject>();
while (reader.Read())
{
items.Add(new GameObject
{
Id = (int)reader["id"],
Uid = (string)reader["uid"],
Bet = (int)reader["bet"],
Game = (string)reader["game"]
});
}
// Return the jsonPacket which will contain all the items in json format.
var jsonPacket = JsonConvert.SerializeObject(items);
In order for this to work, you'll need to reference the Newtonsoft.Json library from nuget. Open your package manager console and type the following command: Install-Package Newtonsoft.Json and it will set it up for you. At the top of our code you'll need to have using Newtonsoft.Json; to be able to use the classes inside the library to serialise to Json. On the receiving end you can pass the string to JsonConvert.DeserializeObject<T>() and you'll get back your list of objects.
You could use a list of objects and anonymous types. There's also a JSON serializer, System.Web.Script.Serialization.JavaScriptSerializer included if you add the System.Web.Extensions assembly to your references.
List<object> list = new List<object>();
while (reader.Read()) {
list.Add(new { id = (int)reader["id"],
uid = (string)reader["uid"],
bet = (int)reader["bet"],
game = (string)reader["game"], });
}
string json = new System.Web.Script.Serialization.JavaScriptSerializer().Serialize(list);

Parse json file without key values

I'm trying to parse a json file with Json.net. The content of the json file is:
[
[ "240521000", "37.46272", "25.32613", "0", "71", "90", "15", "2016-07-18T21:09:00" ],
[ "237485000", "37.50118", "25.23968", "177", "211", "273", "8", "2015-09-18T21:08:00" ]
]
I created the following code:
WebClient wc = new WebClient();
string json = wc.DownloadString("data.json");
dynamic myObject = JsonConvert.DeserializeObject<dynamic>(json);
foreach (string item in myObject[0])
{
var x = item[0];
}
How can I loop through all the individual items without having a key?
You probably just need two nested foreach statements. Try something like this:
foreach (var items in myObject)
{
foreach (var item in items)
{
// do something
}
}
While diiN_'s answer answers the question, I don't think it's a good solution. Having had a look at the Marine Traffic API, it feels like they've made a poor JSON implementation, as the XML representation clearly has attribute names for the values. Their JSON should've been:
{"positions": ["position": {"mmsi": "311029000", "lat": "37.48617", "long": "24.37233", ...}]}
Because it isn't, we have a JSON string instead where it's a nested array, where the array is a two-dimensional array, and you'd have to hope the data model isn't changed to remove something, as you'd have to use an index to retrieve data.
However, if you look at the XML available from the API, the attributes have names. I would suggest that you instead download the XML, and parse this into an object - a model, in ASP.NET - which is strongly typed, and can be used more easily in a View.
Here's an example that I've got running. It uses XML parsing to first read the XML from the API, and then parse it to JSON, and finally an actual object.
First, the model class (Position.cs)
public sealed class Position
{
[JsonProperty("#MMSI")]
public string MMSI { get; set; }
[JsonProperty("#LAT")]
public string Latitude { get; set; }
[JsonProperty("#LON")]
public string Longitude { get; set; }
[JsonProperty("#SPEED")]
public string Speed { get; set; }
[JsonProperty("#HEADING")]
public string Heading { get; set; }
[JsonProperty("#COURSE")]
public string Course { get; set; }
[JsonProperty("#STATUS")]
public string Status { get; set; }
[JsonProperty("#TIMESTAMP")]
public string TimeStamp { get; set; }
}
Next, the parsing logic:
var client = new WebClient();
var xml = client.DownloadString("data.xml");
var doc = new XmlDocument();
doc.LoadXml(xml);
var json = JsonConvert.SerializeXmlNode(doc);
var positions = JObject.Parse(json).SelectToken("pos").SelectToken("row").ToObject<List<Position>>();
At the end of the parsing logic, you now have a list of Positions which you can pass to your view, and have it be strongly typed.
As a brief example:
// after you have the positions list
return View(positions);
Positions.cshtml
#model List<Positions>
<h2>Positions</h2>
#foreach (var position in Model)
{
<p>#position.MMSI (#position.Latitude, #position.Longitude)</p>
}
I hope this is useful to you. If you have any questions, drop me a comment.

XML Element Selection

I am collecting XML data from a weather website xml file here.
I have created the following code, which finds the first instance of 'temp_c' and returns the value. This is the temperature at the current moment.
using (XmlReader reader = XmlReader.Create(new StringReader(weather)))
{
while (reader.Read())
{
switch (reader.NodeType)
{
case XmlNodeType.Element:
if (reader.Name.Equals("temp_c"))
{
reader.Read();
temp_c = reader.Value;
}
break;
}
}
}
return temp_c
This returns the value of the first instance in the XML file called "temp_c" to the string called "temp_c"
What I would now like to do is use the Element in the XML document called "period" and the element found with period called "fcttext". When "period = 0" it means "today", 1 = tomorrow, etc; and I am after the "fcttext_metric" data that is associated with that period value.
I've been struggling to work the examples and XML reading codes I've found into the current code that I have here. Can anyone please point me in the right direction?
Edit
Here is an example of the XML file:
<forecast>
<forecastday>
<period>0</period>
<fcttext_metric>Sunny</fcttext_metric>
<forecastday>
<period>1</period>
<fcttext_metric>Cloudy</fcttext_metric>
This ended up being more annoying that I originally expected, but you can create an object graph from the XML using a DataContractSerializer and a set of classes that match the XML you are reading.
First, you create your classes, with appropriate DataContract attributes.
[DataContract(Name = "response", Namespace = "")]
public class WeatherData
{
[DataMember(Name = "forecast")]
public Forecast Forecast { get; set; }
}
[DataContract(Name = "forecast", Namespace = "")]
public class Forecast
{
[DataMember(Name = "txt_forecast")]
public TxtForecast TxtForecast { get; set; }
}
[DataContract(Name = "txt_forecast", Namespace = "")]
public class TxtForecast
{
[DataMember(Name = "forecastdays")]
public ForecastDay[] ForecastDays { get; set; }
}
[DataContract(Name = "forecastday", Namespace = "")]
public class ForecastDay
{
[DataMember(Name = "period", Order = 1)]
public int Period { get; set; }
public string FctText { get; set; }
[DataMember(Name = "fcttext", EmitDefaultValue = false, Order = 5)]
private CDataWrapper FctTextWrapper
{
get { return FctText; }
set { FctText = value; }
}
}
Heres where it got complicated. The fcttext element is CDATA, which the DataContractSerializer doesn't support by default.
Using the wonderful answer available at Using CDATA with WCF REST starter kits, you create a CDataWrapper class. I won't repeat the code here (because that would be pointless), just follow the link.
You can see that I've already used the CDataWrapper class above in order to work with the fcttext element.
Once you've got the class structure setup you can use the following code to extract the information you were after. You're just navigating the object graph at this point, so you can use whatever C# you want (I've used a simple LINQ query to find Period 0 and print out the fcttext for it).
var s = new DataContractSerializer(typeof(WeatherData));
var reader = XmlReader.Create("http://api.wunderground.com/api/74e1025b64f874f6/forecast/conditions/q/zmw:00000.1.94787.xml");
var o = (WeatherData)s.ReadObject(reader);
var firstPeriod = o.Forecast.TxtForecast.ForecastDays.Where(a => a.Period == 0).Single();
Console.WriteLine(firstPeriod.FctText);
You can extend the classes as necessary to give you access to additional fields inside the XML. As long as the DataMember names match the XML fields it will all work.
Here's a short summary of some problems I ran into for anyone interested:
I had to set the Namespace attributes of all of the classes to the empty string, because the XML doesn't have any namespace info in it.
The Order attributes were important on the ForecastDay class. If they are omitted, the DataContractSerializer ends up not reading the fcttext field in at all (because it thinks fcttext should come first? not sure why to be honest).

Dealing with fanart.tv webservice response JSON and C#

I am trying to use the fanart.tv webservice API but have a couple of issues.
I am using Json.Net (Newtonsoft.Json) and with other web-services I have de-serialized the JSON reponses into C# objects directly.
The issue here is that the element names are changing. Eg. if I search for artist thumbs for Metallica you get
{"Metallica":{"mbid_id":"65f4f0c5-ef9e-490c-aee3-909e7ae6b2ab","artistthumb": [{"id":"36181","url":"http://assets.fanart.tv/fanart/music/65f4f0c5-ef9e-490c-aee3-909e7ae6b2ab/artistthumb/metallica-4fd83b0129f83.jpg","likes":"1"},...]}}
So the root element is called Metallica. Obviously search for another artist and you get a different element name. After attempting to deserialize this to an object I gave up and as in reality all I needed was a list of strings (the urls) I tried to process the JSON
var obj = _downloader.Download<JObject>(url);
if (obj != null)
{
if (obj.HasValues)
{
var fanartArtist = (JProperty)obj.First;
if (fanartArtist.HasValues)
{
var thumbs = fanartArtist.Value[SearchSubTypeToString(subType)];
if (thumbs.HasValues)
{
thumbUrls.AddRange(thumbs.Select(thumb => thumb["url"].ToString()));
}
}
}
}
which works fine when there is a response but if there are no thumbs the web-service returns null and this code fails with
Unable to cast object of type 'Newtonsoft.Json.Linq.JValue' to type 'Newtonsoft.Json.Linq.JObject'.
To complicate matters slightly I am sort of limited by the application and ideally I need to use
JsonConvert.DeserializeObject<TE>(json);
So the question is what what is the best approach to solve both of these issues?
Try this:
First, define objects to hold the data deserialized from the JSON:
class Artist
{
public Guid mb_id { get; set; }
public List<Thumb> artistthumb { get; set; }
}
class Thumb
{
public int id { get; set; }
public string url { get; set; }
public int likes { get; set; }
}
You can then deserialize it like this (where json is a string containing the JSON data from the web service):
Dictionary<string, Artist> artists =
JsonConvert.DeserializeObject<Dictionary<string, Artist>>(json);
Once deserialized, you can access the data like this:
foreach (KeyValuePair<string, Artist> kvp in artists)
{
Console.WriteLine("Urls for " + kvp.Key + ":");
foreach (Thumb thumb in kvp.Value.artistthumb)
{
Console.WriteLine(thumb.url);
}
}
Assuming the data you showed in your question, the output would look like this:
Urls for Metallica:
http://assets.fanart.tv/fanart/music/65f4f0c5-ef9e-490c-aee3-909e7ae6b2ab909e7ae6b2ab/artistthumb/metallica-4fd83b0129f83.jpg
.

Categories