Deserialize XML with elements that differ by attribute - c#

I have this snippet of XML (actually it's XBRL, but that is based on XML)
<xbrl>
<context id="Context_Duration" />
<context id="Context_Instant_Begin" />
<context id="Context_Instant_End" />
<ConstructionDepotSpecification>
<ConstructionDepotLabel contextRef="Context_Duration">depot</ConstructionDepotLabel>
<ConstructionDepotBalance contextRef="Context_Instant_Begin">45000</ConstructionDepotBalance>
<ConstructionDepotBalance contextRef="Context_Instant_End">42000</ConstructionDepotBalance>
</ConstructionDepotSpecification>
</xbrl>
(additional content and xml namespaces declarations removed for clarity)
I want to deserialize this to a class, but I'm not sure how to handle the ConstructionDepotBalance elements. If I define a property ConstructionDepotBalance it will just take the value of the first element, so I think I should create two properties instead, one for begin value and one for end value.
So the class should look like this
[XmlRoot(ElementName = "xbrl")]
public partial class Taxonomy
{
[XmlElement]
public List<ConstructionDepotSpecification> ConstructionDepotSpecification { get; set; }
}
public partial class ConstructionDepotSpecification
{
public string ConstructionDepotLabel { get; set; }
public long? ConstructionDepotBalanceBegin { get; set; }
public long? ConstructionDepotBalanceEnd { get; set; }
}
So the element with attribute Context_Instant_Begin should be deserialized to ConstructionDepotBalanceBegin and the other element with attribute Context_Instant_End should be deserialized to ConstructionDepotBalanceEnd.
Is this possible to achieve? Should I use an IXmlSerializable implementation for this?

My first approach:
You could parse the XML-String first and replace
[ConstructionDepotBalance contextRef="Context_Instant_Begin"]
with
[ConstructionDepotBalanceBegin]
(Same with ConstructionDepotBalanceEnd).
In the second step you deserialze the XML string.

I experimented a bit with the IXmlSerializable interface and came up with this implementation:
public void ReadXml(XmlReader reader)
{
reader.ReadStartElement();
while (!reader.EOF)
{
var ctx = reader.GetAttribute("contextRef");
if (ctx == "Context_Duration")
{
string propName = reader.Name;
var propInfo = GetType().GetProperty(propName);
Type propType = propInfo.PropertyType;
if (propType.GenericTypeArguments.Length > 0)
{
propType = propType.GenericTypeArguments[0];
}
var value = reader.ReadElementContentAs(propType, null);
propInfo.SetValue(this, value);
}
else if (ctx == "Context_Instant_Begin")
{
string propName = reader.Name + "Begin";
var propInfo = GetType().GetProperty(propName);
var value = reader.ReadElementContentAsLong();
propInfo.SetValue(this, value);
}
else if (ctx == "Context_Instant_End")
{
string propName = reader.Name + "End";
var propInfo = GetType().GetProperty(propName);
var value = reader.ReadElementContentAsLong();
propInfo.SetValue(this, value);
}
if (reader.NodeType == XmlNodeType.EndElement)
{
reader.ReadEndElement();
break;
}
}
}
Not sure if it's the best solution for this problem, but for now it does what I want.

Related

Is Dyanamic De-serialization of XML possible? [duplicate]

I have a XML document provided by client applications to my C# application. This is how a client sends the XML file:
<?xml version="1.0" encoding="utf-8"?>
<SomeAccount>
<parentId>2380983</parentId>
<!-- more elements -->
</SomeAccount>
And a C# class that supports the XML deserialization:
[XmlRoot]
public class SomeAccount
{
[XmlElement("parentId")]
public long ParentId { get; set; }
//rest of fields...
}
But there are some clients whose system send the XML in this way (note the upper case in LeParentId):
<?xml version="1.0" encoding="utf-8"?>
<SomeAccount>
<LeParentId>2380983</LeParentId>
<!-- similar for the other elements -->
</SomeAccount>
How can I make this field (and others) to support both XML names parentId and LeParentId?
This is the method I'm currently using for XML deserialization:
public sealed class XmlSerializationUtil
{
public static T Deserialize<T>(string xml)
{
if (xml == null)
return default(T);
XmlSerializer serializer = new XmlSerializer(typeof(T));
StringReader stringReader = new StringReader(xml);
return (T)serializer.Deserialize(stringReader);
}
}
I tried to add [XmlElement] twice in the field, one per element name, but that didn't work.
Take 2 - let's implement this ourselves using the unknown element handling event (see the comments below for some limitations though):
public class XmlSynonymDeserializer : XmlSerializer
{
public class SynonymsAttribute : Attribute
{
public readonly ISet<string> Names;
public SynonymsAttribute(params string[] names)
{
this.Names = new HashSet<string>(names);
}
public static MemberInfo GetMember(object obj, string name)
{
Type type = obj.GetType();
var result = type.GetProperty(name);
if (result != null)
return result;
foreach (MemberInfo member in type.GetProperties().Cast<MemberInfo>().Union(type.GetFields()))
foreach (var attr in member.GetCustomAttributes(typeof(SynonymsAttribute), true))
if (attr is SynonymsAttribute && ((SynonymsAttribute)attr).Names.Contains(name))
return member;
return null;
}
}
public XmlSynonymDeserializer(Type type)
: base(type)
{
this.UnknownElement += this.SynonymHandler;
}
public XmlSynonymDeserializer(Type type, XmlRootAttribute root)
: base(type, root)
{
this.UnknownElement += this.SynonymHandler;
}
protected void SynonymHandler(object sender, XmlElementEventArgs e)
{
var member = SynonymsAttribute.GetMember(e.ObjectBeingDeserialized, e.Element.Name);
Type memberType;
if (member != null && member is FieldInfo)
memberType = ((FieldInfo)member).FieldType;
else if (member != null && member is PropertyInfo)
memberType = ((PropertyInfo)member).PropertyType;
else
return;
if (member != null)
{
object value;
XmlSynonymDeserializer serializer = new XmlSynonymDeserializer(memberType, new XmlRootAttribute(e.Element.Name));
using (System.IO.StringReader reader = new System.IO.StringReader(e.Element.OuterXml))
value = serializer.Deserialize(reader);
if (member is FieldInfo)
((FieldInfo)member).SetValue(e.ObjectBeingDeserialized, value);
else if (member is PropertyInfo)
((PropertyInfo)member).SetValue(e.ObjectBeingDeserialized, value);
}
}
}
And now the actual code of the class would be:
[XmlRoot]
public class SomeAccount
{
[XmlElement("parentId")]
[XmlSynonymDeserializer.Synonyms("LeParentId", "AnotherGreatName")]
public long ParentId { get; set; }
//rest of fields...
}
To deserialize, simply use XmlSynonymDeserializer instead of the regular XmlSerializer. This should work for most of the basic needs.
Known limitations:
This implementation supports only elements with multiple names; extending it for attributes should be trivial
Support for handling of properties/fields in cases where the entities inherit from one another is not tested
This implementation does not check for programming bugs (having the attribute on read-only/constant field/properties, multiple members with the same synonyms and so on)
I know this is an old post, but maybe this will help anyone else having the same problem.
What you could use for this problem is XmlChoiceIdentifier.
[XmlRoot]
public class SomeAccount
{
[XmlIgnore]
public ItemChoiceType EnumType;
[XmlChoiceIdentifier("EnumType")]
[XmlElement("LeParentId")]
[XmlElement("parentId")]
public long ParentId { get; set; }
//rest of fields...
}
[XmlType(IncludeInSchema = false)]
public enum ItemChoiceType
{
LeParentId,
parentId
}
Now if you have a new xml version and a new XmlElement name you just add that name to the ItemChoiceType enum and a new XmlElement to the property.
If you need only one more name, here is a quick (and rather ugly) solution that we deployed in several cases in my work when we had only to read XMLs (this will be problematic for serializing back to an XML), because it's the simplest and easiest to understand:
[XmlRoot]
public class SomeAccount
{
[XmlElement("parentId")]
public long ParentId { get; set; }
[XmlElement("LeParentId")]
public long LeParentId { get { return this.ParentId; } set { this.ParentId = value; } }
//rest of fields...
}

Xml Serializing ICollection

We're working on a project with a modestly-complex model, the core class of which has a number of properties that are lists of other associated classes. We're using EF 6 (code first) on the back end, and, following guidance on how to construct classes for that environment, we heavily leveraged ICollection<T>:
public class Product
{
// a few normal properties
public string Name { get; set; }
public string Description { get; set; }
public string Code { get; set; }
// a large list of ICollection<T> properties
public virtual ICollection<Rule> Rules { get; set; }
public virtual ICollection<Update> Updates { get; set; }
public virtual ICollection<Reference> References { get; set; }
// more ICollections from here...
}
Several of the classes referenced by ICollection<T> have collections within themselves (e.g. Rule has ICollection<Condition> Conditions), creating a fairly deep and complex tree.
Now that we have our model nearly finalized, we've moved on to developing business logic and a UI (ASP.NET MVC). One of the required capabilities of the system is to serialize/deserialize XML to interface with another system. But we've discovered that XmlSerialization.Serialize() doesn't work, complaining that it can't serialize something that uses an interface.
When we started this whole project, knowing that serialization was going to be a factor, we constructed a set of XSDs and used xsd.exe to generate classes for us, which we've since modified heavily. However, the utility put in a bunch of tagging that was supposed to aid in serialization, and we kept it all:
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlRootAttribute("product", Namespace = "", IsNullable = false)]
public class Product
{
....
}
Unfortunately, we're encountering the problem of not being able to serialize, even though we're (theoretically) telling the compiler that, yes indeed, this class is serializable. And each of the sub-classes has a similar set of tags. So, more research was undertaken...
The solutions offered here seem like they come with a lot of baggage. Other SO answers on the subject just seem to describe the difference between ICollection<T> and Collection<T>, without providing much guidance on why you'd want to avoid the concrete class. I'm considering just migrating everything to using Collection<T> instead of ICollection<T>. Am I going to cause myself problems for the future if I do this?
Have you tried IXmlSerializable? From this interface you can control what you write and read. I am not sure if this can help your problem.
public CSortedList<string, CBasicStockData> Stocks { get; set; }
public CSortedList<string, CIndustrySectorExchangeInfo> Exchanges { get; set; }
public CSortedList<string, CIndustrySectorExchangeInfo> Industries { get; set; }
public CSortedList<string, CIndustrySectorExchangeInfo> Sectors { get; set; }
public void WriteXml(XmlWriter writer)
{
try
{
///////////////////////////////////////////////////////////
writer.WriteStartElement("Stocks");
writer.WriteAttributeString("num", Stocks.Count.ToString());
foreach (var kv in Stocks)
{
writer.WriteStartElement("item");
foreach (var p in kv.Value.WritableProperties)
{
var value = p.GetValue(kv.Value);
var str = (value == null ? string.Empty : value.ToString());
writer.WriteAttributeString(p.Name, str);
}
writer.WriteEndElement();
}
writer.WriteEndElement();
///////////////////////////////////////////////////////////
foreach (var propInfo in this.WritableProperties)
{
if (propInfo.Name == "Stocks") continue;
dynamic prop = propInfo.GetValue(this);
writer.WriteStartElement(propInfo.Name);
writer.WriteAttributeString("num", prop.Count.ToString());
foreach (var kv in prop)
{
writer.WriteStartElement("item");
foreach (var p in kv.Value.WritableProperties)
{
var value = p.GetValue(kv.Value);
var str = (value == null ? string.Empty : value.ToString());
writer.WriteAttributeString(p.Name, str);
}
writer.WriteEndElement();
}
writer.WriteEndElement();
}
}
catch (Exception ex)
{
Console.WriteLine(ex);
throw ex;
}
}
public void ReadXml(XmlReader reader)
{
var propName = string.Empty;
while (reader.Read() &&
!(reader.NodeType == XmlNodeType.EndElement && reader.LocalName == this.GetType().Name))
{
if (reader.Name != "item")
{
propName = reader.Name;
continue;
}
switch (propName)
{
case "Stocks":
{
var obj = new CBasicStockData();
foreach (var propInfo in obj.WritableProperties)
{
var value = reader.GetAttribute(propInfo.Name);
if (value == null) //we may add new property to class after the file is created
continue;
propInfo.SetValue(obj, Convert.ChangeType(value, propInfo.PropertyType));
}
this.Stocks.Add(obj.Symbol, obj);
break;
}
case "Exchanges":
case "Industries":
case "Sectors":
{
var obj = new CIndustrySectorExchangeInfo();
foreach (var p in obj.WritableProperties)
{
var value = reader.GetAttribute(p.Name);
if (value == null)
continue;
p.SetValue(obj, Convert.ChangeType(value, p.PropertyType));
}
var propInfo = this.WritableProperties.Find(x => x.Name == propName);
dynamic prop = propInfo.GetValue(this);
prop.Add(obj.Name, obj);
break;
}
default:
break;
}
}
}
public static string XML_Serialize<T>(string filename, T myObject) where T : IXmlSerializable
{
XmlSerializer xmlSerializer = new XmlSerializer(myObject.GetType());
XmlWriterSettings settings = new XmlWriterSettings();
settings.Indent = true;
using (StringWriter stringWriter = new StringWriter())
using (XmlWriter writer = XmlWriter.Create(stringWriter, settings)) {
xmlSerializer.Serialize(writer, myObject);
var xml = stringWriter.ToString(); // Your xml
File.WriteAllText(filename, xml);
return xml;
}
}
public static void XML_DeSerialize<T>(string filename, out T myObject) where T : IXmlSerializable
{
XmlSerializer xmlSerializer = new XmlSerializer(typeof(T));
using (StreamReader reader = new StreamReader(filename)) {
myObject = (T)xmlSerializer.Deserialize(reader);
}
}

How to define multiple names for XmlElement field?

I have a XML document provided by client applications to my C# application. This is how a client sends the XML file:
<?xml version="1.0" encoding="utf-8"?>
<SomeAccount>
<parentId>2380983</parentId>
<!-- more elements -->
</SomeAccount>
And a C# class that supports the XML deserialization:
[XmlRoot]
public class SomeAccount
{
[XmlElement("parentId")]
public long ParentId { get; set; }
//rest of fields...
}
But there are some clients whose system send the XML in this way (note the upper case in LeParentId):
<?xml version="1.0" encoding="utf-8"?>
<SomeAccount>
<LeParentId>2380983</LeParentId>
<!-- similar for the other elements -->
</SomeAccount>
How can I make this field (and others) to support both XML names parentId and LeParentId?
This is the method I'm currently using for XML deserialization:
public sealed class XmlSerializationUtil
{
public static T Deserialize<T>(string xml)
{
if (xml == null)
return default(T);
XmlSerializer serializer = new XmlSerializer(typeof(T));
StringReader stringReader = new StringReader(xml);
return (T)serializer.Deserialize(stringReader);
}
}
I tried to add [XmlElement] twice in the field, one per element name, but that didn't work.
Take 2 - let's implement this ourselves using the unknown element handling event (see the comments below for some limitations though):
public class XmlSynonymDeserializer : XmlSerializer
{
public class SynonymsAttribute : Attribute
{
public readonly ISet<string> Names;
public SynonymsAttribute(params string[] names)
{
this.Names = new HashSet<string>(names);
}
public static MemberInfo GetMember(object obj, string name)
{
Type type = obj.GetType();
var result = type.GetProperty(name);
if (result != null)
return result;
foreach (MemberInfo member in type.GetProperties().Cast<MemberInfo>().Union(type.GetFields()))
foreach (var attr in member.GetCustomAttributes(typeof(SynonymsAttribute), true))
if (attr is SynonymsAttribute && ((SynonymsAttribute)attr).Names.Contains(name))
return member;
return null;
}
}
public XmlSynonymDeserializer(Type type)
: base(type)
{
this.UnknownElement += this.SynonymHandler;
}
public XmlSynonymDeserializer(Type type, XmlRootAttribute root)
: base(type, root)
{
this.UnknownElement += this.SynonymHandler;
}
protected void SynonymHandler(object sender, XmlElementEventArgs e)
{
var member = SynonymsAttribute.GetMember(e.ObjectBeingDeserialized, e.Element.Name);
Type memberType;
if (member != null && member is FieldInfo)
memberType = ((FieldInfo)member).FieldType;
else if (member != null && member is PropertyInfo)
memberType = ((PropertyInfo)member).PropertyType;
else
return;
if (member != null)
{
object value;
XmlSynonymDeserializer serializer = new XmlSynonymDeserializer(memberType, new XmlRootAttribute(e.Element.Name));
using (System.IO.StringReader reader = new System.IO.StringReader(e.Element.OuterXml))
value = serializer.Deserialize(reader);
if (member is FieldInfo)
((FieldInfo)member).SetValue(e.ObjectBeingDeserialized, value);
else if (member is PropertyInfo)
((PropertyInfo)member).SetValue(e.ObjectBeingDeserialized, value);
}
}
}
And now the actual code of the class would be:
[XmlRoot]
public class SomeAccount
{
[XmlElement("parentId")]
[XmlSynonymDeserializer.Synonyms("LeParentId", "AnotherGreatName")]
public long ParentId { get; set; }
//rest of fields...
}
To deserialize, simply use XmlSynonymDeserializer instead of the regular XmlSerializer. This should work for most of the basic needs.
Known limitations:
This implementation supports only elements with multiple names; extending it for attributes should be trivial
Support for handling of properties/fields in cases where the entities inherit from one another is not tested
This implementation does not check for programming bugs (having the attribute on read-only/constant field/properties, multiple members with the same synonyms and so on)
I know this is an old post, but maybe this will help anyone else having the same problem.
What you could use for this problem is XmlChoiceIdentifier.
[XmlRoot]
public class SomeAccount
{
[XmlIgnore]
public ItemChoiceType EnumType;
[XmlChoiceIdentifier("EnumType")]
[XmlElement("LeParentId")]
[XmlElement("parentId")]
public long ParentId { get; set; }
//rest of fields...
}
[XmlType(IncludeInSchema = false)]
public enum ItemChoiceType
{
LeParentId,
parentId
}
Now if you have a new xml version and a new XmlElement name you just add that name to the ItemChoiceType enum and a new XmlElement to the property.
If you need only one more name, here is a quick (and rather ugly) solution that we deployed in several cases in my work when we had only to read XMLs (this will be problematic for serializing back to an XML), because it's the simplest and easiest to understand:
[XmlRoot]
public class SomeAccount
{
[XmlElement("parentId")]
public long ParentId { get; set; }
[XmlElement("LeParentId")]
public long LeParentId { get { return this.ParentId; } set { this.ParentId = value; } }
//rest of fields...
}

DumpObject with GetFields and GetProperties for instance with nested class

this is my first question in stackoverflow and I am a beginner in using reflection.
I would like to dump all values of an object instance for reference (to keep track about used values on a test). I am using Compact Framework 3.5 not the full framework. Keep that in mind for your suggestions.
Imagine following classes:
public class Camera : IDisposable
{
public Camera.FilenameProperties SnapshotFile;
public double DigitalZoomFactor { get; set; }
public bool DisplayHistogram { get; set; }
public int ImageUpdateInterval { get; set; }
public Camera.ImprintCaptionPosType ImprintCaptionPos { get; set; }
public string ImprintCaptionString { get; set; }
}
where the 'special' types are:
public class FilenameProperties
{
public string Directory { get; set; }
public string Filename { get; set; }
public Camera.FilenamePaddingType FilenamePadding { get; set; }
public Camera.ImageType ImageFormatType { get; set; }
public Camera.ImageResolutionType ImageResolution { get; set; }
public int JPGQuality { get; set; }
public void Restore();
public void Save();
public enum Fnametype
{
tSnapshot = 0,
tCircularCapture = 1,
}
}
public enum ImprintCaptionPosType
{
Disabled = 0,
LowerRight = 1,
LowerLeft = 2,
LowerCenter = 3,
UpperRight = 4,
UpperLeft = 5,
UpperCenter = 6,
Center = 7,
}
Now, I can get the 'base' names and properties and the field names of an instance of camera:
Camera cam = new Camera();
dumpProperties(cam);
...
void dumpProperties(object oClass)
{
System.Diagnostics.Debug.WriteLine(oClass.ToString());
FieldInfo[] _Info = oClass.GetType().GetFields(BindingFlags.Public | BindingFlags.Instance);
for(int i = 0; i<_Info.Length; i++)
{
System.Diagnostics.Debug.WriteLine(_Info[i].Name + ":'" + _Info[i].GetValue(oClass).ToString()+"'");
}
foreach (PropertyInfo pi in oClass.GetType().GetProperties())
{
System.Diagnostics.Debug.WriteLine(pi.Name + ":'" + pi.GetValue(oClass, null)
+ "' Type=" + pi.PropertyType.ToString());
}
}
and then get soemthing like this:
Intermec.Multimedia.Camera
SnapshotFile:'Intermec.Multimedia.Camera+FilenameProperties'
DigitalZoomFactor:'1' Type=System.Double
DisplayHistogram:'False' Type=System.Boolean
ImageUpdateInterval:'1' Type=System.Int32
ImprintCaptionPos:'Disabled' Type=Intermec.Multimedia.Camera+ImprintCaptionPosType
ImprintCaptionString:'' Type=System.String
Now, for simple properties like DigitalZoomFactor and ImageUpdateInterval I get what I need, but for the nested class (correct wording?) I only get the type as for example with SnapshotFile. For the nested enum I get the value as with 'ImprintCaptionPos'.
How can I get the values of the nested values like FilenameProperties.Filename of the SnapshotFile field/property?
If I use dumpProperties(cam.SnapshotFile), I get the output I am looking for:
Intermec.Multimedia.Camera+FilenameProperties
Directory:'\Program Files\FrmCamera' Type=System.String
Filename:'myphoto' Type=System.String
ImageFormatType:'JPG' Type=Intermec.Multimedia.Camera+ImageType
FilenamePadding:'None' Type=Intermec.Multimedia.Camera+FilenamePaddingType
ImageResolution:'Medium' Type=Intermec.Multimedia.Camera+ImageResolutionType
JPGQuality:'100' Type=System.Int32
But how can I automate that?
I did a lot of search and test coding but was unable to find a solution. The problem seems to be getting the instance of the field to be able to iterate thru it.
I do not have the source code of the Camera class, so I cannot add or remove code in there.
Can anyone help?
I need to get something like the debugger shows:
You simply need to use recursion, and loop back into the method if your property is a class. Here's an example of an XML Serialization routine we use, that effectively walks the properties of a target using reflection, and generates an XElement from it. Your logic would be somewhat different as you're not going to build up XML, but the structure of what you're going to do will be pretty similar.
public XElement Serialize(object source,
string objectName,
bool includeNonPublicProperties)
{
XElement element;
var flags = BindingFlags.Instance | BindingFlags.Public;
if(includeNonPublicProperties)
{
flags |= BindingFlags.NonPublic;
}
var props = source.GetType().GetProperties(flags);
var type = source.GetType();
string nodeName;
if(objectName == null)
{
if (type.IsGenericType)
{
nodeName = type.Name.CropAtLast('`');
}
else
{
nodeName = type.Name;
}
}
else
{
nodeName = objectName;
}
element = new XElement(nodeName);
foreach (var prop in props)
{
string name = prop.Name;
string value = null;
bool valIsElement = false;
if (!prop.CanRead) continue;
if(prop.PropertyType.IsEnum)
{
value = prop.GetValue(source, null).ToString();
}
else
{
string typeName;
if (prop.PropertyType.IsNullable())
{
typeName = prop.PropertyType.GetGenericArguments()[0].Name;
}
else
{
typeName = prop.PropertyType.Name;
}
switch (typeName)
{
case "String":
case "Boolean":
case "Byte":
case "TimeSpan":
case "Single":
case "Double":
case "Int16":
case "UInt16":
case "Int32":
case "UInt32":
case "Int64":
case "UInt64":
value = (prop.GetValue(source, null) ?? string.Empty).ToString();
break;
case "DateTime":
try
{
var tempDT = Convert.ToDateTime(prop.GetValue(source, null));
if (tempDT == DateTime.MinValue) continue;
value = tempDT.ToString("MM/dd/yyyy HH:mm:ss.fffffff");
}
catch(Exception ex)
{
continue;
}
break;
default:
var o = prop.GetValue(source, null);
XElement child;
if (o == null)
{
child = new XElement(prop.Name);
}
else
{
child = Serialize(o, prop.Name, includeNonPublicProperties);
}
element.Add(child);
valIsElement = true;
break;
}
}
if (!valIsElement)
{
element.AddAttribute(name, value);
}
}
return element;
}
OK, I find a way (a workaround) to get all properties (in an XML but who cares) using the code from here:
The output is xml like but acceptable for me. Here an excerpt:
<xml version="1.0" encoding="utf-8">
<Camera xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
...
<ImprintCaptionPos>Disabled</ImprintCaptionPos>
<SnapshotFile>
<Directory>\\Program Files\\FrmCamera</Directory>
<Filename>myphoto</Filename>
<ImageFormatType>JPG</ImageFormatType>
<FilenamePadding>None</FilenamePadding>
<ImageResolution>Medium</ImageResolution>
<JPGQuality>100</JPGQuality>
</SnapshotFile>
...
In my code I just have to call
string s = serialization.mySerialize.SerializeObject<Intermec.Multimedia.Camera>(cam);
To get a 'dump' of all current properties of the instance.
Thanks to all for your help. Possibly I was misunderstood with my question and reflection is unable to give what I want.
Thanks
Josef
You have to do the iteration to all inner object members like you did for outer class. Will be an exercise for you to implement it for complete set of .NET types. Below is the pseudo code of simple implementation
void dumpProperties(object target)
{
if (target.GetType().IsSimple() || target.GetType().IsMethodImplemented("ToString"))
System.Diagnostics.Debug.WriteLine(target.ToString());
else if (target is IEnumerable)
{
foreach (object item in (IEnumerable)target)
{
System.Diagnostics.Debug.WriteLine(dumpProperties(target));
}
}
else
{
foreach (FieldInfo fieldInfo in target.GetType().GetFields(BindingFlags.Public | BindingFlags.Instance))
{
System.Diagnostics.Debug.WriteLine(dumpProperties(fieldInfo.FieldHandle.Value));
}
foreach (PropertyInfo propertyInfo in oClass.GetType().GetProperties())
{
System.Diagnostics.Debug.WriteLine(dumpProperties(propertyInfo.GetGetMethod().MethodHandle.Value));
}
}
}
Or you can use Cinchoo framework, which has built in function to dump any object.
Console.WriteLine(ChoObject.ToString(camera));

XML Serialization - XmlCDataSection as Serialization.XmlText

I am having problems serializing a cdata section using c#
I need to serialize XmlCDataSection object property as the innertext of the element.
The result I am looking for is this:
<Test value2="Another Test">
<![CDATA[<p>hello world</p>]]>
</Test>
To produce this, I am using this object:
public class Test
{
[System.Xml.Serialization.XmlText()]
public XmlCDataSection value { get; set; }
[System.Xml.Serialization.XmlAttributeAttribute()]
public string value2 { get; set; }
}
When using the xmltext annotation on the value property the following error is thrown.
System.InvalidOperationException:
There was an error reflecting property
'value'. --->
System.InvalidOperationException:
Cannot serialize member 'value' of
type System.Xml.XmlCDataSection.
XmlAttribute/XmlText cannot be used to
encode complex types
If I comment out the annotation, the serialization will work but the cdata section is placed into a value element which is no good for what I am trying to do:
<Test value2="Another Test">
<value><![CDATA[<p>hello world</p>]]></value>
</Test>
Can anybody point me in the right direction to getting this to work.
Thanks, Adam
Thanks Richard, only now had chance to get back to this. I think I have resolved the problem by using your suggestion. I have created a CDataField object using the following:
public class CDataField : IXmlSerializable
{
private string elementName;
private string elementValue;
public CDataField(string elementName, string elementValue)
{
this.elementName = elementName;
this.elementValue = elementValue;
}
public XmlSchema GetSchema()
{
return null;
}
public void WriteXml(XmlWriter w)
{
w.WriteStartElement(this.elementName);
w.WriteCData(this.elementValue);
w.WriteEndElement();
}
public void ReadXml(XmlReader r)
{
throw new NotImplementedException("This method has not been implemented");
}
}
The way Test is defined, your data is a CData object. So the serialisation system is trying to preserve the CData object.
But you want to serialise some text data as a CData section.
So first, the type of Test.value should be String.
You then need to control how that field is serialised, but there does not appear to be any inbuilt method or attribute to control how strings are serialised (as string, maybe with entities for reserved characters, or as CDATA). (Since, from an XML infoset perspective all of these are the same, this is not surprising.)
You can of course implemented IXmlSerializable and just code the serialisation of the Test type yourself which gives you complete control.
This basically shorter version of Jack answer with better error messages:
[XmlIgnore]
public string Content { get; set; }
[XmlText]
public XmlNode[] ContentAsCData
{
get => new[] { new XmlDocument().CreateCDataSection(Content) };
set => Content = value?.Cast<XmlCDataSection>()?.Single()?.Data;
}
Just found an alternative from here:
[XmlIgnore]
public string Content { get; set; }
[XmlText]
public XmlNode[] CDataContent
{
get
{
var dummy = new XmlDocument();
return new XmlNode[] {dummy.CreateCDataSection(Content)};
}
set
{
if (value == null)
{
Content = null;
return;
}
if (value.Length != 1)
{
throw new InvalidOperationException(
String.Format(
"Invalid array length {0}", value.Length));
}
var node0 = value[0];
var cdata = node0 as XmlCDataSection;
if (cdata == null)
{
throw new InvalidOperationException(
String.Format(
"Invalid node type {0}", node0.NodeType));
}
Content = cdata.Data;
}
}
}
I had very same problem as Adam. However this Answer does not helped me at 100% :) but gives me a clue. So I'va created a code like below. It generates XML like this:
<Actions>
<Action Type="reset">
<![CDATA[
<dbname>longcall</dbname>
<ontimeout>
<url>http://[IPPS_ADDRESS]/</url>
<timeout>10</timeout>
</ontimeout>
]]>
</Action>
<Action Type="load">
<![CDATA[
<dbname>longcall</dbname>
]]>
</Action>
</Actions>
Code:
public class ActionsCDataField : IXmlSerializable
{
public List<Action> Actions { get; set; }
public ActionsCDataField()
{
Actions = new List<Action>();
}
public XmlSchema GetSchema()
{
return null;
}
public void WriteXml(XmlWriter w)
{
foreach (var item in Actions)
{
w.WriteStartElement("Action");
w.WriteAttributeString("Type", item.Type);
w.WriteCData(item.InnerText);
w.WriteEndElement();
w.WriteString("\r\n");
}
}
public void ReadXml(XmlReader r)
{
XmlDocument xDoc = new XmlDocument();
xDoc.Load(r);
XmlNodeList nodes = xDoc.GetElementsByTagName("Action");
if (nodes != null && nodes.Count > 0)
{
foreach (XmlElement node in nodes)
{
Action a = new Action();
a.Type = node.GetAttribute("Type");
a.InnerText = node.InnerXml;
if (a.InnerText != null && a.InnerText.StartsWith("<![CDATA[") && a.InnerText.EndsWith("]]>"))
a.InnerText = a.InnerText.Substring("<![CDATA[".Length, a.InnerText.Length - "<![CDATA[]]>".Length);
Actions.Add(a);
}
}
}
}
public class Action
{
public String Type { get; set; }
public String InnerText { get; set; }
}

Categories

Resources