XML serialization: class within a class - c#

I am serializing with xml, and I had it working with just a simple class, but when I made a secondary class, of which the simple class was just a component, the serialization stopped working. It fails with an "Error reflecting type" error at the serialization stage. The code is as follows:
public class CustomField
{
[XmlAttribute("FieldID")]
public string FieldID;
[XmlAttribute("FieldValue")]
public string FieldValue;
public CustomField() { }
public CustomField(string fieldID, string fieldValue)
{
this.FieldID = fieldID;
this.FieldValue = fieldValue;
}
}
[XmlType("Entry")]
public class CustomEntry
{
[XmlAttribute("Author")]
public string Author;
[XmlAttribute("Title")]
public string Title;
[XmlAttribute("Trial")]
public string Trial;
[XmlAttribute("Responses")]
public List<CustomField> Responses;
public CustomEntry() { }
}
public static class EntrySerializer
{
public static void SerializeObject(this CustomEntry entry, string file)
{
var serializer = new XmlSerializer(typeof(CustomEntry));
using (var stream = File.OpenWrite(file))
{
serializer.Serialize(stream, entry);
}
}
}
Is it a labeling issue with the Xml markers, or is it something else?

Try defining your serializer like this:
var serializer = new XmlSerializer(typeof(CustomEntry), new Type[] { typeof(CustomField) });
You need to inform the serializer of the additional types it is expecting to serialize.

I usually tag with XMLRoot (two places). I need to see sample of XML to give better answer.
[XmlRoot("CustomField")]
public class CustomField
{
[XmlAttribute("FieldID")]
public string FieldID;
[XmlAttribute("FieldValue")]
public string FieldValue;
public CustomField() { }
public CustomField(string fieldID, string fieldValue)
{
this.FieldID = fieldID;
this.FieldValue = fieldValue;
}
}
[XmlRoot("Entry")]
public class CustomEntry
{
[XmlAttribute("Author")]
public string Author;
[XmlAttribute("Title")]
public string Title;
[XmlAttribute("Trial")]
public string Trial;
[XmlAttribute("Responses")]
public List<CustomField> Responses;
public CustomEntry() { }
}
​

Related

Newtonsoft.Json.JsonConvert(object) returns null object

I have a 'complex' object that I want to serialize with JSon.Convert. As 'complex' objects go it is rather simple: Here are the objects:
The main object:
public class CustomerContactRequest
{
private RequestHeaderArea header;
private RequestPayloadArea payload;
public CustomerContactRequest(string headerMessage, string npsGroup, string npsSection)
{
this.header = new RequestHeaderArea(headerMessage);
this.payload = new RequestPayloadArea(npsGroup, npsSection);
}
}
The 'header' Object:
public class RequestHeaderArea
{
private string headerMessage;
public string HeaderMessage { get { return headerMessage; } }
public RequestHeaderArea(string headerMessage)
{
this.headerMessage = headerMessage;
}
}
The Payload Area:
public class RequestPayloadArea
{
private string npsGroup;
private string npsSection;
public string NPSGroup { get { return npsGroup; } }
public string NPSSection { get { return npsSection; } }
public RequestPayloadArea(string npsGroup, string npsSection)
{
this.npsGroup = npsGroup;
this.npsSection = npsSection;
}
}
And Finally, the main process:
static void Main(string[] args)
{
CustomerContactRequest ccRequest = new CustomerContactRequest(
headerMessage: "test",
npsGroup: "1234567",
npsSection: "0000");
retrieveContactInfo(ccRequest);
}
static void retrieveContactInfo(CustomerContactRequest ccRequest)
{
string jsonRequest = JsonConvert.SerializeObject(ccRequest);
// code to call service
}
jsonRequest returns {} even though ccRequest contains the expected values. What am I missing?
I am expecting something like this (sans formatting):
{
"headerArea": {
"messageId": "test"
},
"payloadArea": {
"group": {
"Number": "1234567",
"Suffix": "0000"
}
}
}
Implementing Chris's answer my classes now look like below (main program did not change except that I added Formatted.Indented to the SerializeObject call to make it pretty):
CustomerContactRequest:
public class CustomerContactRequest
{
public RequestHeaderArea headerArea;
public RequestPayloadArea payloadArea;
public CustomerContactRequest(string headerMessage, string npsGroup, string npsSection)
{
this.headerArea = new RequestHeaderArea(headerMessage);
this.payloadArea = new RequestPayloadArea(npsGroup, npsSection);
}
}
RequestHeaderArea:
public class RequestHeaderArea
{
private string messageId;
public string MessageId { get { return messageId; } }
public RequestHeaderArea(string headerMessage)
{
this.messageId = headerMessage;
}
}
RequestPayloadArea:
public class RequestPayloadArea
{
public Group group;
public RequestPayloadArea(string npsGroup, string npsSection)
{
this.group = new Group(npsGroup, npsSection);
}
}
And a new class: Group:
public class Group
{
public string Number;
public string Suffix;
public Group(string npsGroup, string npsSection)
{
Number = npsGroup;
Suffix = npsSection;
}
}
Now my Json looks exactly as expected (see green text above)
SerializeObject ignores private members by default. You can either make them public, or by adding the SerializableAttribute to your CustomerContractRequest class.

C# json deserialize to (this) inside object

I am trying to get an object to deserialize into itself. I have tride the following:-
public class JobID
{
public string jobname;
public string first;
public string second;
public string third;
public string clientName;
public string workflow;
}
public void load(string fname)
{
string s = File.ReadAllText(fname);
this = JsonConvert.DeserializeObject<JobID>(s);
}
But the word this is 'read only' according to the error I get.
I have used 'this.jobname = "X";' before so clearly 'this' is not read only.
I am using Newtonsof.Json.
Why not use static method to load the object. Such as :
public class JobID
{
public string jobname;
public string first;
public string second;
public string third;
public string clientName;
public string workflow;
public static JobId Load(string fname){
string s = File.ReadAllText(fname);
return JsonConvert.DeserializeObject<JobID>(s);
}
}
Although you can assign a value to a property of 'this', you can't change the object to which 'this' refers to.
The 'this' keyword refers to the current object instance in the context (https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/this).
Assuming the Load function is inside another object (or static), you could do something like:
public class JobID
{
public string jobname;
public string first;
public string second;
public string third;
public string clientName;
public string workflow;
}
public class JobReader
{
// Property to store deserialized object
public JobID Job { get; set; }
public void load(string fname)
{
string s = File.ReadAllText(fname);
// Assign object to property.
this.JobID = JsonConvert.DeserializeObject<JobID>(s);
}
}
You can use the static method as suggested by Xbotter to create a new instance. But for some reason if you want to deserialize the file content into current object only, then easiest way would be:
public class JobID
{
public string jobname;
public string first;
public string second;
public string third;
public string clientName;
public string workflow;
public void load(string fname)
{
string s = File.ReadAllText(fname);
JobID tmp = JsonConvert.DeserializeObject<JobID>(s);
copy(tmp);
}
public void copy(JobID tmp)
{
this.jobname = tmp.jobname;
// do the same for other properties that you want to copy
}
}
If you want to do this without a static method, you can use the JsonSerializer.Populate method. Example code:
public class JobID
{
public string jobname;
public string first;
public string second;
public string third;
public string clientName;
public string workflow;
public void load(string fname)
{
string s = File.ReadAllText(fname);
new JsonSerializer().Populate(new JsonTextReader(new StringReader(s)), this);
}
}
Anssssss provided the answer I've been looking for years. Thank you!
This is how I used it:
bool bOk = false;
StreamReader myFileStream = null;
Newtonsoft.Json.JsonSerializer mySerializer = new Newtonsoft.Json.JsonSerializer();
try
{
myFileStream = File.OpenText(sFilePath);
mySerializer.Populate(myFileStream, this);
bOk = true;
}
catch (Exception e)
{
string sException = e.ToString();
CHelper.ThrowException(new Exception(sException + "\n\n" + sFilePath));
}
finally
{
if (myFileStream != null)
{
myFileStream.Close();
}
}
return bOk;

XMLSerializer - Parameter less Construction

I have the following class:
[Serializable]
public class ClassOne : ClassTwo
{
private string _parameterOne;
private string _parameterTwo;
private string _parameterThree;
public Category (string parameterOne, string parameterTwo, string parameterThree)
{
_parameterOne = parameterOne;
_parameterTwo = parameterTwo;
_parameterThree = parameterThree;
}
}
I then want to make use of the XMLSerializer:
private void Serialize()
{
XmlSerializer xmlSerializer = new xmlSerializer(typeof(ClassOne));
xmlSerializer.Serialize(stream, object);
}
However, I can't serialize that class because it does not have a parameterless construction... How can I practically resolve this situation?
Add empty constructor. And you should use public properties for fields that you want to serialize
[Serializable]
public class ClassOne : ClassTwo
{
private string _parameterOne;
private string _parameterTwo;
private string _parameterThree;
public Category() { }
public Category (string parameterOne, string parameterTwo, string parameterThree)
{
_parameterOne = parameterOne;
_parameterTwo = parameterTwo;
_parameterThree = parameterThree;
}
}

Deserialize XML String into Class

I am trying to deserialize an XML String into my class which is derived from another class but I am having a problem in doing so, I am getting the following error:
{"The specified type is abstract: name='DeviceRequest', namespace='', at <DeviceRequest xmlns=''>."}
I assume i am missing some decorator attribute that will inform the serializer how to deserialize this xml into the class?
Here is my code:
//Usage
DeviceRequest dreq = DeviceRequest.ParseRequest(e.XML);
//Base Class
public abstract class IFSFRequestBase
{
private string m_ApplicationSender = "";
private int m_WorkStationID = 0;
private string m_RequestID = "";
[XmlAttribute()]
public abstract string RequestType { get; set; }
public abstract byte[] Message();
[XmlAttribute()]
public string ApplicationSender
{
get
{
return m_ApplicationSender;
}
set
{
m_ApplicationSender = value;
}
}
[XmlAttribute()]
public int WorkStationID
{
get
{
return m_WorkStationID;
}
set
{
m_WorkStationID = value;
}
}
[XmlAttribute()]
public string RequestID
{
get
{
return m_RequestID;
}
set
{
m_RequestID = value;
}
}
}
//Derived Class
public abstract class DeviceRequest : IFSFRequestBase
{
private string m_TerminalID = "";
private string m_SequenceID = "";
private Output m_OutputField = null;
[XmlAttribute(), DefaultValue("")]
public string TerminalID
{
get
{
return m_TerminalID;
}
set
{
m_TerminalID = value;
}
}
[XmlAttribute(), DefaultValue("")]
public string SequenceID
{
get
{
return m_SequenceID;
}
set
{
m_SequenceID = value;
}
}
[XmlElement("Output")]
public Output OutputField
{
get
{
return m_OutputField;
}
set
{
m_OutputField = value;
}
}
public static DeviceRequest ParseRequest(string sXML)
{
XmlSerializer serializer = new XmlSerializer(typeof(DeviceRequest));
StringReader sr = new StringReader(sXML);
NamespaceIgnorantXmlTextReader XMLWithoutNamespace = new NamespaceIgnorantXmlTextReader(sr);
return (DeviceRequest)serializer.Deserialize(XMLWithoutNamespace);
}
}
// helper class to ignore namespaces when de-serializing
public class NamespaceIgnorantXmlTextReader : XmlTextReader
{
public NamespaceIgnorantXmlTextReader(System.IO.TextReader reader) : base(reader) { }
public override string NamespaceURI
{
get { return ""; }
}
}
UPDATE:
OK based on the answers here. I have updated the code, I now have a class Display that derives from DeviceRequest. I now get the following error:
{"There is an error in XML document (2, 2)."}, {"<DeviceRequest xmlns=''> was not expected."}
public class Display : DeviceRequest
{
public static Display ParseRequest(string sXML)
{
XmlSerializer serializer = new XmlSerializer(typeof(Display));
StringReader sr = new StringReader(sXML);
NamespaceIgnorantXmlTextReader XMLWithoutNamespace = new NamespaceIgnorantXmlTextReader(sr);
return (Display)serializer.Deserialize(XMLWithoutNamespace);
}
[XmlAttribute()]
public override string RequestType
{
get { return "Output"; } set { }
}
public override byte[] Message()
{
throw new NotImplementedException();
}
}
DeviceRequest dreq = Display.ParseRequest(e.XML);
As DeviceRequest is an abstract type, it cannot be instantiated directly. It is impossible to directly deserialize into instances of Device-Request. That said, if there are some non-abstract classes derived from it, please show some of them.
OK, I have resolved this issue now. Thanks for the input guys.
I needed to add:
[System.Xml.Serialization.XmlRootAttribute(ElementName = "DeviceRequest")]
to the Display Class
[System.Xml.Serialization.XmlRootAttribute(ElementName = "DeviceRequest")]
public class Display : DeviceRequest
{
public static Display ParseRequest(string sXML)
{
XmlSerializer serializer = new XmlSerializer(typeof(Display));
StringReader sr = new StringReader(sXML);
NamespaceIgnorantXmlTextReader XMLWithoutNamespace = new NamespaceIgnorantXmlTextReader(sr);
return (Display)serializer.Deserialize(XMLWithoutNamespace);
}
[XmlAttribute()]
public override string RequestType
{
get { return "O"; } set { }
}
public override byte[] Message()
{
throw new NotImplementedException();
}
}

Add an XML wrapper element to serialized data

I have a couple of lists that I have serialized into an XML file.
I am having difficulty figuring out how to put an XML wrapper element around my element list.
Currently my XML looks like this:
<finding>
<issue1></issue1>
<issue2><issue2 />
<issue3><issue3 />
</finding>
<finding>
<issue1></issue1>
<issue2><issue2 />
<issue3><issue3 />
</finding>
I want to wrap these finding elements so the XML looks like this instead:
<Findings>
<finding>
<issue1></issue1>
<issue2><issue2 />
<issue3><issue3 />
</finding>
<finding>
<issue1></issue1>
<issue2><issue2 />
<issue3><issue3 />
</finding>
<findings/>
Here is how I am serializing:
[XmlElement("finding")]
public List<Findings> Findings { get {return findings;}}
[XmlElement("Issue1")]
public string getIssue1 { get { return issue1; } }
[XmlElement("issue2")]
public string getissue2 { get { return issue2; } }
[XmlElement("issue3")]
public string getissue3 { get { return issue3; } }
Here is the calling code:
XmlSerializer ser = new XmlSerializer(typeof(RuleSet));
using (StreamWriter writer = new StreamWriter(fileName))
{
ser.Serialize(writer, findings);
}
Findings Class:
[Serializable()]
[XmlRoot("Findings")]
public class Findings
{
public string issue1;
public string issue2;
public string issue3;
public string issue4;
public Findings()
{
}
public Findings(string string flag1, string flag2, string flag3, string flag4)
{
this.issue1 = flag1;
this.issue2 = flag2;
this.issue3 = flag3;
this.issue4 = flag4;
}
}
}
and the ruleset class
[Serializable()]
public class RuleSet
{
private List<Findings> findings = new List<Findings>();
Findings findresults = new Findings();
[XmlElement("finding")]
public List<Findings> Findings { get {return findings;}}
[XmlElement("Issue1")]
public string getIssue1 { get { return findresults.issue1; } }
[XmlElement("issue2")]
public string getissue2 { get { return findresults.issue2; } }
[XmlElement("issue3")]
public string getissue3 { get { return findresults.issue3; } }
[XmlElement("issue4")]
public string getissue4 { get { return findresults.issue4; } }
public void setFindings(List<Findings> findings)
{
this.findings = findings;
}
public void addFindings(Findings findings)
{
this.findings.Add(findings);
}
}
}
I have been fighting with this for hours. What am I missing?
This should work:
public class Findings
{
[System.Xml.Serialization.XmlElementAttribute("finding")]
public List<Finding> finding = new List<Finding>();
}
public class Finding
{
public string issue1;
public string issue2;
public string issue3;
}
Note the XmlElementAttribute which names the items of the list. In this case a lower case finding. Also, my example uses fields for issue1 etc., but your properties will be just fine.

Categories