I am dealing with an API that occasionally changes the namespaces on the XML that I receive. The XML structure remains the same. I need to deserialize the XML into a strongly typed model.
How do I perform the deserialization regardless of what namespace is on the XML?
I was using a model like this:
[Serializable, XmlRoot(ElementName = "TestModel", Namespace = "http://schemas.datacontract.org/UnknownNamespace1")]
public class TestModel
{
public TestModel()
{
TestElements = new List<TestModelChildren>();
}
[XmlElement("TestModelChildren")]
public List<TestModelChildren> TestElements { get; set; }
}
I try to deserialize some XML into that model with code like this:
public TestModel DeserializeIt(XDocument xDoc)
{
TestModel result;
var serializer = new XmlSerializer(typeof(TestModel));
using(var sr = new StringReader(xDoc.ToString()))
{
result = (TestModel)serializer.Deserialize(sr);
}
return result;
}
My problem is that every so often, the namespace on the XML I am receiving changes. I might start getting XML like this:
<TestModel xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/UnknownNamespace2">
<TestModelChildren>
...
</TestModelChildren>
</TestModel>
I don't want to have to recompile my code every time this namespace change happens. How do I deal with it?
I was able to solve the problem by passing the namespace in to the XmlSerializer as a default namespace. I can pluck the namespace off of the XDocument to do this.
My new model would look like this without a namespace specified:
[Serializable, XmlRoot(ElementName = "TestModel")]
public class TestModel
{
public TestModel()
{
TestElements = new List<TestModelChildren>();
}
[XmlElement("TestModelChildren")]
public List<TestModelChildren> TestElements { get; set; }
}
My code to deserialize the XML would look like this:
public TestModel DeserializeIt(XDocument xDoc)
{
TestModel result;
var serializer = new XmlSerializer(typeof(TestModel), xDoc.Root.Name.Namespace.ToString());
using(var sr = new StringReader(xDoc.ToString()))
{
result = (TestModel)serializer.Deserialize(sr);
}
return result;
}
This has been working for me.
Related
I am a bit lost with the deserialization of a particular part of my soap response.
The response:
<?xml version="1.0" encoding="UTF-8"?><soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"><soapenv:Body>
<ns1:LoginResult xmlns:ns1="http://abc.def.schema">
<sessionId>123456789</sessionId>
<sessionTimeout>30</sessionTimeout>
<organizationName>WebService Test Account XYZ</organizationName>
<userInfoResult>
<accessibleOrgs>
<name>WebService Test Account XYZ</name>
<description/>
<prefix>10</prefix>
<countryCallingCode>+49</countryCallingCode>
<treeLevel>0</treeLevel>
<timeZone>
<timeZoneId>Europe/Berlin</timeZoneId>
<currentUtcOffset>3600000</currentUtcOffset>
</timeZone>
<billingCompany>COMPANY 123</billingCompany>
<language>DE</language>
</accessibleOrgs>
<isDemo>true</isDemo>
<prefixLength>0</prefixLength>
<alarmNumberLength>0</alarmNumberLength>
<groupNumberLength>0</groupNumberLength>
<personNumberLength>0</personNumberLength>
</userInfoResult>
</ns1:LoginResult>
</soapenv:Body>
I need to deserialize the "LoginResult" part. I am aware of deserialization methods but I am struggling with the fact that A) there are namespaces and B) I just need a subset of the XML.
Maybe somebody could point me into the right direction.
Thx in advcance
Start with definition of LoginResult class.
[XmlRootAttribute(Namespace = "http://abc.def.schema", IsNullable = false, ElementName = "LoginResult")]
public class LoginResult
{
[XmlElement(Namespace ="")]
public int sessionId { get; set; }
[XmlElement(Namespace = "")]
public string organizationName { get; set; }
..... some more properties
}
Use XDocument class from System.Xml.Linq to parse the xml.
Find the "LoginResult" element.
Deserialize as LoginResult type.
var xDoc = XDocument.Parse(str);
var xLoginResult = xDoc.Root.Descendants().FirstOrDefault(d => d.Name.LocalName.Equals("LoginResult"));
var serializer = new XmlSerializer(typeof(LoginResult));
using (var reader = xLoginResult.CreateReader())
{
var result = (LoginResult)serializer.Deserialize(reader);
}
I am struggling with a subject that has a lot of variants, but I can't seem to find one that works for me, and I think it's because of the way that my JSON array is.
I'm not an expert in C# or JSON, but I already manage to "almost" get this to work. I need to get hand with the class that the JSON will deserialize to.
When I run the code I dont get an error, just a nulls in the xKisokData var.
The JSON data that I am getting. Their are these two different ones.
"{\"Event\": \"sConnection\",\"data[device]\": \"fb16f550-2ef1-11e5-afe9-ff37129acbf4\",\"data[mode]\": \"customer\",\"data[starttime]\": \"2015-07-22T16:07:42.030Z\",\"data[endtime]\": \"\"}"
"{\"Event\": \"Log\",\"data[id]\": \"2015-07-22T16:07:23.063Z\",\"data[messages][0][source]\": \"server\",\"data[messages][0][message]\": \"Server is listening on port 1553\"}"
The code I have so far:
// Read in our Stream into a string...
StreamReader reader = new StreamReader(JSONdataStream);
string JSONdata = reader.ReadToEnd();
JavaScriptSerializer jss = new JavaScriptSerializer();
wsKisokData[] xKisokData = jss.Deserialize<wsKisokData[]>(JSONdata);
My Class:
namespace JSONWebService
{
[DataContract]
[Serializable]
public class KisokEvent
{
public string eventTrigger { get; set; }
}
[DataContract]
[Serializable]
public class KisokData
{
public string data { get; set; }
}
[DataContract]
[Serializable]
public class wsKisokData
{
public KisokEvent KDEvent { get; set; }
public List<KisokData> KDData { get; set; }
}
}
I am sure that I don't understand the Deserialize process. Thanks for the help.
EDIT:
I put the JSON in the top part right from the debugger, here is the strings.
{
"Event": "sConnection",
"data[device]": "fb16f550-2ef1-11e5-afe9-ff37129acbf4",
"data[mode]": "customer",
"data[starttime]": "2015-07-22T16:07:42.030Z",
"data[endtime]": ""
}
{
"Event": "Log",
"data[id]": "2015-07-22T16:07:23.063Z",
"data[messages][0][source]": "server",
"data[messages][0][message]": "Server is listening on port 1553"
}
I would HIGHLY recommend using the json.net package off nuget instead.
You can generate template classes (models) for it by pasting the json into http://json2csharp.com/
Then use said models to convert the json into a c# object (deserializing) by doing a
var jsonStructure = JsonConvert.DeserializeObject<model>(json)
And query as if it was just a standard object
foreach (var x in jsonStructure.KDData)
{
doAction(x.data);
}
// for example
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).
I need to ask a general question. I don't have the code in front of me because I'm writing this on my iPhone.
I have a Class that represents a certain XML schema. I have a SPROC that returns this XML. What I need to do is deserialize the XML to this Class.
XML:
<xml>
<person>
<firstName>Bob</firstName>
<lastName>Robby</lastName>
</person>
</xml>
I need to deserialize this XML into the custom Person Class so I can loop through this Model and spit it out in the View. I'm sure there's some kind of casting involved, I just don't know how to do it.
My Solution:
public class Program {
public static void Main(string[] args) {
string xml = #"<xml><person><firstName>Bob</firstName><lastName>Robby</lastName></person></xml>";
var doc = XElement.Parse(xml);
var person = (from x in doc.Elements("person") select x).FirstOrDefault();
XmlSerializer serializer = new XmlSerializer(typeof(Person));
var sr = new StringReader(person.ToString());
// Use the Deserialize method to restore the object's state.
var myPerson = (Person)serializer.Deserialize(sr);
}
}
And Class:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Serialization;
namespace ConsoleApplication3 {
[XmlRoot("person")]
public class Person {
[XmlElement("firstName")]
public string FirstName { get; set; }
[XmlElement("lastName")]
public string LastName { get; set; }
}
}
in linq it would be something like this
XDocument xmlFile = XDocument.Parse(yourXml)
var people = (from x in xmlFile.Descendants("person")
select new Person(){
firstname = (string)x.Element("firstname").Value,
lastname = (string)x.Element("lastname").Value
});
I'm trying to get an XML file generated using a namespace as such:
<namespace:Example1>
<namespace:Part1>Value1</namespace:Part1>
</namespace:Example1>
I've tried using
[XmlAttribute(Namespace = "namespace")]
public string Namespace { get; set; }
but I'm clearly missing something. The structure I've used is
[XmlRoot("Example1")]
public class Blah
{
[XmlAttribute(Namespace = "namespace")]
public string Namespace { get; set; }
but all I get is
<Example1>
<Part1>Value1</Part1>
</Example1>
Any help would be greatly appreciated.
Edit:
[XmlRoot(ElementName="Chart2", Namespace="vc")]
doesn't work.
You can use the XmlSerializerNamespaces class to add the prefix for a given namespace in the xml.
I hope the below code will he you better.
[XmlRoot(ElementName = "Example1")]
public class Blah
{
public string Part1 { get; set; }
}
Blah bl = new Blah();
bl.Part1 = "MyPart1";
// Serialization
/* Create an XmlSerializerNamespaces object and add two prefix-namespace pairs. */
XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
ns.Add("namespace", "test");
XmlSerializer s = new XmlSerializer(typeof(Blah),"test");
TextWriter w = new StreamWriter(#"c:\list.xml");
s.Serialize(w, bl,ns);
w.Close();
/* Output */
<?xml version="1.0" encoding="utf-8"?>
<namespace:Example1 xmlns:namespace="test">
<namespace:Part1>MyPart1</namespace:Part1>
</namespace:Example1>
Can you try this on your Model.cs:
Copy the whole XML, then on the Model.cs:
Edit > Paste Special > Paste XML as Classes.
Might help you. ;)