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);
}
}
Related
I am using SharpSerializer to serialize/deserialize object.
I want the ability to ignore specific properties when deserializing.
SharpSerializer has an option to ignore properties by attribute or by classes and property name:
SharpSerializerSettings.AdvancedSettings.AttributesToIgnore
SharpSerializerSettings.AdvancedSettings.PropertiesToIgnore
but it seems that these settings are only used to ignore from serialization, not from deserialization (I tested with the GitHub source code and the NugetPackage).
Am I correct?
Is there any way to ignore attributes/properties from deserialization?
P.S.
I'm sure there are other great serialization libraries, but it will take a great amount of effort to change the code and all the existing serialized files.
I opened an issue on the GitHub project, but the project does not seem to be active since 2018.
The object with properties to ignore need not be the root object.
You are correct that SharpSerializer does not implement ignoring of property values when deserializing. This can be verified from the reference source for ObjectFactory.fillProperties(object obj, IEnumerable<Property> properties):
private void fillProperties(object obj, IEnumerable<Property> properties)
{
foreach (Property property in properties)
{
PropertyInfo propertyInfo = obj.GetType().GetProperty(property.Name);
if (propertyInfo == null) continue;
object value = CreateObject(property);
if (value == null) continue;
propertyInfo.SetValue(obj, value, _emptyObjectArray);
}
}
This code unconditionally sets any property read from the serialization stream into the incoming object using reflection, without checking the list of ignored attributes or properties.
Thus the only way to ignore your desired properties would seem to be to create your own versions of XmlPropertyDeserializer or BinaryPropertyDeserializer that skip or filter the unwanted properties. The following is one possible implementation for XML. This implementation reads the properties from XML into a Property hierarchy as usual, then applies a filter action to remove properties corresponding to .NET properties with a custom attribute [SharpSerializerIgnoreForDeserialize] applied, then finally creates the object tree using the pruned Property.
[System.AttributeUsage(System.AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public class SharpSerializerIgnoreForDeserializeAttribute : System.Attribute { }
public class PropertyDeserializerDecorator : IPropertyDeserializer
{
readonly IPropertyDeserializer deserializer;
public PropertyDeserializerDecorator(IPropertyDeserializer deserializer) => this.deserializer = deserializer ?? throw new ArgumentNullException();
public virtual void Open(Stream stream) => deserializer.Open(stream);
public virtual Property Deserialize() => deserializer.Deserialize();
public virtual void Close() => deserializer.Close();
}
public class CustomPropertyDeserializer : PropertyDeserializerDecorator
{
Action<Property> deserializePropertyAction;
public CustomPropertyDeserializer(IPropertyDeserializer deserializer, Action<Property> deserializePropertyAction = default) : base(deserializer) => this.deserializePropertyAction = deserializePropertyAction;
public override Property Deserialize()
{
var property = base.Deserialize();
if (deserializePropertyAction != null)
property.WalkProperties(p => deserializePropertyAction(p));
return property;
}
}
public static partial class SharpSerializerExtensions
{
public static SharpSerializer Create(SharpSerializerXmlSettings settings, Action<Property> deserializePropertyAction = default)
{
// Adapted from https://github.com/polenter/SharpSerializer/blob/42f9a20b3934a7f2cece356cc8116a861cec0b91/SharpSerializer/SharpSerializer.cs#L139
// By https://github.com/polenter
var typeNameConverter = settings.AdvancedSettings.TypeNameConverter ??
new TypeNameConverter(
settings.IncludeAssemblyVersionInTypeName,
settings.IncludeCultureInTypeName,
settings.IncludePublicKeyTokenInTypeName);
// SimpleValueConverter
var simpleValueConverter = settings.AdvancedSettings.SimpleValueConverter ?? new SimpleValueConverter(settings.Culture, typeNameConverter);
// XmlWriterSettings
var xmlWriterSettings = new XmlWriterSettings
{
Encoding = settings.Encoding,
Indent = true,
OmitXmlDeclaration = true,
};
// XmlReaderSettings
var xmlReaderSettings = new XmlReaderSettings
{
IgnoreComments = true,
IgnoreWhitespace = true,
};
// Create Serializer and Deserializer
var reader = new DefaultXmlReader(typeNameConverter, simpleValueConverter, xmlReaderSettings);
var writer = new DefaultXmlWriter(typeNameConverter, simpleValueConverter, xmlWriterSettings);
var _serializer = new XmlPropertySerializer(writer);
var _deserializer = new CustomPropertyDeserializer(new XmlPropertyDeserializer(reader), deserializePropertyAction);
var serializer = new SharpSerializer(_serializer, _deserializer)
{
//InstanceCreator = settings.InstanceCreator ?? new DefaultInstanceCreator(), -- InstanceCreator not present in SharpSerializer 3.0.1
RootName = settings.AdvancedSettings.RootName,
};
serializer.PropertyProvider.PropertiesToIgnore = settings.AdvancedSettings.PropertiesToIgnore;
serializer.PropertyProvider.AttributesToIgnore = settings.AdvancedSettings.AttributesToIgnore;
return serializer;
}
public static void WalkProperties(this Property property, Action<Property> action)
{
if (action == null || property == null)
throw new ArgumentNullException();
// Avoid cyclic dependencies.
// Reference.IsProcessed is true only for the first reference of an object.
bool skipProperty = property is ReferenceTargetProperty refTarget
&& refTarget.Reference != null
&& !refTarget.Reference.IsProcessed;
if (skipProperty) return;
action(property);
switch (property.Art)
{
case PropertyArt.Collection:
{
foreach (var item in ((CollectionProperty)property).Items)
item.WalkProperties(action);
}
break;
case PropertyArt.Complex:
{
foreach (var item in ((ComplexProperty)property).Properties)
item.WalkProperties(action);
}
break;
case PropertyArt.Dictionary:
{
foreach (var item in ((DictionaryProperty)property).Items)
{
item.Key.WalkProperties(action);
item.Value.WalkProperties(action);
}
}
break;
case PropertyArt.MultiDimensionalArray:
{
foreach (var item in ((MultiDimensionalArrayProperty )property).Items)
item.Value.WalkProperties(action);
}
break;
case PropertyArt.Null:
case PropertyArt.Simple:
case PropertyArt.Reference:
break;
case PropertyArt.SingleDimensionalArray:
{
foreach (var item in ((SingleDimensionalArrayProperty)property).Items)
item.WalkProperties(action);
}
break;
default:
throw new NotImplementedException(property.Art.ToString());
}
}
public static void RemoveIgnoredChildProperties(Property p)
{
if (p.Art == PropertyArt.Complex)
{
var items = ((ComplexProperty)p).Properties;
for (int i = items.Count - 1; i >= 0; i--)
{
if (p.Type.GetProperty(items[i].Name)?.IsDefined(typeof(SharpSerializerIgnoreForDeserializeAttribute), true) == true)
{
items.RemoveAt(i);
}
}
}
}
}
Then, given the following models:
public class Root
{
public List<Model> Models { get; set; } = new ();
}
public class Model
{
public string Value { get; set; }
[SharpSerializerIgnoreForDeserialize]
public string IgnoreMe { get; set; }
}
You would deserialize using the customized XmlPropertyDeserializer as follows:
var settings = new SharpSerializerXmlSettings();
var customSerialzier = SharpSerializerExtensions.Create(settings, SharpSerializerExtensions.RemoveIgnoredChildProperties);
var deserialized = (Root)customSerialzier.Deserialize(stream);
If you need binary deserialization, use the following factory method to create the serializer instead:
public static partial class SharpSerializerExtensions
{
public static SharpSerializer Create(SharpSerializerBinarySettings settings, Action<Property> deserializePropertyAction = default)
{
// Adapted from https://github.com/polenter/SharpSerializer/blob/42f9a20b3934a7f2cece356cc8116a861cec0b91/SharpSerializer/SharpSerializer.cs#L168
// By https://github.com/polenter
var typeNameConverter = settings.AdvancedSettings.TypeNameConverter ??
new TypeNameConverter(
settings.IncludeAssemblyVersionInTypeName,
settings.IncludeCultureInTypeName,
settings.IncludePublicKeyTokenInTypeName);
// Create Serializer and Deserializer
Polenter.Serialization.Advanced.Binary.IBinaryReader reader;
Polenter.Serialization.Advanced.Binary.IBinaryWriter writer;
if (settings.Mode == BinarySerializationMode.Burst)
{
// Burst mode
writer = new BurstBinaryWriter(typeNameConverter, settings.Encoding);
reader = new BurstBinaryReader(typeNameConverter, settings.Encoding);
}
else
{
// Size optimized mode
writer = new SizeOptimizedBinaryWriter(typeNameConverter, settings.Encoding);
reader = new SizeOptimizedBinaryReader(typeNameConverter, settings.Encoding);
}
var _serializer = new BinaryPropertySerializer(writer);
var _deserializer = new CustomPropertyDeserializer(new BinaryPropertyDeserializer(reader), deserializePropertyAction);
var serializer = new SharpSerializer(_serializer, _deserializer)
{
//InstanceCreator = settings.InstanceCreator ?? new DefaultInstanceCreator(), -- InstanceCreator not present in SharpSerializer 3.0.1
RootName = settings.AdvancedSettings.RootName,
};
serializer.PropertyProvider.PropertiesToIgnore = settings.AdvancedSettings.PropertiesToIgnore;
serializer.PropertyProvider.AttributesToIgnore = settings.AdvancedSettings.AttributesToIgnore;
return serializer;
}
}
And do:
var settings = new SharpSerializerBinarySettings();
var customSerialzier = SharpSerializerExtensions.Create(settings, SharpSerializerExtensions.RemoveIgnoredChildProperties);
var deserialized = (Root)customSerialzier.Deserialize(stream);
Notes:
The methods SharpSerializerExtensions.Create() were modeled on SharpSerializer.initialize(SharpSerializerXmlSettings settings) and SharpSerializer.initialize(SharpSerializerBinarySettings settings) by Pawel Idzikowski
The version of SharpSerializer available on nuget, version 3.0.1, only includes commits through 10/8/2017. Submissions since then that add the ability to use Autofac as the instance creator are not available via nuget. My code is based on the version available via nuget, and thus does not initialize SharpSerializer.InstanceCreator which was added in 2018. The project appears not to have updated at all since then.
SharpSerializer.Deserialize() deserializes to the type specified in the serialization stream rather than to a type specified by the caller. It thus appears vulnerable to the sort of type injection attacks described in Alvaro Muñoz & Oleksandr Mirosh's blackhat paper https://www.blackhat.com/docs/us-17/thursday/us-17-Munoz-Friday-The-13th-JSON-Attacks-wp.pdf.
For details see e.g. TypeNameHandling caution in Newtonsoft Json.
If you are willing to fork, modify and build SharpSerializer yourself, you might consider updating ObjectFactory.fillProperties(object obj, IEnumerable<Property> properties) to not set ignored properties.
Demo fiddle #1 here for XML, and #2 here for binary.
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.
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...
}
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...
}
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));