I'm trying to deserialize some xml without having the original class that was used to create the object in xml. The class is called ComOpcClientConfiguration.
It's succesfully setting the ServerUrl and ServerUrlHda values, but not rest of them...
So what I'm asking is: How can I make the rest of these values get set properly, and why aren't they working with my current code.
Here is my deserialization code:
conf is an XElement which represents the ComClientConfiguration xml
DataContractSerializer ser = new DataContractSerializer(typeof(ComClientConfiguration), new Type[] {typeof(ComClientConfiguration), typeof(ComOpcClientConfiguration) });
ComOpcClientConfiguration config = (ComOpcClientConfiguration)ser.ReadObject(conf.CreateReader());
I don't know why I have to have ComClientConfiguration and ComOpcClientConfiguration, there's probably a better way to do the known types hack I have. But for now it's what I have.
Here is the xml as it looks in the file.
<ComClientConfiguration xsi:type="ComOpcClientConfiguration" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<ServerUrl>The url</ServerUrl>
<ServerName>a server name </ServerName>
<ServerNamespaceUrl>a namespace url</ServerNamespaceUrl>
<MaxReconnectWait>5000</MaxReconnectWait>
<MaxReconnectAttempts>0</MaxReconnectAttempts>
<FastBrowsing>true</FastBrowsing>
<ItemIdEqualToName>true</ItemIdEqualToName>
<ServerUrlHda>hda url</ServerUrlHda>
</ComClientConfiguration>
Here is the class I built to deserialize into:
[DataContract(Name = "ComClientConfiguration", Namespace = "http://opcfoundation.org/UA/SDK/COMInterop")]
public class ComClientConfiguration
{
public ComClientConfiguration() { }
//Prog-ID for DA-connection
[DataMember(Name = "ServerUrl"), OptionalField]
public string ServerUrl;//url
[DataMember(Name = "ServerName")]
public string ServerName;
[DataMember(Name = "ServerNamespaceUrl")]
public string ServerNamespaceUrl;//url
[DataMember(Name = "MaxReconnectWait")]
public int MaxReconnectWait;
[DataMember(Name = "MaxReconnectAttempts")]
public int MaxReconnectAttempts;
[DataMember(Name = "FastBrowsing")]
public bool FastBrowsing;
[DataMember(Name = "ItemIdEqualToName")]
public bool ItemIdEqualToName;
//ProgID for DA-connection
[DataMember, OptionalField]
public string ServerUrlHda;//url
}
I additionally had to make this class, it's the same but with a different name. Used for known types in the Serializer because I don't know exactly how the whole type naming stuff works.
[DataContract(Name = "ComOpcClientConfiguration", Namespace = "http://opcfoundation.org/UA/SDK/COMInterop")]
public class ComOpcClientConfiguration
{
public ComOpcClientConfiguration() { }
... Same innards as ComClientConfiguration
}
Data-contract-serializer is... Fussy. In particular, I wonder if simply element order is the problem here. However, it is also not necessarily the best tool for working with XML. XmlSerializer is probably a more robust here - it can handle a much better range of XML. DCS simply isn't intended with that as it's primary goal.
With simple XML you often don't even need any attributes etc. You can even use xsd.exe on your existing XML to generate the matching c# classes (in two steps; XML-to-xsd; xsd-to-c#).
To get all values, try hardcoding the order (otherwise maybe it tries the alphabetical order):
[DataMember(Name = "ServerUrl", Order = 0)]
..
[DataMember(Name = "ServerName", Order = 1)]
..
[DataMember(Name = "ServerNamespaceUrl", Order = 2)]
..
[DataMember(Name = "MaxReconnectWait", Order = 3)]
..
Related
I do have a problem with serializing a ArrayList. Most propably I use wrong XML Attributes for it since I when I changed them it would not even serialize it and got errors like 'The type may not be used in this context.'
I need to use a non generic ArrayList. On adding [XmlArray("LineDetails")] made this code to run but the output is not correct, it should give me the LineDetails structure. Any idea how to fix this?
This is a part of a whole xml like Document > Header > LineCollection > LineDeatails.
The problem is only with this details if I use a standard string field it is ok but the range of the colletion if changing with every document.
[XmlType(TypeName = "LineCollection")]
public class LineCollection
{
public String LineCount{ get; set; }
// [XmlElement(ElementName = "LineDetails")]
[XmlArray("LineDetails")]
public ArrayList LineDetails{ get; set; }
}
public class LineDetails: ArrayList
{
public String LineNum{ get; set; }
public String ItemId{ get; set; }
public String ItemName{ get; set; }
//... only strings
}
public class Utf8StringWriter : StringWriter
{
public override Encoding Encoding => new UTF8Encoding(false);
}
public string Serialize()
{
// var xmlserializer = new XmlSerializer(this.GetType());
var xmlserializer = new XmlSerializer(this.GetType(), new Type[] { typeof(LineDetails) });
var Utf8StringWriter = new Utf8StringWriter();
var xns = new XmlSerializerNamespaces();
xns.Add(string.Empty, string.Empty);
using (var writer = XmlWriter.Create(Utf8StringWriter))
{
xmlserializer.Serialize(writer, this, xns);
return Utf8StringWriter.ToString();
}
}
And the incorrect output of this...
<LineColletion>
<LineDetails>
<anyType xmlns:p5="http://www.w3.org/2001/XMLSchema-instance" p5:type="ArrayOfAnyType"/>
<anyType xmlns:p5="http://www.w3.org/2001/XMLSchema-instance" p5:type="ArrayOfAnyType"/>
<anyType xmlns:p5="http://www.w3.org/2001/XMLSchema-instance" p5:type="ArrayOfAnyType"/>
</LineDetails>
</LineColletion>
it should be like this
<LineColletion>
<LineDetails>
<LineNum>1</LineNum>
<ItemId>Item_2321</ItemId>
<ItemName>TheItemName</ItemName>
</LineDetails>
<LineDetails>
<LineNum>2</LineNum>
<ItemId>Item_232100000</ItemId>
<ItemName>TheItemName0</ItemName>
</LineDetails>
<LineDetails>
<LineNum>3</LineNum>
<ItemId>Item_23217777</ItemId>
<ItemName>TheItemName7</ItemName>
</LineDetails>
</LineColletion>
Now the wrong xml looks like this...
<LineDetails>
<anyType xmlns:p5="http://www.w3.org/2001/XMLSchema-instance" p5:type="LineDetails">
<LineNum>1</LineNum>
<ItemId>Item_2321</ItemId>
<ItemName>TheItemName</ItemName>
</anyType>
<anyType xmlns:p5="http://www.w3.org/2001/XMLSchema-instance" p5:type="LineDetails">
<LineNum>2</LineNum>
<ItemId>Item_2321</ItemId>
<ItemName>TheItemName</ItemName>
</anyType>
</LineDetails>
You may generate the required XML by modifying your data model as follows:
[XmlType(TypeName = "LineColletion")] // Fixed: TypeName. But do you want LineColletion (misspelled) or LineCollection (correctly spelled)? Your XML shows LineColletion but your code used LineCollection.
public class LineCollection
{
public String LineCount{ get; set; }
[XmlElement("LineDetails", typeof(LineDetails))] // Fixed -- specify type(s) of items in the ArrayList.
public ArrayList LineDetails{ get; set; }
}
public class LineDetails // Fixed: removed inheritance from ArrayList.
{
public String LineNum{ get; set; }
public String ItemId{ get; set; }
public String ItemName{ get; set; }
//... only strings
}
Notes:
Your model makes LineDetails inherit from ArrayList. XmlSerializer will never serialize collection properties, it will only serialize collection items. In order to serialize it correctly, I removed the inheritance since you don't seem to be using it anyway.
If you really need LineDetails to inherit from ArrayList, you will need to implement IXmlSerializable or replace it collection with a DTO.
Implementing IXmlSerializable is tedious and error-prone. I don't recommend it.
Your LineDetails collection is serialized without an outer wrapper element. To make the serializer do this, apply XmlElementAttribute to the property.
ArrayList is an untyped collection, so you must inform XmlSerializer of the possible types it might contain. You have two options:
Assigning a specific type to a specific element name by setting XmlElementAttribute.Type (or XmlArrayItemAttribute.Type), OR
Adding xsi:type attributes by informing the serializer of additional included types. You are doing this by passing them into the constructor, which is why you are seeing the p5:type="LineDetails" attribute.
Since you don't want the attributes, you need to set the element name by setting the type like so:
[XmlElement("LineDetails", typeof(LineDetails))]
The XML element corresponding to your LineCollection is named <LineColletion>. Note that the spelling is inconsistent. You will need to set [XmlType(TypeName = "LineColletion")] to the name you actually want.
Demo fiddle here.
I have an List<AnimalsEnum> Foo property in a class that I'm serializing to XML with RestSharp for the body of a request. I'd like the output to be:
<rootNode>
... existing content...
<Foo>Elephant</Foo>
<Foo>Tiger</Foo>
.... more content
Instead, for the relevant serialisation part, I have
<Foo>
<AnimalsEnum />
<AnimalsEnum />
</Foo>
I'd like to convert the enum values to strings and remove the container element that is automatically added. Is this possible with RestSharp? I thought it may be possible with attributes, but apparently not. Am I going to have to wrangle this output myself with a custom serialiser?
Code is difficult to post, but keeping with the example:
class Bar
{
public string Name{get;set;}
public List<AnimalsEnum> Foo{get;set;}
public enum AnimalsEnum {Tiger,Elephant,Monkey}
}
and to serialize into a request
var req = new RestSharp.RestRequest(RestSharp.Method.POST);
req.RequestFormat = RestSharp.DataFormat.Xml;
req.AddQueryParameter("REST-PAYLOAD", "");
req.AddXmlBody(myBar);
You can use the built-in DotNetXmlSerializer of RestSharp to make Microsoft's XmlSerializer do the actual serialization. Then you can use XML serialization attributes to specify that the List<AnimalsEnum> of Bar should be serialized without an outer container element by applying [XmlElement]:
public class Bar
{
public string Name { get; set; }
[System.Xml.Serialization.XmlElement]
public List<AnimalsEnum> Foo { get; set; }
public enum AnimalsEnum { Tiger, Elephant, Monkey }
}
Then, when making the request, do:
var req = new RestSharp.RestRequest(RestSharp.Method.POST);
// Use XmlSerializer to serialize Bar
req.XmlSerializer = new RestSharp.Serializers.DotNetXmlSerializer();
req.RequestFormat = RestSharp.DataFormat.Xml;
req.AddQueryParameter("REST-PAYLOAD", "");
req.AddXmlBody(myBar);
Note that Bar must be public because XmlSerializer can only serialize public types.
I am retrieving XML documents from a web service I have no control over. The XML is formatted similarly to the following:
<?xml version="1.0"?>
<ns:obj xmlns:ns="somenamespace">
<address>1313 Mockingbird Lane</address>
<residents>5</residents>
</ns:obj>
where the root node is in the "ns" namespace, but none of its child elements are.
After some trial and error, I found that I could deserialize the document to a C# object by doing the following:
[XmlRoot(Namespace="somenamespace", ElementName="obj")]
public class xmlObject
{
[XmlElement(Namespace = "")]
public string address { get; set; }
[XmlElement(Namespace = "")]
public int residents { get; set; }
}
class Program
{
static void Main(string[] args)
{
string xml =
"<?xml version=\"1.0\"?>" +
"<ns:obj xmlns:ns=\"somenamespace\">" +
" <address>1313 Mockingbird Lane</address>" +
" <residents>5</residents>" +
"</ns:obj>";
var serializer = new XmlSerializer(typeof(xmlObject));
using (var reader = new StringReader(xml))
{
var result = serializer.Deserialize(reader) as xmlObject;
Console.WriteLine("{0} people live at {1}", result.residents, result.address);
// Output: "5 people live at 1313 Mockingbird lane"
}
}
}
If I omit the XmlElementAttribute on the individual members, I instead get an empty object. I.e. The output reads
0 people live at
(result.address is equal to null.)
I understand the rationale behind why the deserialization process works like this, but I'm wondering if there is a less verbose way to tell XmlSerializer that the child elements of the object are not in the same namespace as the root node.
The objects I'm working with in production have dozens of members and, for cleanliness sake, I'd like to avoid tagging all of them with [XmlElement(Namespace = "")] if it's easily avoidable.
You can combine XmlRootAttribute with XmlTypeAttribute to make it so the root element, and the root element's elements, have different namespaces:
[XmlRoot(Namespace="somenamespace", ElementName="obj")]
[XmlType(Namespace="")]
public class xmlObject
{
public string address { get; set; }
public int residents { get; set; }
}
Using the type above, if I deserialize and re-serialize your XML I get:
<q1:obj xmlns:q1="somenamespace">
<address>1313 Mockingbird Lane</address>
<residents>5</residents>
</q1:obj>
Sample fiddle.
If you know the contract with the web service, why not use a DataContractSerializer to deserialize the XML into the objects?
I want to deserialize XML to object in C#, object has one string property and list of other objects.
There are classes which describe XML object, my code doesn't work (it is below, XML is at end of my post). My Deserialize code doesn't return any object.
I think I do something wrong with attributes, could you check it and give me some advice to fix it.
Thanks for your help.
[XmlRoot("shepherd")]
public class Shepherd
{
[XmlElement("name")]
public string Name { get; set; }
[XmlArray(ElementName = "sheeps", IsNullable = true)]
[XmlArrayItem(ElementName = "sheep")]
public List<Sheep> Sheeps { get; set; }
}
public class Sheep
{
[XmlElement("colour")]
public string colour { get; set; }
}
There is C# code to deserialize XML to objects
var rootNode = new XmlRootAttribute();
rootNode.ElementName = "createShepherdRequest";
rootNode.Namespace = "http://www.sheeps.pl/webapi/1_0";
rootNode.IsNullable = true;
Type deserializeType = typeof(Shepherd[]);
var serializer = new XmlSerializer(deserializeType, rootNode);
using (Stream xmlStream = new MemoryStream())
{
doc.Save(xmlStream);
var result = serializer.Deserialize(xmlStream);
return result as Shepherd[];
}
There is XML example which I want to deserialize
<?xml version="1.0" encoding="utf-8"?>
<createShepherdRequest xmlns="http://www.sheeps.pl/webapi/1_0">
<shepherd>
<name>name1</name>
<sheeps>
<sheep>
<colour>colour1</colour>
</sheep>
<sheep>
<colour>colour2</colour>
</sheep>
<sheep>
<colour>colour3</colour>
</sheep>
</sheeps>
</shepherd>
</createShepherdRequest>
XmlRootAttribute does not change the name of the tag when used as an item. The serializer expects <Shepherd>, but finds <shepherd> instead. (XmlAttributeOverrides does not seem to work on arrays either.) One way to to fix it, is by changing the case of the class-name itself:
public class shepherd
{
// ...
}
An easier alternative to juggling with attributes, is to create a proper wrapper class:
[XmlRoot("createShepherdRequest", Namespace = "http://www.sheeps.pl/webapi/1_0")]
public class CreateShepherdRequest
{
[XmlElement("shepherd")]
public Shepherd Shepherd { get; set; }
}
The web service I consume responces with json data.
it gives resultObject as array:
resultObject:[{object1}, {object2},...] if there more then one object
and it returns
resultObject:{object1} if there only one object.
for serializing in .NET I created a "static" structure of classes to map json schema. But if in one case i've got an array (list) of objects an in other case just one object, how is it possible to handle this situation?
I have found a plethora of ugly solutions to this one, but so far goes:
If you use the System.Web.Script.Serialization.JavaScriptSerializer you have very limited control. If the result data type is simple, you could simply use the DeserializeObject method; it will translate everything into Dictionary and the "resultObject" property will in the first case be a Dictionary while the latter case will turn it into an array of such dictionary. It will not save you the headache of the final translation, but you will get the data into dictionaries which could be considered a first step.
I also attempted to use the KnownTypes and the DataContractJsonSerializer, but alas the datacontract serializer needs "hints" in the form of specially named properties to aid it deserializing. (Why am I using the KnownType attribute wrong?). This is a hopeless strategy if you don't have any control of the serialization which I guess is the case for you.
So now we are down to the butt-ugly solutions of which trial-and-error is my first choice:
When using the ScriptSerializer the conversion will fail with an InvalidOperationException if anything is wrong. I then created two data types one with data-as-arrays and one where data is a single instance (the DataClass is my invention since you do not specify the data types):
[DataContract]
public class DataClass
{
[DataMember]
public string FirstName { get; set; }
[DataMember]
public int BirthYear { get; set; }
public override string ToString()
{
return "FirstName : '" + FirstName + "', BirthYear: " + BirthYear;
}
}
[DataContract]
public class ResultSingle
{
[DataMember]
public DataClass Data { get; set; }
}
[DataContract]
public class ResultArray
{
[DataMember]
public List<DataClass> Data { get; set; }
}
Using these data types it is possible to translate using
JavaScriptSerializer jSer = new JavaScriptSerializer();
var one = jSer.Deserialize<ResultSingle>(jsonWithSingleInstance);
var many = jSer.Deserialize<ResultArray>(jsonWithArray);
But this of course require you to known the data type in advance and you get two different result types. Instead you could choose to always convert to ResultArray. If you get an exception you should convert as ResultSingle and then instantiate the ResultArray and manually add the data object to the Data list:
static ResultArray ConvertJson(string json)
{
ResultArray fromJson;
JavaScriptSerializer jSer = new JavaScriptSerializer();
try
{
fromJson = jSer.Deserialize<ResultArray>(json);
return fromJson;
}
catch (InvalidOperationException)
{
var single = jSer.Deserialize<ResultSingle> (json);
fromJson = new ResultArray();
fromJson.Data = new List<DataClass>();
fromJson.Data.Add(single.Data);
return fromJson;
}
}
static void Main(string[] args)
{
var jsonWithSingleInstance = "{\"Data\":{\"FirstName\":\"Knud\",\"BirthYear\":1928}}";
var jsonWithArray = "{\"Data\":[{\"FirstName\":\"Knud\",\"BirthYear\":1928},{\"FirstName\":\"Svend\",\"BirthYear\":1930}]}";
var single = ConvertJson(jsonWithSingleInstance);
var array = ConvertJson(jsonWithArray);
}
I do not say this is a beautiful solution (it isn't), but it should do the job.
You could also look at json.net which seem to be the json serializer of choice in .NET: How to install JSON.NET using NuGet?
Well, my service provider finally said that it is really a bug.
http://jira.codehaus.org/browse/JETTISON-102
says that is it because of java version that they use.