C# XML deserialize self closing raw element - c#

I need to process some XML input which has HTML code in some tags. For these tags I want the raw content to process it later. I followed this answer and used XmlElement which works fine in most cases. The only problem I'm facing are self closing tags.
[Serializable]
public class Root
{
public XmlElement Description { get; set; }
public string Name { get; set; }
}
var serializer = new XmlSerializer(typeof(Root));
var obj1 = serializer.Deserialize(new StringReader(#"<Root><Description><p>test</p></Description><Name>Test</Name></Root>"));
// Description: "Element, Name=\"p\""
// Name: "Test"
var obj2 = serializer.Deserialize(new StringReader(#"<Root><Description></Description><Name>Test</Name></Root>"));
// Description: null
// Name: "Test"
var obj3 = serializer.Deserialize(new StringReader(#"<Root><Description/><Name>Test</Name></Root>"));
// Description: "Element, Name=\"Name\""
// Name: null
obj1 and obj2 are ok (obj2.Description == "" would be better) but in obj3 the Description member is greedy and contains the Name part.
Is there a workaround for this problem?

A possible workaround is to declare a custom class for the Description property, matching any content inside the element using the [XmlAnyElement] attribute:
public class Root
{
public Description Description { get; set; }
public string Name { get; set; }
}
public class Description
{
[XmlAnyElement]
public List<XmlElement> Content { get; set; }
}
The only drawback is this won't work for mixed content. In other words, this will deserialize well:
<Description><p>test</p></Description>
but this won't, deserializing the <span> only:
<Description>some <span>other</span> text</Description>
Should you need mixed content, implement IXmlSerializable on the Description class.
However, it does work for <Description/>. That being said, I do agree with #MarcGravell that it's a bug in XmlSerializer and shall be reported.

As suggested I reported this issue.
I use this code now to workaround the issue:
using System;
using System.IO;
using System.Xml;
using System.Xml.Serialization;
using System.Xml.Schema;
public class Program
{
public static void Main()
{
var serializer = new XmlSerializer(typeof(Root));
var obj1 = serializer.Deserialize(new StringReader(#"<Root><Description><p>test</p></Description><Name>Test</Name></Root>"));
Console.WriteLine(obj1); // Description: <p>test</p> / Name: Test
var obj2 = serializer.Deserialize(new StringReader(#"<Root><Description></Description><Name>Test</Name></Root>"));
Console.WriteLine(obj2); // Description: / Name: Test
var obj3 = serializer.Deserialize(new StringReader(#"<Root><Description/><Name>Test</Name></Root>"));
Console.WriteLine(obj3); // Description: / Name: Test
}
public class Root
{
public HtmlElement Description { get; set; }
public string Name { get; set; }
public override string ToString() => $"Description: {Description.Content} / Name: {Name}";
}
public class HtmlElement : IXmlSerializable
{
public string Content { get; set; } = "";
public XmlSchema GetSchema() => null;
public void ReadXml(XmlReader reader)
{
reader.MoveToContent();
if (!reader.IsEmptyElement && reader is XmlTextReader xtr)
{
Content = xtr.ReadInnerXml();
}
}
public void WriteXml(XmlWriter writer) => throw new NotImplementedException();
}
}

Related

Getting empty(blank) value while printing JSON field (value) using JsonConverter.Deserialize in C#

I am trying to parse a JSON using NewtonSoft (my JSON is an array type). Below is what it looks like...
{
"steps": [
{
"stepsType": "runWizard",
"wizardType": "demoServer",
"config": {
"mode": "add",
"resourceName": "demo server 1",
"activeDbPrimaryServer": {
"serverName": "abc",
"serverAddress": "abc.demo.local"
},
"activeDbCatalog": "demoActiveDB",
"activeDBUserId": "sa",
"activeDBPassword": "xyz",
}
}
]
}
I have created a class using JsonToC# convertor....
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ServerSetupWizardConsoleApp
{
// Root myDeserializedClass = JsonConvert.DeserializeObject<Root>(myJsonResponse);
public class ActiveDbPrimaryServer
{
public string serverName { get; set; }
public string serverAddress { get; set; }
}
public class Config
{
public string mode { get; set; }
public string resourceName { get; set; }
public ActiveDbPrimaryServer activeDbPrimaryServer { get; set; }
public string activeDbCatalog { get; set; }
public string activeDBUserId { get; set; }
public string activeDBPassword { get; set; }
}
public class Step
{
public string stepsType { get; set; }
public string wizardType { get; set; }
public Config config { get; set; }
}
public class Root
{
public List<Step> steps { get; set; }
}
}
And now when I am trying to deserialise it in another class, I am getting a empty response in console...
I have a TEXT from where I am reading the JSON and then storing it in a string type variable and the using the string to deserialise it.
public Object ReadJson()
{
string jsonText = File.ReadAllText("C:\\Desktop\\demo.json");
var rootObject = (Root) JsonConvert.DeserializeObject(jsonText, typeof(Root));
var activeDbAttr = (ActiveDbPrimaryServer)JsonConvert.DeserializeObject(jsonText, typeof(ActiveDbPrimaryServer));
Console.WriteLine("Value : " + activeDbAttr.serverAddress);
}
This activeDbAttr.serverAddress is giving me nothing in CONSOLE
It print --> value : (nothing after ":" like blank)
Can someone tell me what is wrong here. I followed some old answers, not getting to a point where I can fix it.
Live demo : https://dotnetfiddle.net/PuO8F8
It's a simple navigation issue.
You deserialize to a Root object, and instead of navigating with the property you deserialize again into an other thing, hopping to land in the right place.
var rootObject = JsonConvert.DeserializeObject<Root>(jsonText);
var activesDBs = json.steps.Select( x=> x.config.activeDbPrimaryServer).ToList();
Result:
Dumping object(System.Linq.SelectListIterator`2[Step,ActiveDbPrimaryServer])
[
{
serverAddress : abc.demo.local
serverName : abc
}
]
Why did it failed ?
var jsonDB = JsonConvert.DeserializeObject<ActiveDbPrimaryServer>(GetJson());
Tell the Parser that the Json is of type ActiveDbPrimaryServer.
The parser open the Json and find the first object:
{
"steps": [..]
}
Look for the property of the expected type ActiveDbPrimaryServer
public class ActiveDbPrimaryServer
{
public string serverName { get; set; }
public string serverAddress { get; set; }
}
And find nothing. It end there and give you an object of the right type with no property initialized
Partial deserialization:
If you want to deserialize only the part you need, refer to this documentation from Newtonsoft:
Deserializing Partial JSON Fragments
JObject rootObj = JObject.Parse(GetJson());
// get JSON result objects into a list
IList<JToken> results = rootObj["steps"].Children() // Step is a list so we use `.Children()`
["config"]["activeDbPrimaryServer"].ToList();
IList<ActiveDbPrimaryServer> dbResults = new List<ActiveDbPrimaryServer>();
foreach (JToken result in results)
{
// JToken.ToObject is a helper method that uses JsonSerializer internally
ActiveDbPrimaryServer dbResult = result.ToObject<ActiveDbPrimaryServer>();
dbResults.Add(dbResult);
}
you are deserializing
var rootObject = (Root) JsonConvert.DeserializeObject(jsonText, typeof(Root));
var activeDbAttr = (ActiveDbPrimaryServer)JsonConvert.DeserializeObject(jsonText, typeof(ActiveDbPrimaryServer));
[the second one yields null ... and null cast to ActiveDbPrimaryServer yields null.]
the same string twice into 2 different types of objects that do not share common base class (of course other than object).
your text represents either a Root or a ActiveDbPrimaryServer not both. the ActiveDbPrimaryServer is a member of the config class and that in turn of Step. ....
step through your graph root.Step.Config .. if all the instances are set you should reach your instance of ActibeDdPrimaryServer

C# parse XML file to object

Using C#, is there a way to easily parse an XML file so that it can be used as an object?
Example XML:
<Config>
<Ui>
<Colour>black</Colour>
<Size>small</Size>
</Ui>
<Output>
<Mode>smb</Mode>
<Version>2</Version>
</Output>
</Config>
And then refer to the parameters in my application by
Config.Output.Mode
I've tried this method - How to Deserialize XML document
But when I try
var cfg = new Config();
cfg.Load(#"config.xml");
Console.WriteLine(cfg.Output.Mode);
visual studio indicates .Output.Mode is not valid.
Where Config.Load is
xmlData = File.ReadAllText(configPath);
var serializer = new XmlSerializer(typeof(Config));
using (var reader = new StringReader(xmlData))
{
Config result = (Config)serializer.Deserialize(reader);
}
You have to create the classes that match the definition in the xml file in order to deserialize the file into an instance of the class. Note that I've named the properties with the same name as we have in the xml file. If you want to use different property names, then you'd need to add an attribute above the property that specifies the xml element that should map to it (like for the Ui, you would add the attribute: [XmlElement("Ui")]).
Note that I've also overridden the ToString methods for the classes so we can output them to the console in a nice fashion:
public class Config
{
public UI Ui { get; set; }
public Output Output { get; set; }
public override string ToString()
{
return $"Config has properties:\n - Ui: {Ui}\n - Output: {Output}";
}
}
public class UI
{
public string Colour { get; set; }
public string Size { get; set; }
public override string ToString()
{
return $"(Colour: {Colour}, Size: {Size})";
}
}
public class Output
{
public string Mode { get; set; }
public int Version { get; set; }
public override string ToString()
{
return $"(Mode: {Mode}, Version: {Version})";
}
}
Now all we have to do is create a StreamReader, point it to our file path, and then use the XmlSerializer class to Deserialize the file (casting the output to the appropriate type) into an object:
static void Main(string[] args)
{
var filePath = #"f:\private\temp\temp2.txt";
// Declare this outside the 'using' block so we can access it later
Config config;
using (var reader = new StreamReader(filePath))
{
config = (Config) new XmlSerializer(typeof(Config)).Deserialize(reader);
}
Console.WriteLine(config);
GetKeyFromUser("\n\nDone! Press any key to exit...");
}
Output
Here are the classes:
public class Config
{
public UI UI { get; set; }
public Output Output { get; set; }
}
public struct UI
{
public string Colour { get; set; }
public string Size { get; set; }
}
public struct Output
{
public string Mode { get; set; }
public int Version { get; set; }
}
The Deserialize function:
public static T Deserialize<T>(string xmlString)
{
if (xmlString == null) return default;
var serializer = new XmlSerializer(typeof(T));
using (var reader = new StringReader(xmlString))
{
return (T) serializer.Deserialize(reader);
}
}
And here's a working version:
Config cfg = Deserialize<Config>(xmlString);
Console.WriteLine(cfg.Output.Mode);

Null value on xml deserialization using [XmlAttribute]

I have the following XML;
<?xml version="1.0" encoding="UTF-8" ?>
<feedback>
<report_metadata>
<org_name>example.com</org_name>
</report_metadata>
</feedback>
and the following Feedback.cs class;
[XmlRoot("feedback", Namespace = "", IsNullable = false)]
public class Feedback
{
[XmlElement("report_metadata")]
public MetaData MetaData { get; set; }
}
[XmlType("report_metadata")]
public class MetaData
{
[XmlAttribute("org_name")]
public string Organisation { get; set; }
}
When I attempt to deserialize, the value for Organisation is null.
var xml = System.IO.File.ReadAllText("example.xml");
var serializer = new XmlSerializer(typeof(Feedback));
using (var reader = new StringReader(input))
{
var feedback = (Feedback)serializer.Deserialize(reader);
}
Yet, when I change Feedback.cs to the following, it works (obviously the property name has changed).
[XmlType("report_metadata")]
public class MetaData
{
//[XmlAttribute("org_name")]
public string org_name { get; set; }
}
I want the property to be Organisation, not org_name.
In the example XML file org_name is an XML element, not an XML attribute. Changing
[XmlAttribute("org_name")] to [XmlElement("org_name")] at the Organisation property will deserialize it as an element:
[XmlElement("org_name")]
public string Organisation { get; set; }
probably just typo
[XmlAttribute("org_name")]
public string Organisation { get; set; }
was supposed to be
[XmlElement("org_name")]
public string Organisation { get; set; }
Try to modify your Xml classes like
[XmlRoot(ElementName = "report_metadata")]
public class MetaData
{
[XmlElement(ElementName = "org_name")]
public string Organisation { get; set; }
}
[XmlRoot(ElementName = "feedback")]
public class Feedback
{
[XmlElement(ElementName = "report_metadata")]
public MetaData MetaData { get; set; }
}
Then you will get your desired output like
class Program
{
static void Main(string[] args)
{
Feedback feedback = new Feedback();
var xml = System.IO.File.ReadAllText(#"C:\Users\Nullplex6\source\repos\ConsoleApp4\ConsoleApp4\Files\XMLFile1.xml");
var serializer = new XmlSerializer(typeof(Feedback));
using (var reader = new StringReader(xml))
{
feedback = (Feedback)serializer.Deserialize(reader);
}
Console.WriteLine($"Organization: {feedback.MetaData.Organisation}");
Console.ReadLine();
}
}
Output:

What is the correct way to deserialize this XML string?

I store the items displayed on my homepage as an XML string (in the settings).
<?xml version=""1.0""?>
<HomePageItemList>
<PlantHomePageItem>
<Name>Plant1</Name>
</PlantHomePageItem>
<PlantHomePageItem>
<Name>Plant2</Name>
</PlantHomePageItem>
<AdminHomePageItem>
<Name>Admin1</Name>
</AdminHomePageItem>
</HomePageItemList>
Some items represent a plant PlantHomePageItem, others an admin tool AdminHomePageItem. Both inherit the same base class HomePageItem
[XmlRoot("HomePageItemList")]
public class TestSerialization
{
[XmlArray("HomePageItemList")]
[XmlArrayItem("PlantHomePageItem", Type = typeof(PlantHomePageItem))]
[XmlArrayItem("AdminHomePageItem", Type = typeof(AdminHomePageItem))]
public List<HomePageItem> HomePageItemList { get; set; }
}
[XmlInclude(typeof(PlantHomePageItem))]
[XmlInclude(typeof(AdminHomePageItem))]
public class HomePageItem
{
[XmlElement("Name")]
public string Name { get; set; }
}
public class PlantHomePageItem : HomePageItem { }
public class AdminHomePageItem : HomePageItem { }
When I try an deserialize it, I have no runtime error, just an empty object...
MemoryStream ms = new MemoryStream(Encoding.UTF8.GetBytes(XML));
XmlSerializer xs = new XmlSerializer(typeof(TestSerialization));
TestSerialization obj = (TestSerialization)xs.Deserialize(ms);
Console.WriteLine(obj.HomePageItemList.Count);
foreach (var item in obj.HomePageItemList)
{
Console.WriteLine("{0}: {1}", item.GetType().Name, item.Name);
}
returns
0
A .NET Fiddle with the entire thing is available: https://dotnetfiddle.net/f51b0K
Change your TestSerialization class to this...
[XmlRoot("HomePageItemList")]
public class TestSerialization
{
[XmlElement("PlantHomePageItem", Type = typeof(PlantHomePageItem))]
[XmlElement("AdminHomePageItem", Type = typeof(AdminHomePageItem))]
public List<HomePageItem> HomePageItemList { get; set; }
}
Your modified example...

Deserialization of xml file by using XmlArray?

I am trying to deserialize this xml structure.
<?xml version="1.0"?>
<DietPlan>
<Health>
<Fruit>Test</Fruit>
<Fruit>Test</Fruit>
<Veggie>Test</Veggie>
<Veggie>Test</Veggie>
</Health>
</DietPlan>
And I tried:
[Serializable]
[XmlRoot(ElementName = "DietPlan")]
public class TestSerialization
{
[XmlArray("Health")]
[XmlArrayItem("Fruit")]
public string[] Fruits { get; set; }
[XmlArray("Health")]
[XmlArrayItem("Veggie")]
public string[] Veggie { get; set; }
}
But this throws an exception "The XML element is already present in the current scope. Use XML attributes to specify another XML name or namespace for the element."
Thanks in adv.
You need a common type to be able to deserialize your XML, and with that you can define with the [XmlElement] namespace what type to instantiate depending on the name of the element, as shown below.
public class StackOverflow_15907357
{
const string XML = #"<?xml version=""1.0""?>
<DietPlan>
<Health>
<Fruit>Test</Fruit>
<Fruit>Test</Fruit>
<Veggie>Test</Veggie>
<Veggie>Test</Veggie>
</Health>
</DietPlan>";
[XmlRoot(ElementName = "DietPlan")]
public class TestSerialization
{
[XmlArray("Health")]
[XmlArrayItem("Fruit", Type = typeof(Fruit))]
[XmlArrayItem("Veggie", Type = typeof(Veggie))]
public Food[] Foods { get; set; }
}
[XmlInclude(typeof(Fruit))]
[XmlInclude(typeof(Veggie))]
public class Food
{
[XmlText]
public string Text { get; set; }
}
public class Fruit : Food { }
public class Veggie : Food { }
public static void Test()
{
MemoryStream ms = new MemoryStream(Encoding.UTF8.GetBytes(XML));
XmlSerializer xs = new XmlSerializer(typeof(TestSerialization));
TestSerialization obj = (TestSerialization)xs.Deserialize(ms);
foreach (var food in obj.Foods)
{
Console.WriteLine("{0}: {1}", food.GetType().Name, food.Text);
}
}
}

Categories

Resources