How do Idesign a generic class to deserialize this XML? - c#

I am trying to write classes for deserializing an XML response from some API.
Here is a sample response. When I query for ObjectA,
<Response>
<Status>0</Status>
<Message>Ok</Message>
<Data>
<ObjectAs>
<Count>2</Count>
<ObjectA>...</ObjectA>
<ObjectA>...</ObjectA>
</ObjectAs>
</Data>
</Response>
When I query for ObjectB,
<Response>
<Status>0</Status>
<Message>Ok</Message>
<Data>
<ObjectBs>
<Count>1</Count>
<ObjectB>...</ObjectB>
</ObjectBs>
</Data>
</Response>
I am trying to create a generic Response class, but everything I tried seems to be futile.
I can't change the response structure. And I am trying to avoid writing a new Response class for each of the response type.
Notice how under Data, each API response is different. For ObjectA it is <ObjectAs> and for ObjectB it is <ObjectBs>.
Here is my ApiResponse class,
[XmlRoot(ElementName = "Response")]
public class ApiResponse<T>
{
public int Code { get; set; }
public string Message { get; set; }
[XmlAnyElement("Data")]
public XmlElement DataElement { get; set; }
[XmlIgnore]
public List<T> Data
{
get
{
{
return null; // How do I parse and return the list of Objects (A, B, C, whatever?)
}
}
}
}
Here is my sample API XML response, when I query for devices.
<Response>
<Code>0</Code>
<Message>OK</Message>
<Data>
<Devices>
<Count>2</Count>
<Device>
<Name>0001</Name>
<Active>TRUE</Active>
<DeviceType>1</DeviceType>
<Address>192.168.0.75</Address>
<Port>80</Port>
<Memo/>
</Device>
<Device>
<Name>0002</Name>
<Active>TRUE</Active>
<DeviceType>1</DeviceType>
<Address>192.168.0.78</Address>
<Port>80</Port>
</Device>
</Devices>
</Data>
</Response>
And when I query for users,
<Response>
<Code>0</Code>
<Message>OK</Message>
<Data>
<Users>
<Count>1</Count>
<User>
<Name>Administrator</Name>
<Group>Admins</Group>
</User>
</Users>
</Data>
</Response>

Here is a generic solution using LINQ to XML that works:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
namespace ConsoleApplication1
{
class Program
{
const string FILENAME = #"c:\temp\test.xml";
static void Main(string[] args)
{
ApiResponse apiResponse = new ApiResponse(FILENAME);
}
}
public class ApiResponse
{
public int code { get; set; }
public string message { get; set; }
public string _type { get; set; }
public int count { get; set; }
public Dictionary<string, Dictionary<string, string>> dict { get; set; }
public ApiResponse(string filename)
{
XDocument doc = XDocument.Load(filename);
XElement response = doc.Root;
code = (int)response.Element("Code");
message = (string)response.Element("Message");
XElement types = (XElement)response.Element("Data").FirstNode;
_type = types.Name.LocalName;
count = (int)types.Element("Count");
dict = types.Elements().Where(x => x.Name.LocalName != "Count")
.GroupBy(x => (string)x.Element("Name"), y => y.Elements()
.GroupBy(a => a.Name.LocalName, b => (string)b)
.ToDictionary(a => a.Key, b => b.FirstOrDefault()))
.ToDictionary(x => x.Key, y => y.FirstOrDefault());
}
}
}

Related

Display field from XML Response Data using C# Console

I have below xml response data. Now i need to display ProjectName and MigrationStatus using C# console Application. I am new to C# please guide me to get fied names from xml node list. I have tried below code but confuisng a lot.
Thanks in advance
XML Response Code:
<?xml version="1.0" encoding="utf-8"?>
<feed xml:base="https://URL"
<id><<URL>></id>
<title type="text">Projects</title>
<updated>2020-06-07T06:24:57Z</updated>
<link rel="self" title="Projects" href="Projects" />
<entry>
<id><<URL>>(guid'86c492af-8c63-ea11-b119-00155d6c5103')</id>
<category term="ReportingData.Project" scheme="<<URL>>" />
<link rel="edit" title="Project" href="Projects(guid'86c492af-8c63-ea11-b119-00155d6c5103')" />
<title />
<updated>2020-06-07T06:24:57Z</updated>
<author>
<name />
</author>
<content type="application/xml">
<m:properties>
<d:NOde1>BULLL</d:ProjectName>
<d:NOde2>DOM</d:NOde2>
</m:properties>
</content>
</entry>
</feed>
Try following xml linq to parse file
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
namespace ConsoleApplication1
{
class Program
{
const string FILENAME = #"c:\temp\test.xml";
static void Main(string[] args)
{
XDocument doc = XDocument.Load(FILENAME);
XElement feed = doc.Root;
XNamespace ns = feed.GetDefaultNamespace();
XNamespace nsM = feed.GetNamespaceOfPrefix("m");
XNamespace nsD = feed.GetNamespaceOfPrefix("d");
List<Entry> entries = doc.Descendants(ns + "entry").Select(x => new Entry()
{
id = (string)x.Element(ns + "id"),
title = (string)x.Element(ns + "title"),
updated = (DateTime)x.Element(ns + "updated"),
author_name = (string)x.Descendants(ns + "name").FirstOrDefault(),
ProjectName = (string)x.Descendants(nsD + "ProjectName").FirstOrDefault(),
MigrationStatus = (string)x.Descendants(nsD + "MigrationStatus").FirstOrDefault()
}).ToList();
Dictionary<string, Entry> dict = entries
.GroupBy(x => x.ProjectName, y => y)
.ToDictionary(x => x.Key, y => y.FirstOrDefault());
foreach (KeyValuePair<string, Entry> entry in dict)
{
Console.WriteLine(entry.Key);
}
}
}
public class Entry
{
public string id { get; set; }
public string title { get; set; }
public DateTime updated { get; set; }
public string author_name { get; set; }
public string ProjectName { get; set; }
public string MigrationStatus { get; set; }
}
}

Searching for an attribute by its value

I have following XML file:
<?xml version="1.0" encoding="utf-8"?>
<root>
<Communication Id ="456">
<Person> Ivan Ivanov </Person>
<Describtion>
<Age> 16 </Age>
<Place> Moscow </Place>
<Key Name ="Language"> English </Key>
<Key Name ="Profession"> Doctor </Key>
</Describtion>
</Communication>
<Communication Id ="1010">
<Person> Petr Petrov </Person>
<Describtion>
<Age> 21 </Age>
<Place> St.Peterburg </Place>
<Key Name ="Language"> Français </Key>
<Key Name ="Profession"> Ingeneer </Key>
</Describtion>
</Communication>
</root>
There is a list of Key tags with attribute Name which has different value. This value determines which variable the value between tags will be written to. How can I write an algorithm for such a search?
You can search like this.
xmlDoc.SelectNodes("root/Communication/Describtion/Key[#Name=\"Language\"][text()=\" English \"]")
Try with XmlDocument
public static string getNodeValue(XmlDocument xmldoc, string id, string key)
{
return xmldoc.SelectSingleNode($"root/Communication[#Id='{id}']/Describtion/Key[#Name='{key}']")
.InnerText
.Trim();
}
Usage
var xmlDoc = new XmlDocument();
xmlDoc.Load(xmlFile);
Console.WriteLine(getNodeValue(xmlDoc, "456", "Language"));
Console.WriteLine(getNodeValue(xmlDoc, "1010", "Language"));
Output
English
Français
I used Xml Linq along with a dictionary and IEquatable
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
using System.IO;
namespace ConsoleApplication1
{
class Program
{
const string FILENAME = #"c:\temp\test.xml";
static void Main(string[] args)
{
StreamReader reader = new StreamReader(FILENAME);
reader.ReadLine(); //allow unicode characters
XDocument doc = XDocument.Load(reader);
List<Person> people = doc.Descendants("Communication").Select(x => new Person()
{
id = (int)x.Attribute("Id"),
name = (string)x.Element("Person"),
age = (int)x.Descendants("Age").FirstOrDefault(),
place = (string)x.Descendants("Place").FirstOrDefault(),
language = ((string)x.Descendants("Key").Where(y => (string)y.Attribute("Name") == "Language").FirstOrDefault()).Trim(),
profession = ((string)x.Descendants("Key").Where(y => (string)y.Attribute("Name") == "Profession").FirstOrDefault()).Trim()
}).ToList();
Dictionary<Person, List<Person>> dict = people
.GroupBy(x => x, y => y)
.ToDictionary(x => x.Key, y => y.ToList());
List<Person> results = dict[new Person() { language = "English", profession = "Doctor" }].ToList();
}
}
public class Person : IEquatable<Person>
{
public int id { get; set; }
public string name { get; set; }
public int age { get; set; }
public string place { get; set; }
public string language { get; set; }
public string profession { get; set; }
public Boolean Equals(Person other)
{
return
(language == other.language) && (profession == other.profession);
}
public override int GetHashCode()
{
return (language + "^" + profession).GetHashCode() ;
}
}
}

C# Deserialize XML elements with attributes into List

Here's the XML:
<xml id = "1234">
<connect id="2"/>
<connect id="1"/>
<connect id="21"/>
<connect id="3"/>
<connect id="7"/>
</xml>
Currently I am doing this:
public class xml
{
//Constructor
[XmlAttribute ("id")]
public uint id;
[XmlElement ("connect")]
public List<Connection> Connections { get; set; }
//Deserializer
}
public class Connection
{
[XmlAttribute ("id")]
public uint id { get; set; }
}
The goal is to get rid of the Connection class entirely and Deserialize the xml straight into:
List<uint> connections;
First, your XML is not valid, i guess it's just a typo - there no end tag for "connect".
I recommend you to use linq XDocument.
Then it's easy:
XDocument xdoc = XDocument.Parse(xml);
List<uint> list = xdoc
.Descendants("connect")
.Select(node => uint.Parse(node.Attribute("id").Value))
.ToList();

unable to deserialize all elements of this xml

Few elements of this XML does not deserialize and but it does not throw any errors either.
<?xml version="1.0" encoding="utf-8"?>
<TrialMWordsRecord xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<MyList>
<MWords>
<Id>0</Id>
<Name>ListMWords1</Name>
<Type>LIST</Type>
<MyOutput>true</MyOutput>
<WordsElements>
<Words>
<Name>ListMWords1</Name>
<Value>Apple</Value>
<Type>STRING</Type>
</Words>
<Words>
<Name>ListMWords1</Name>
<Value>Mango</Value>
<Type>STRING</Type>
</Words>
<Words>
<Name>ListMWords1</Name>
<Value>Chickoo</Value>
<Type>STRING</Type>
</Words>
</WordsElements>
</MWords>
<MWords>
<Id>1</Id>
<Type>RANDOM</Type>
<MyOutput>true</MyOutput>
<WordsElements>
<Name>Limit</Name>
<Value>3,8</Value>
<Type>NUMERIC</Type>
</WordsElements>
</MWords>
</TrialMWordsList>
</MyListRecord>
Below are my classes:
[Serializable()]
[XmlRootAttribute("MyListRecord")]
public class MyList
{
[XmlArray("MyList")]
public List<MWords> MWords
{
get;
set;
}
public static MyList Deserialize()
{
XmlSerializer deserializer = new XmlSerializer(typeof(MyList));
TextReader textReader = new StreamReader(Application.StartupPath + "\\MyList.xml");
MyList resultList = (MyList)deserializer.Deserialize(textReader);
textReader.Close();
return resultList;
}
}
[Serializable()]
public class MWords
{
public int Id
{
get;
set;
}
public MWordsType Type
{
get;
set;
}
public bool MyOutput
{
get;
set;
}
public string Requirement
{
get;
set;
}
[XmlArrayItem("WordsElements")]
public List<Words> WordList
{
get;
set;
}
public static MWords Deserialize()
{
XmlSerializer deserializer = new XmlSerializer(typeof(MWords));
TextReader textReader = new StreamReader(Application.StartupPath + "\\MWords.xml");
MWords mwords = (MWords)deserializer.Deserialize(textReader);
textReader.Close();
return mwords;
}
}
public class Words
{
public string Value
{
get;
set;
}
public TYPE Type
{
get;
set;
}
public string Name
{
get;
set;
}
}
Now when I deserialize this XML, if Type is LiST, the WordList gets updated, e.g. here count for WordList will be 3 but if Type is RANDOM, WordList is 0 which should actually be 1 and not 0.
Really don't know what could be the reason.
The problem is in your XML. Look at what it's like for your working case:
<WordsElements>
<Words>
<Name>ListMWords1</Name>
<Value>Apple</Value>
<Type>STRING</Type>
</Words>
<Words>
...
</Words>
<Words>
...
</Words>
</WordsElements>
Now compare that with your broken case:
<WordsElements>
<Name>Limit</Name>
<Value>3,8</Value>
<Type>NUMERIC</Type>
</WordsElements>
You've got no Words element in there - it should be:
<WordsElements>
<Words>
<Name>Limit</Name>
<Value>3,8</Value>
<Type>NUMERIC</Type>
</Words>
</WordsElements>
If you can't change the XML, you may need to deserialize it by hand - which probably wouldn't be too hard.
As an aside, you might want to look into writing your automatically implemented properties on one line - it's a lot more compact to write
public string Name { get; set; }
than
public string Name
{
get;
set;
}
... and it's no harder to read, IMO.
The contents of element WordsElements for type RANDOM does not wrap in <Words></Words>:
<MWords>
<Id>1</Id>
<Type>RANDOM</Type>
<MyOutput>true</MyOutput>
<WordsElements>
<Words>
<Name>Limit</Name>
<Value>3,8</Value>
<Type>NUMERIC</Type>
</Words>
</WordsElements>
</MWords>

xml Serialization and Deserialization (hide one node header)

I have an XML file and I need to deserialize it. Without bypassing all the nodes, just deserilizing the XML file to an object.
Is it possible to hide from the result ActionGetSiteResultData or only one way
use custom serialization/deserialization?
Classes:
// root
public Result Result { get; set; }
// rows
public class Result
{
public List<ResultData> Data { get; set; }
}
//item
public class ResultData
{
[XmlElement(ElementName = "gen_info")]
public GenInfo GenInfo { get; set; }
[XmlElement(ElementName = "hosting")]
public Hosting Hosting { get; set; }
}
Result:
<Result>
<Id>1</Id>
<Data>
<ResultData> <--- REMOVE THIS
<gen_info>
<ascii-name>sadsad</ascii-name>
</gen_info>
<hosting/>
</ResultData> <--- REMOVE THIS
</Data>
</Result>
Need:
<Result>
<Id>1</Id>
<Data>
<gen_info>
<ascii-name>sadsad</ascii-name>
</gen_info>
<hosting/>
</Data>
</Result>
<Result>
<Id>2</Id>
<Data>
<gen_info>
<ascii-name>sadsad2</ascii-name>
</gen_info>
<hosting/>
</Data>
</Result>
This should do but is verbose. Try [XmlElement(ElementName = "gen_info")] on ResultData property first, if it doesn't work:
public class Result
{
[XmlIgnore]
public List<ResultData> Data { get; set; }
[XmlElement(ElementName = "gen_info")]
public ResultData[] __XmlSerializedData{
get{ return Data.ToArray();}
set{ Data = new List<ResultData>(value);}
}
}

Categories