trying to deserialize a Xml string, but always get problem for elements like these:
<Taxable />
<DefaultPurchasePrice />
My C# code snippet:
[XmlRoot(ElementName = "Product", Namespace = "http://api.test.com/version/1", IsNullable = false)]
public class Product
{
public Guid Guid { get; set; }
public string ProductName { get; set; }
public bool Taxable { get; set; }
public Decimal DefautSellPrice { get; set; }
[XmlElement("DefaultPurchasePrice")]
public string DefaultPurchasePriceElement
{
get
{
if (DefaultPurchasePrice == null)
return String.Empty;
else
return DefaultPurchasePrice.ToString();
}
set
{
if (value == null | value.Length == 0)
DefaultPurchasePrice = null;
else
DefaultPurchasePrice = Convert.ToDecimal(value);
}
}
[XmlIgnore]
public decimal? DefaultPurchasePrice{ get; set;}
}
Seems like
xsi:nil="true"
attribute in XML should solve my problem. But as we are using XML provided by from a REST server as part of an API testing. We don't have direct control how the XML be constructed, but we can give them feedback. So I think I should explicitly ask them to fix their XML, as it is their XML's problem right?
In the mean time, I could get individual elements deserialized by the following code:
[XmlElement("DefaultPurchasePrice")]
public string DefaultPurchasePriceElement
{
get
{
if (DefaultPurchasePrice == null)
return String.Empty;
else
return DefaultPurchasePrice.ToString();
}
set
{
if (value == null | value.Length == 0)
DefaultPurchasePrice = null;
else
DefaultPurchasePrice = Convert.ToDecimal(value);
}
}
[XmlIgnore]
public decimal? DefaultPurchasePrice{ get; set;}
But there are quite a few null elements in the XML string, and again, the other party could fix their XML so I don't need do anything to my deserialize code in that case right?
Anyway, could I do something in my code before deserialization so the XML could have proper xsi:nil="true" attribute for null elements so that I don't need do much in my C# code but can quickly fix their XML?
I am thinking about #Ryan's solution in the 2nd last from here: deserialize-xml-with-empty-elements-in-c, but not sure are there any better solutions?
EDIT:
Just did a small test, adding xsi:nill='true' in XML null elements will indeed working with my existing C# code.
But I do need make sure my C# class mapped from XML have nullable datattype for those null elements comeing from XML with xsi:nill='true'. But it make sense: when some datafield come from XML might be a null type, I need explicitly define the correspond datatype as nullable. I am much happy with that rather than my current solution.
I don't know the answer to your problem, but it seems to me that asking your colleagues to fix their XML isn't the right answer. It is common wisdom when writing network and file format code to "Be conservative in what you give, but accepting in what you receive", or some such.
That is, you should be prepared to receive just about ANYTHING in your incoming XML stream. If the XML is well-formed and contains the elements and attributes you require, you should be able to parse it correctly. If it has elements you don't permit, you should gracefully either ignore them or raise an error condition. If the XML is not well-formed, you should raise an error.
Otherwise your program won't be robust in the face of errors coming in from the other end, and could have security holes.
Related
I have a problem where DataContractSerializer returns null for all my variable. It's like it doesn't see them or something. I'm using it to deserialize a json file into an object. I had it working for another json file that was using another class with 3 string attributes. This one is composed of 40 attributes mostly string and a few bool. I have been working on it for hours and I just can't seem to find what I'm doing wrong. I even tried it with only 1 string attribute and it still returned null. Here is a simplified version with only 1 string attribute and 1 bool attribute. Any advice is more than appreciated.
Thank you
Json :
[{"Proposal_x0020_Type":"Lite Proposal","BI_x0020_Criteria_x0020_1":true}]
Function that tries to deserialize the string:
public Proposal[] Deserializer(string jsonFile)
{
MemoryStream ms = new MemoryStream(Encoding.Unicode.GetBytes(jsonFile));
DataContractJsonSerializer deserializer = new DataContractJsonSerializer(typeof(Proposal[]));
Proposal[] projectArr = (Proposal[])deserializer.ReadObject(ms);
Console.WriteLine(jsonFile);
Console.ReadLine();
return projectArr;
}
Class of the object that the deserializer should create:
namespace PMIS
{
[DataContract]
public class Proposal
{
[DataMember(Order = 0)]
public string Proposal_x0020_Type { get; set; }
[DataMember(Order = 1)]
public bool BI_x0020_Criteria_x0020_1 { get; set; }
}
}
I managed to find the problem by comparing the code of this Deserialization to a previous one that I had. The only difference was the name of the variables inside the Json file. The fact that they contain the Unicode for space (x00200) causes some problem inside the DataContractDeserializer. I believe that it is seeing it as space, so instead of seeing "Proposal_x0020_Type", it is seeing "Proposal_ _Type", but I'm not sure about it. Anyway, the solution was to remove x0020 from all the variables inside the Json file. After that it worked perfectly fine.
Let's say I have this JSON:
{
"top.level": {
"mykey": "3.301.0.97",
"mykey2": "not interested in this one"
},
"another.top.level": "not interested in this either"
}
I'm looking to use Json.NET to parse this string, and get the value of of "mykey".
I have two questions:
How can I do this without strong typing in C#, while gracefully handling scenarios where the JSON doesn't have top.level or mykey? I don't want to end up throwing NullReferenceExceptions or similar. Is there a more elegant answer than below?
string answer;
var jsonObj = JObject.Parse(jsonString);
if (jsonObj != null)
{
var topElement = jsonObj["top.level"];
if (topElement != null)
{
var keyElement = topElement["mykey"];
if (keyElement != null)
{
answer = keyElement.Value<string>();
}
}
}
Let's say I do want to use strong typing. How do I do this, considering the presence of the period in top.level? What type definition would I create, considering I'm ignoring mykey2 and another.top.level?
I'm also open to doing both of these things without using Json.NET, using any of .NET's built-in serialization mechanisms, so all ideas and suggestions welcome. Thanks!
You can use null propagation to make your code more readable. For instance, the following code could replace your example:
var answer = jsonObj?["top.level"]?.Value<string>("myKey");
For your second question, you can use JsonProperty attribute to define a custom name for the property. This could represent your example:
public class Level2 { public string MyKey { get; set; } }
public class Level1
{
[JsonProperty("top.level")]
public Level2 TopLevel { get; set; }
}
Now, you can deserialize with this code:
var strongType = JsonConvert.DeserializeObject<Level1>(str);
var answer = strongType?.TopLevel?.MyKey;
I'm in the process of deserializing into C# objects a custom inflexible XML schema to traverse and migrate the data within.
A brief example:
<Source>
...
<Provider>
<![CDATA[read 1]]>
<Identifier><![CDATA[read 2]]></Identifier>
<IdentificationScheme><![CDATA[read 3]]></IdentificationScheme>
</Provider>
...
</Source>
I'm looking the deserialize the Provider element with the first CDATA element value, read 1, and it's sibling element values too, read 2 and read 3.
Using http://xmltocsharp.azurewebsites.net/ it produces the following objects:
[XmlRoot(ElementName = "Provider")]
public class Provider
{
[XmlElement(ElementName = "Identifier")]
public string Identifier { get; set; }
[XmlElement(ElementName = "IdentificationScheme")]
public string IdentificationScheme { get; set; }
}
[XmlRoot(ElementName = "Source")]
public class Source
{
[XmlElement(ElementName = "Provider")]
public Provider Provider { get; set; }
}
But it fails to account for the the CDATA value, in fact I think deserializing it like this the value would not be reachable.
I think this maybe also be related to the XmlDeserializer to use, I was planning on RestSpharp's (as it's a library to the website already) or System.Xml.Link.XDocument, but I'm not sure whether either can handle this scenario?
In my searches I couldn't find an example either, but stack did suggest this <!{CDATA[]]> and <ELEMENT> in a xml element that is precisely the same schema option.
Thanks so much for any help in advance,
EDIT 1
As far as I can tell the [XmlText] is the solution required, as pointed out in Marc Gravell's answer below, but it does not work/is implemented on RestSharp's XmlDeserializer, but further testing would be required to ascertain that for sure.
The CDATA is essentially just escaping syntax and is handled by most readers. What you are looking for is:
[XmlText]
public string WhateverThisIs { get; set; }
on the object that has raw content. By adding that to Provider, WhateverThisIs gets the value of "read 1". The other 2 properties already deserialize correctly as "read 2" and "read 3" without you having to do anything.
For reference, everything here would behave almost the same without the CDATA (there are some whitespace issues):
<Provider>
read 1
<Identifier>read 2</Identifier>
<IdentificationScheme>read 3</IdentificationScheme>
</Provider>
I have been agonizing over this problem for a few days now and have no hope left. I'm still in the early stages of learning C#, so excuse me if my explanations or understanding are lacking.
My scenario is that I have a need to access an API and download the data as JSON then deserialize it into a class. At the moment, things work as they should, however every variable is defined as String which means I need to convert and manipulate data that should be int/double on the fly constantly as the API can give "N/A" for these data. The impression I get is relying on everything being string is bad practice.
So how should I implement it? I need to be able to store the data as the correct type while keeping in mind that it could be wrong.
Example of properties with wrong type
public string Title { get; set; }
public string Year { get; set; } // Wanted int. Often has an end year "2010-2014"
public string Metascore { get; set; } // Wanted double. Could be "N/A"
The only way I can imagine solving this is by having two classes: the first one being the original string-only class, then having the second being an almost identical class with the desired properties that uses the data from the original then converts it.
My problem with that is that the class already has a few dozen properties, so duplicating it seems nearly as wasteful as the original problem. Regardless, I would like to know an alternative for future use anyway.
EDIT:
Found a similar question here, though unfortunately it didn't help.
you can deserialize the json to JObject and than load it your self
public class RootObject
{
public RootObject(JObject obj)
{
Title = obj["Title"].ToString();
var year = obj["year"].ToString();
Year = year == "N/A" ? 0 : int.Parse(year);
var metascore = obj["Metascore"].ToString();
Metascore = metascore == "N/A" ? 0 : int.Parse(metascore);
}
public string Title { get; set; }
public int Year { get; set; }
public double Metascore { get; set; }
}
static void Main(string[] args)
{
string json = "{\"Title\":\"test\",\"year\":\"2012\",\"Metascore\":\"N/A\"}";
RootObject root = new RootObject(JObject.Parse(json));
}
I have Xml like so
<pda:Party>
...Snip....
<pda:CitizenName>
<pda:CitizenNameTitle>MR</pda:CitizenNameTitle>
<pda:CitizenNameForename>John</pda:CitizenNameForename>
<pda:CitizenNameSurname>Wayne</pda:CitizenNameSurname>
</pda:CitizenName>
.....Snip...
</pda:Party>
Where Citizen Name is a complex type within the Party Node. ( This is xml received from a 3rd party integration that I'm creating an adapter for )
I'm not interested that there is a sub type as in my class I'm attempting to deserialize into I would rather have.
public class Party
{
public string FirstName { get; set; }
public string LastName {get;set;}
}
So rather than have my class definition as a concrete definition of what the XML represents, can I decorate the properties with something like XPath, eg.
[XmlElement("\CitizenName\CitizenNameForeName")]
public string FirstName {get;set;}
To cherry pick information from the xml into a class the contains the data i'm interested in?
The xml received from the 3rd party is very verbose and I'm only interested in specific aspects. One option is to just create an XMLDocument and map to my class manually using XPath and a conversion method, but I thought I would ask in case there was an intermediary solution?
One option would be to use an XSLT transform to parse the incoming XML into s format that matches your class.
In the end, I set up my own attribute to do what i wanted it to do. So a custom attribute that takes an XPath path...
[System.AttributeUsage(System.AttributeTargets.Property)]
public class PathToXmlNode : System.Attribute
{
public string Path { get; set; }
public PathToXmlNode(string path)
{
this.Path = path;
}
}
followed by a decorated property.. ( namespaces omitted for simplicity )
[PathToXmlNode("Party[1]/CitizenName/CitizenNameForename")]
public string FirstName { get; set; }
Then when i want to populate the class I called the following method.
var type = typeof(T);
foreach (var property in type.GetProperties())
{
var attributes = property.GetCustomAttributes(typeof(PathToXmlNode), true);
if (attributes != null && attributes.Length > 0)
{
//this property has this attribute assigned.
//get the value to assign
var xmlAttribute = (PathToXmlNode)attributes[0];
var node = doc.SelectSingleNode(xmlAttribute.Path, nmgr);
if (node != null && !string.IsNullOrWhiteSpace(node.InnerText))
{
dynamic castedValue;
if (property.PropertyType == typeof(bool))
{
castedValue = Convert.ToBoolean(node.InnerText);
}
...Snip all the casts....
else
{
castedValue = node.InnerText;
}
//we now have the node and it's value, now set it to the property.
property.SetValue(obj, castedValue, System.Reflection.BindingFlags.SetProperty, null, null, System.Globalization.CultureInfo.CurrentCulture);
}
}
}
This has been a good starting point, however If anyone else see's this as a viable intermediary solution, You need to be aware that it will need adapting for non simple data types. Which is what I'm setting off to do now!