I'd like to use LINQ to XML to extract values from an XML doc that has nested namespaces.
My question had partially been answered by another SO user: LINQ to XML w/ nested namespaces
I am still having trouble trying to figure out how to select the values into an object's properties.
Here are the resources for this problem:
Sample XML File Link
Contents of above link:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ns1:Envelope xmlns:ns1="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:ns3="http://www.3gpp.org/ftp/Specs/archive/23_series/23.140/schema/REL-6-MM7-1-4">
<ns1:Header>
<ns3:TransactionID ns1:mustUnderstand="1">XXXX-XXXX-XXXX-XXXX</ns3:TransactionID>
</ns1:Header>
<ns1:Body>
<ns3:DeliverReq>
<ns3:MM7Version>6.8.0</ns3:MM7Version>
<ns3:LinkedID>LINKED-ID-IS-HERE</ns3:LinkedID>
<ns3:Sender>
<ns3:Number>3025551212</ns3:Number>
</ns3:Sender>
<ns3:Recipients>
<ns3:To>
<ns3:Number displayOnly="false">11111</ns3:Number>
</ns3:To>
</ns3:Recipients>
<ns3:TimeStamp>2011-04-25T10:28:40.000Z</ns3:TimeStamp>
<ns3:UACapabilities UAProf="motok1c"/>
<ns3:Content allowAdaptations="true" href="cid:default.cid"/>
</ns3:DeliverReq>
</ns1:Body>
</ns1:Envelope>
Object to be filled from this XML:
public class TestClass
{
public string TransactionId { get; set; }
public string MessageType { get; set; }
public string Mm7Version { get; set; }
public string VaspId { get; set; }
public string VasId { get; set; }
public string Sender { get; set; }
public string Recipients { get; set; }
public string LinkedId { get; set; }
public string TimeStamp { get; set; }
public string Priority { get; set; }
public string Subject { get; set; }
public string Content { get; set; }
public string UaCapabilities { get; set; }
// not all properties appear in every XML message received
}
The SO example helped me understand the need to add a namespace (XNamespace), but I am falling short when trying to fill the object using a LINQ statement.
I think I need to do something like:
// (don't know the "from" in this case - decendants?
// which XML key / node?)
select new TestClass
{
TransactionId = [LINQ to select the "TransactionID" value that
appears under the Header key]
Mm7Version = [LINQ to select the "MM7Version" value, under the
DeliverReq key]
.....
}
What is the shape of the LINQ to be able to select values into properties of an object when you have XML like posted above?
I care about the data in 2 portions of the XML: The TransactionID value in the header, and the values that are under the DeliverReq. It is odd to be that they are spread out and it is more confusing than simply selecting the values strictly from the DeliverReq key. If I saw the FROM portion, and 2 to 3 of the properties filled with values from the XML then I would be able to pick it up from there.
Please let me know if you need any clarifications. My main problems are with the "FROM" portion of the LINQ and how to deal with the fact that TransactionId is outside of the DeliverReq tag. I think I can handle the other cases, such as the nesting on the values under the Sender and Recipients tags.
Since there is only properties for one of your TestClass instances in your XML, and the values for those are all over the place it makes sense to select them manually and then create the instance:
XDocument doc = XDocument.Load(#"test.xml");
XNamespace ns3 = "http://www.3gpp.org/ftp/Specs/archive/23_series/23.140/schema/REL-6-MM7-1-4";
string transactionId = doc.Descendants(ns3 + "TransactionID").Single().Value;
string mm7Version = doc.Descendants(ns3 + "MM7Version").Single().Value;
//...select the other elements
TestClass testClass = new TestClass() { TransactionId = transactionId,
Mm7Version = mm7Version};
Related
I have the following test XML string:
<?xml version="1.0" encoding="UTF-8"?>
<test id="myid">
<b>b1</b>
<a>a2</a>
<a>a1</a>
<b>b2</b>
</test>
which I deserialize using this class:
[XmlRoot(ElementName = "test")]
public class Test
{
[XmlElement(ElementName = "a")]
public List<string> A { get; set; }
[XmlElement(ElementName = "b")]
public List<string> B { get; set; }
[XmlAttribute(AttributeName = "id")]
public string Id { get; set; }
}
If I'm now going to serialize the object the result will be:
<?xml version="1.0" encoding="utf-16"?>
<test xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" id="myid">
<a>a2</a>
<a>a1</a>
<b>b1</b>
<b>b2</b>
</test>
Is there a way to keep the initial sort order?
I guess I can't use [XmlElementAttribute(Order = x)] cause the order shouldn't be hardcoded but identically with the initial xml.
I thought about adding an order property to my lists like that
[XmlRoot(ElementName="a")]
public class A
{
[XmlAttribute(AttributeName="order")]
public string Order { get; set; }
[XmlText]
public string Text { get; set; }
}
[XmlRoot(ElementName="b")]
public class B
{
[XmlAttribute(AttributeName="order")]
public string Order { get; set; }
[XmlText]
public string Text { get; set; }
}
[XmlRoot(ElementName="test")]
public class Test
{
[XmlElement(ElementName="a")]
public List<A> A { get; set; }
[XmlElement(ElementName="b")]
public List<B> B { get; set; }
[XmlAttribute(AttributeName="id")]
public string Id { get; set; }
}
but I don't know how to order by them when serializing.
You can do this with XmlSerializer by using a single collection to capture both the <a> and <b> elements and applying the [XmlElement(Name, Type = typeof(...))] attribute to it multiple times, once for each desired element name. Because you are using a single collection to deserialize both elements, the order is automatically preserved. However, to make this work, XmlSerializer must be able to determine the correct element name when re-serializing. There are two approaches to accomplish this, as documented in Choice Element Binding Support:
If the collection contains polymorphic items, the element name can be mapped to the concrete item type by using the [XmlElementAttribute(String, Type)] constructor. For instance, if you have a sequence of elements that might be strings or integers like so:
<Things>
<string>Hello</string>
<int>999</int>
</Things>
This can be bound to a collection as follows:
public class Things
{
[XmlElement(Type = typeof(string)),
XmlElement(Type = typeof(int))]
public List<object> StringsAndInts { get; set; }
}
If the collection contains only a single type of item, the element name can be encoded in an associated array of enum values, where the enum names correspond to the element names and the array itself is identified via the [XmlChoiceIdentifierAttribute] attribute.
For details, see the documentation examples.
I find option #1 easier to work with than option #2. Using this approach, the following model will deserialize and re-serialize your XML while successfully preserving the order of the <a> and <b> elements:
public abstract class StringElementBase
{
[XmlText]
public string Text { get; set; }
public static implicit operator string(StringElementBase element)
{
return element == null ? null : element.Text;
}
}
public sealed class A : StringElementBase
{
}
public sealed class B : StringElementBase
{
}
[XmlRoot(ElementName = "test")]
public class Test
{
[XmlElement("a", Type = typeof(A))]
[XmlElement("b", Type = typeof(B))]
public List<StringElementBase> Items { get; } = new List<StringElementBase>();
[XmlIgnore]
// For convenience, enumerate through the string values of the items.
public IEnumerable<string> ItemValues { get { return Items.Select(i => (string)i); } }
[XmlAttribute(AttributeName = "id")]
public string Id { get; set; }
}
Working .Net fiddle.
For more examples of using [XmlChoiceIdentifier] to deserialize a sequence of elements with different names to c# objects of the same type, see for instance here or here.
No, basically; XmlSerializer doesn't support that. If you want to use that option you'd need to write it manually with XDocument or XmlDocument or XmlReader.
I need to parse a xml document into object models that I've created but I can't figure out how to do so, I think it's because of my lack of understanding of the xml structure.
I've tried to get all the elements from the document and to create individual object from each based on their attributes I think they're called.
Here is my C# code :
var manifest = XDocument.Load(theDocument);
var allTheElements = manifest.Descendants();
foreach (var element in allTheElements)
{
//No idea how to parse each object into individual ManifestModel's
}
public class ManifestModel
{
public string Version { get; set; }
public string Resource { get; set; }
public string Size { get; set; }
public string Checksum { get; set; }
}
And here is the XML data :
<?xml version="1.0" encoding="UTF-8"?>
<manifest version="1.0.0" totalbytes="6131797">
<source uri="codeapi.io/Game/patches/">
<file resource="FooGame.sln" size="1125" checksum="530B9F1C2412A6D74EF017919074FD8966E5280D" />
<file resource=".vs\FooGame\v16\.suo" size="69120" checksum="438976A3681FDD503DB4FBFCBB5D420E9E8838DD" />
</source>
</manifest>
Just like we have json2csharp for JSON, we have Xml2Csharp for XML. There are probably lots of other sites that will do this.
Paste your XML and it generates this:
[XmlRoot(ElementName="file")]
public class File {
[XmlAttribute(AttributeName="resource")]
public string Resource { get; set; }
[XmlAttribute(AttributeName="size")]
public string Size { get; set; }
[XmlAttribute(AttributeName="checksum")]
public string Checksum { get; set; }
}
[XmlRoot(ElementName="source")]
public class Source {
[XmlElement(ElementName="file")]
public List<File> File { get; set; }
[XmlAttribute(AttributeName="uri")]
public string Uri { get; set; }
}
[XmlRoot(ElementName="manifest")]
public class Manifest {
[XmlElement(ElementName="source")]
public Source Source { get; set; }
[XmlAttribute(AttributeName="version")]
public string Version { get; set; }
[XmlAttribute(AttributeName="totalbytes")]
public string Totalbytes { get; set; }
}
One could call that lazy or cheating, but I don't see the point in writing code that can be generated for me in a second. You might not always get perfect results, but it's a good starting point. For example, it uses string for all attribute types. If you're expecting all numeric values you could replace those with int or long.
Now you can deserialize like this:
var serializer = new XmlSerializer(typeof(Manifest), new XmlRootAttribute("manifest"));
using (var stream = System.IO.File.OpenRead("test.xml"))
{
var deserialized = (Manifest)serializer.Deserialize(stream);
}
Once you've got the data deserialized into something, the rest is much easier. You can either use the auto-generated models or map them to your own.
Using LINQ...
c#
void Main()
{
string fileName = #"e:\Temp\GamePatches.xml";
XDocument manifest = XDocument.Load(fileName);
string version = manifest.Root.Attribute("version").Value;
List<ManifestModel> manifestModel = new List<ManifestModel>();
foreach (XElement e in manifest.Descendants("file"))
{
manifestModel.Add(new ManifestModel() { Version = version
, Resource = (string)e.Attribute("resource").Value
, Size = (string)e.Attribute("size").Value
, Checksum = (string)e.Attribute("checksum").Value }
);
}
}
// Define other methods and classes here
public class ManifestModel
{
public string Version { get; set; }
public string Resource { get; set; }
public string Size { get; set; }
public string Checksum { get; set; }
}
I spent a lot of time working on a similar app that parsed through XML Schema, and I found the easiest way is to turn the XML Document into an XmlNodeList. From here you can use the SelectNodes and SelectSingleNodes to navigate through it. Take a look at this:https://learn.microsoft.com/en-us/dotnet/api/system.xml.xmlnode.selectnodes?view=netframework-4.8, but basically what you do is create an xpath string which selects the node you need. Here is some documentation on that: https://learn.microsoft.com/en-us/dotnet/standard/data/xml/select-nodes-using-xpath-navigation
I have a string in JSON format as follows
string jsonStr = "{"Type":1, "Id":1000,"Date":null,"Group": "Admin","Country":"India","Type":1}";
I want to modify this string so that Id attribute should always be the first. The order of attributes matters.
Is there any way I can modify this string.
I tried searching google but did not find appropriate solution.
Any help would be appreciated.
EDIT:
I also tried to deserialize object using
object yourOjbect = new JavaScriptSerializer().DeserializeObject(jsonStr);
But here also the "type" attribute comes first. I dont find any way to move the attributes within this deserialized object
It's possible. Use the JsonProperty attribute, property Order.
http://www.newtonsoft.com/json/help/html/JsonPropertyOrder.htm.
Let me know if it works.
Instead of attempting to manipulate the order of the outputted JSON and comparing strings, I would transform both JSON strings that you want to compare, into objects and then perform your comparison. You could then compare individual properties or entire objects with something like the following:
void CompareJSON()
{
string json = #"{""Type"":1, ""Id"":1000,""Date"":null,""Group"": ""Admin"",""Country"":""India"",""Type"":1}";
string jsonToCompare = "JSON TO COMPARE";
MyObject myJsonObject = JsonConvert.DeserializeObject<MyObject>(json);
MyObject myJsonObjectToCompare = JsonConvert.DeserializeObject<MyObject>(jsonToCompare);
if (myJsonObject.Id == myJsonObjectToCompare.Id)
{
// Do something
}
}
class MyObject
{
public int Id { get; set; }
public int Type { get; set; }
public DateTime? Date { get; set; }
public string Group { get; set; }
public string Country { get; set; }
}
Please note that this example is carried out using the Newtonsoft.JSON library. More information on the library can be found here.
Just make your JSON into a c# class with Id first and then serialize it again if that is what you need. You do know that you have "Type" twice in the JSON string? In this solution it will get "fixed" so you only have it once as it should be. But if your string really is with two Type this wont work since the strings will be incorrect. If they really are like that you need to do some ugly string manipulation to fix the order but i hope the first string is incorrect only here and not in your code.
private void Test() {
string json = #"{""Type"":1, ""Id"":1000,""Date"":null,""Group"": ""Admin"",""Country"":""India"",""Type"":1}";
JavaScriptSerializer jsonSerializer = new JavaScriptSerializer();
MyJsonObject myJsonObject = jsonSerializer.Deserialize<MyJsonObject>(json);
string s = jsonSerializer.Serialize(myJsonObject);
//Returns: {"Id":1000,"Type":1,"Date":null,"Group":"Admin","Country":"India"}
}
class MyJsonObject {
public int Id { get; set; }
public int Type { get; set; }
public DateTime? Date { get; set; }
public string Group { get; set; }
public string Country { get; set; }
}
I am getting the flight details from the expedia API, please check the link below for the XML format.
The result output contains Rateinfo then flight details as separate node and there is no relation between rateinfo and flightsegment.
Normally I load the XML into dataset and use the dataset to populate the records, but in this case there is no relation between rate and flight segment, how will I parse this XML in C#. I need to show the user the flight segments and the corresponding rates.
http://api.ean.com/ean-services/rs/air/200919/xmlinterface.jsp?cid=55505&resType=air&intfc=ws&apiKey=fc9hjrvrur9vr4y2dqa249w4&xml=<AirSessionRequest method="getAirAvailability"><AirAvailabilityQuery><originCityCode>MCI</originCityCode><destinationCityCode>CLT</destinationCityCode><departureDateTime>09/01/2011 01:00 AM</departureDateTime><returnDateTime>09/04/2011 01:00 AM</returnDateTime><fareClass>Y</fareClass><tripType>R</tripType><Passengers><adultPassengers>2</adultPassengers></Passengers><xmlResultFormat>1</xmlResultFormat><searchType>2</searchType></AirAvailabilityQuery></AirSessionRequest>
Using LINQ to XML as Braveyard mentioned above might help. You could use LINQ to break down the XML and deal with one group of rate info and flight segment at a time. If you use anonymous types you could make your own connection between one rate info and the associated flight segments, and then store that in the database.
Here is a rough example to get you going down that path:
XDocument xDoc = new XDocument();
xDoc = xDoc.Parse(responseXML); // Parse will take a string and load the XDocument with it
// You can also use Load to load from a file, StreamReader, etc.
// First, grab a collection of all the AirAvailabilityReply
var airAvailability = from x in xDoc.Descendants("AirAvailabilityReply")
select x;
// Now loop through each of the query results in the collection
foreach (var available in airAvailability)
{
// Get the rate info
var rates = from r in available.Descendants("RateInfo")
select new RateInfo {
NativeBaseFare = r.Element("nativeBaseFare").Value,
NativeTotalPrice = r.Element("NativeTotalPrice").Value,
// etc
};
// Get the flight segment info
var segments = from s in available.Descendants("FlightSegment")
select new FlightSegment {
SegmentOutgoing = s.Element("segmentOutgoing").Value,
AirlineCode = s.Element("airlineCode").Value,
// etc
};
// Now you can take RateInfo (should only be one) and the FlightSegments (should be a collection of FlightSegments) and put them into your database.
}
In the above example, I'm assuming you have two classes (RateInfo and FlightSegment) and you'll populate the properties with the corresponding values from the XML.
This is probably not the most efficient example, but hopefully it'll give you an idea of how to tackle this using LINQ to XML.
I think the basic and powerful way of parsing xml in C# would be Linq2XML.It has really easy-to-consume methods and please check the answer below:
A dataset represents relational data much easier than hierarchial data, which is what the xml represents, and that is probably the crux of your problem.
Linq2Xml is one option, or you could create a set of classes that are seriealizable and deserialize the xml into instances of the class, so then you can just iterate through the classes with foreach, or use linq on them.
I'm not saying this is the best way to go about doing what you need, but it is relatively tidy, and you don't need to deal with xml, just class instances. I tested this with the xml you provided and it worked quite well, although I do think that the Linq2Xml method might be more performant - Not 100% sure though.
Assuming your xml is in a variable called sXmlResult:
public class Test
{
public void Run()
{
//Load the xml and serialize it into instances of our classes
System.Xml.Serialization.XmlSerializer ser = new System.Xml.Serialization.XmlSerializer(typeof(AirAvailabilityResults));
System.IO.StringReader sr = new System.IO.StringReader(sXmlResult);
AirAvailabilityResults results = (AirAvailabilityResults)ser.Deserialize(sr);
//Now we can access all the data like we would any other object.
foreach (AirAvailabilityReply reply in results.AirAvailabilityReply)
{
double dNativeBaseFare = reply.RateInfo.nativeBaseFare;
foreach (FlightSegment segment in reply.FlightSegment)
{
int iFlightNumber = segment.flightNumber;
}
}
}
}
//Create the seriealizable classes to represent the xml.
//I created these by infering the schema from the xml. These classes may need some changes, if
//they don't exactly match the actual schema that expedia uses
public class AirAvailabilityResults
{
[System.Xml.Serialization.XmlElement()]
public AirAvailabilityReply[] AirAvailabilityReply { get; set; }
[System.Xml.Serialization.XmlAttribute()]
public int size {get;set;}
[System.Xml.Serialization.XmlElement()]
public string cacheKey {get;set;}
[System.Xml.Serialization.XmlElement()]
public string cacheLocation {get;set;}
}
public class AirAvailabilityReply
{
public enum SupplierType
{
S
}
public enum TripType
{
R
}
public enum TicketType
{
E
}
[System.Xml.Serialization.XmlElement()]
public SupplierType supplierType {get;set;}
[System.Xml.Serialization.XmlElement()]
public TripType tripType {get;set;}
[System.Xml.Serialization.XmlElement()]
public TicketType ticketType {get;set;}
public RateInfo RateInfo { get; set; }
[System.Xml.Serialization.XmlElement()]
public FlightSegment[] FlightSegment {get;set;}
}
public class RateInfo
{
public double nativeBaseFare {get;set;}
public double nativeTotalPrice {get;set;}
public string nativeCurrencyCode { get; set; }
public double displayBaseFare {get;set;}
public double displayTotalPrice {get;set;}
public string displayCurrencyCode { get; set; }
}
public class FlightSegment
{
public bool segmentOutgoing {get;set;}
public string airlineCode {get;set;}
public string airline {get;set;}
public int flightNumber {get;set;}
public string originCityCode {get;set;}
public string destinationCityCode {get;set;}
public string departureDateTime {get;set;}
public string arrivalDateTime { get; set; }
public string fareClass {get;set;}
public string equipmentCode {get;set;}
public int numberOfStops {get;set;}
public string originCity {get;set;}
public string originStateProvince {get;set;}
public string originCountry {get;set;}
public string destinationCity {get;set;}
public string desintationStateProvince {get;set;}
public string destinationCountry { get; set; }
}
The API indicates that the RateInfo node corresponds to all the sibling FlightSegment nodes appended. So you dont have RateInfo for 1 FlightSegment
OK. I'm trying to work on communicating with the Pivotal Tracker API, which only returns data in an XML format. I have the following XML that I'm trying to deserialize into my domain model.
<?xml version="1.0" encoding="UTF-8"?>
<stories type="array" count="2" total="2">
<story>
<id type="integer">2909137</id>
<project_id type="integer">68153</project_id>
<story_type>bug</story_type>
<url>http://www.pivotaltracker.com/story/show/2909137</url>
<current_state>unscheduled</current_state>
<description></description>
<name>Test #2</name>
<requested_by>Anthony Shaw</requested_by>
<created_at type="datetime">2010/03/23 20:05:58 EDT</created_at>
<updated_at type="datetime">2010/03/23 20:05:58 EDT</updated_at>
</story>
<story>
<id type="integer">2909135</id>
<project_id type="integer">68153</project_id>
<story_type>feature</story_type>
<url>http://www.pivotaltracker.com/story/show/2909135</url>
<estimate type="integer">-1</estimate>
<current_state>unscheduled</current_state>
<description></description>
<name>Test #1</name>
<requested_by>Anthony Shaw</requested_by>
<created_at type="datetime">2010/03/23 20:05:53 EDT</created_at>
<updated_at type="datetime">2010/03/23 20:05:53 EDT</updated_at>
</story>
</stories>
My 'story' object is created as follows:
public class story
{
public int id { get; set; }
public int estimate { get; set; }
public int project_id { get; set; }
public string story_type { get; set; }
public string url { get; set; }
public string current_state { get; set; }
public string description { get; set; }
public string name { get; set; }
public string requested_by { get; set; }
public string labels { get; set; }
public string lighthouse_id { get; set; }
public string lighthouse_url { get; set; }
public string owned_by { get; set; }
public string accepted_at { get; set; }
public string created_at { get; set; }
public attachment[] attachments { get; set; }
public note[] notes { get; set; }
}
When I execute my deserialization code, I receive the following exception:
Exception:
There is an error in XML document (2, 2).
Inner Exception:
<stories xmlns=''> was not expected.
I can deserialize the individual stories just fine, I just cannot deserialize this xml into an array of 'story' objects
And my deserialization code (value is a string of the xml)
var byteArray = Encoding.ASCII.GetBytes(value);
var stream = new MemoryStream(byteArray);
var deserializedObject = new XmlSerializer(typeof (story[])).Deserialize(stream)
Does anybody have any ideas?
Let me offer a more concise solution. Set your deserialization up to look like this:
var deserializedObject = new XmlSerializer(typeof(story[]), new XmlRootAttribute("stories")).Deserialize(stream);
By specifying that second parameter in the XmlSerializer, you can avoid having to stub out that class. It lets the serializer know what the root element's name is.
For this to work, the name of the class that represents the array-element type must exactly match the XML name, e.g. class story {}, <story>. You can get around this (and I'd recommend it as a best practice anyway) by specifying the XmlType:
[XmlType("story")]
public class Story
{
...
}
I prefer to do this as it frees me from being stuck with the XML type name.
The problem is that you have no property named "stories". The XML Serializer has no idea what to do with the stories element when it sees it.
One thing you could try is to create a "stories" class:
public class stories : List<story> {}
and use
var byteArray = Encoding.ASCII.GetBytes(value);
stories deserializedObject = null;
using (var stream = new MemoryStream(byteArray))
{
var storiesSerializer = new XmlSerializer(typeof (stories));
deserializedObject = (stories)storiesSerializer .Deserialize(stream);
}
Try something like
public class stories
{
[XmlElement("story")]
public story[] storyarray { get; set; }
}
...
var byteArray = Encoding.ASCII.GetBytes(value);
XmlSerializer serializer = new XmlSerializer(typeof(stories));
stories myStories = null;
using (var stream = new MemoryStream(byteArray))
{
myStories = (stories)serializer.Deserialize(stream);
}
foreach (story stor in myStories.storyarray)
Console.WriteLine(stor.story_type);
Edit: Updated code sample to use using statement based on feedback.
XMSerializer expects an XML Namespace with which to understand your XML from.
xmlns="http://schemas.microsoft.com"
... ought to do. See the XML sample at the bottom of this page.
I would recommend that you generate an XSD from some sample XML you get from the web service. Then, with that XSD, you can generate the classes that have all the proper serialization attributes affixed to them.
To generate a schema (unless you prefer to write your own), open the sample XML file in Visual Studio, and select the XML -> Create Schema menu option. Save that XSD.
To generate the classes, run the XSD command from the VS command prompt. If you run it without parameters, it'll show you the command-line parameters you can use.
Now you can create a type-safe XML serializer.