Generic Object to Xml Mapper
$begingroup$
This code is part of a larger mapping library I'm working on to address some business concerns of transforming data. I was inspired by mapstruct in Java, but opted for users to annotate / add attributes to a poco to control the mapping operation.
Because reflection can be an expensive operation, I opted for scanning all loaded assemblies at run time (and filtering on criteria) and caching the data in memory. Below I am fetching metadata for types by going to the cache and accessing both the common property context and xml property context to check for certain conditions.
Some code has been omitted to keep this focused, but if you're curious the rest of the repo is here: https://github.com/Igneous01/Integration
Sample of how code is exercised
public class BooleanConverter : IXmlPropertyConverter<bool>
{
public bool ConvertToSourceType(string source)
{
if ("y".Equals(source.ToLower()))
return true;
else
return false;
}
public string ConvertToDestinationType(bool source)
{
if (source)
return "Y";
else
return "N";
}
}
public class DateTimeConverter : IXmlPropertyConverter<DateTime>
{
private const string DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
public DateTime ConvertToSourceType(string source)
{
return DateTime.ParseExact(source, DATE_TIME_FORMAT, System.Globalization.CultureInfo.InvariantCulture);
}
public string ConvertToDestinationType(DateTime source)
{
return source.ToString(DATE_TIME_FORMAT);
}
}
[IncludeInScan]
public class UnMarked
{
public int UnmarkedFieldA { get; set; }
public double UnmarkedFieldB { get; set; }
}
[XmlMapper(ParentNodeName = "Applicant", MappingOperation = XmlMappingOperation.NODE)]
public class Applicant
{
[Name("name")]
public string FirstName { get; set; }
[Name("surname")]
public string LastName { get; set; }
}
[XmlMapper(ParentNodeName = "ApplicationNode",
MappingOperation = XmlMappingOperation.NODE,
IgnoreNulls = true)]
public class Application
{
public int ApplicationID { get; set; }
[XmlPropertyConverter(typeof(DateTimeConverter))]
[Name("date")]
public DateTime CreateDate { get; set; }
public string Name { get; set; }
public object AnotherNull { get; set; }
}
[XmlMapper(ParentNodeName = "Loan",
MappingOperation = XmlMappingOperation.NODE,
IgnoreNulls = false)]
public class Loan
{
[Ignore]
public string ProductCode { get; set; }
[Name("name")]
public string Name { get; set; }
[Name("amount")]
public int Amount { get; set; }
[Name("joint")]
[XmlPropertyConverter(typeof(BooleanConverter))]
public bool IsJointAccount { get; set; }
[Name("applicant")]
public Applicant Applicant { get; set; }
public Application Application { get; set; }
[Name("CustomList")]
[XmlList(NodeName = "ListingItem")]
public List<string> Listing { get; set; }
public object Null { get; set; }
}
[XmlMapper(ParentNodeName = "CustomLoan",
MappingOperation = XmlMappingOperation.ATTRIBUTE,
IgnoreNulls = true,
Formatting = System.Xml.Linq.SaveOptions.None)]
public class CustomLoan : Loan
{
public string CustomFieldA { get; set; }
public string CustomFieldB { get; set; }
[XmlFlattenHierarchy]
public UnMarked FlattenThisProperty { get; set; }
public Dictionary<string, UnMarked> Dictionary { get; set; }
public LinkedList<Applicant> JointApplicants { get; set; }
}
static void Main(string args)
{
List<CustomLoan> loans = new List<CustomLoan>();
for (int i = 0; i < 1000; i++)
{
loans.Add(new CustomLoan()
{
Name = "Revolving Loan",
Amount = 25000,
IsJointAccount = true,
ProductCode = "RLP",
Applicant = new Applicant()
{
FirstName = "George",
LastName = "Bush"
},
Application = new Application()
{
ApplicationID = 1,
CreateDate = new DateTime(2018, 5, 22),
Name = "New Application"
},
Listing = new List<string>() { "string", "another", "text" },
CustomFieldA = "Custom Data For Client Here",
CustomFieldB = "Custom Other Data For Client Here",
Dictionary = new Dictionary<string, UnMarked>()
{
{ "KeyA", new UnMarked(){ UnmarkedFieldA = 20, UnmarkedFieldB = 3.14564 } },
{ "KeyB", new UnMarked(){ UnmarkedFieldA = 77, UnmarkedFieldB = 16.981357 } },
{ "KeyC", new UnMarked(){ UnmarkedFieldA = 86486, UnmarkedFieldB = -1.1384 } }
},
JointApplicants = new LinkedList<Applicant>(new List<Applicant>
{
new Applicant() { FirstName = "Jemma", LastName = "Busher" },
new Applicant() { FirstName = "Harrison", LastName = "Bushmaster" }
})
});
}
var xmlMapper = new XmlMapper<CustomLoan>();
string values = "";
Stopwatch watch = Stopwatch.StartNew();
foreach (CustomLoan loan in loans)
values = xmlMapper.MapToXmlString(loan);
watch.Stop();
Console.WriteLine(watch.ElapsedMilliseconds);
Console.WriteLine(values);
XmlDocument xml = new XmlDocument();
xml.LoadXml(values);
CustomLoan mappedBackObject = xmlMapper.MapToObject(xml);
//Console.WriteLine("Serialization");
//Console.WriteLine(ToXML(loans[0]));
Console.ReadKey();
return;
}
Output
<CustomLoan CustomFieldA="Custom Data For Client Here" CustomFieldB="Custom Other Data For Client Here" name="Revolving Loan" amount="25000" joint="Y">
<Dictionary>
<KeyA UnmarkedFieldA="20" UnmarkedFieldB="3.14564" />
<KeyB UnmarkedFieldA="77" UnmarkedFieldB="16.981357" />
<KeyC UnmarkedFieldA="86486" UnmarkedFieldB="-1.1384" />
</Dictionary>
<JointApplicants>
<Applicant>
<name>Jemma</name>
<surname>Busher</surname>
</Applicant>
<Applicant>
<name>Harrison</name>
<surname>Bushmaster</surname>
</Applicant>
</JointApplicants>
<applicant>
<name>George</name>
<surname>Bush</surname>
</applicant>
<Application>
<ApplicationID>1</ApplicationID>
<date>2018-05-22 00:00:00</date>
<Name>New Application</Name>
</Application>
<CustomList>
<ListingItem>string</ListingItem>
<ListingItem>another</ListingItem>
<ListingItem>text</ListingItem>
</CustomList>
</CustomLoan>
I did a basic performance check and was averaging ~140ms to map 1000 objects of this complexity.
I am having issues simplifying some of the conditions in the ToObject and ToXml methods. I think I can wrap some of these into private methods to simplify things, but I find the logic difficult to follow at times.
You may have noticed that I am not doing any error checking (or checking that the attributes are compatible with the types in question) - I am validating all of this when constructing the MetaDataCache, where I throw exceptions on startup if attributes are improperly configured. However there are probably some spots I should be error checking but I missed.
Any tips on how to simplify the mapping code would be much appreciated.
public class XmlMapper<T> where T : new()
{
private const string XPATH_ROOT_NODE_EXPRESSION = "(/*)";
private Type type = typeof(T);
// this is a hacky way of asking the XmlMapperAttribute what are it's default values in the case that the class does not have this attribute on it
private XmlMapperAttribute xmlMapperAttribute = new XmlMapperAttribute();
public XmlMapper()
{
if (MetaDataCache.Contains<T>())
{
ClassMetaData metaData = MetaDataCache.Get<T>();
if (metaData.ClassAttributeContext.ContainsAttribute<XmlMapperAttribute>())
xmlMapperAttribute = metaData.ClassAttributeContext.GetAttribute<XmlMapperAttribute>();
}
}
public T MapToObject(XmlDocument xml)
{
T instance = new T();
instance = (T)ToObject(instance, xml.SelectSingleNode(XPATH_ROOT_NODE_EXPRESSION));
return instance;
}
public string MapToXmlString(T instance)
{
return ToXmlWrapper(type, instance).ToString(xmlMapperAttribute.Formatting);
}
public XmlDocument MapToXmlDocument(T instance)
{
return ToXmlWrapper(type, instance).ToXmlDocument();
}
private object ToObject(object instance, XmlNode xmlNode, string nodeName = null)
{
System.Type type = instance.GetType();
ClassMetaData classMetaData = MetaDataCache.Get(type);
XmlMappingOperation xmlMappingOperation = xmlMapperAttribute.MappingOperation;
if (classMetaData.ClassAttributeContext.ContainsAttribute<XmlMapperAttribute>())
{
XmlMapperAttribute xmlMapper = classMetaData.ClassAttributeContext.GetAttribute<XmlMapperAttribute>();
xmlMappingOperation = xmlMapper.MappingOperation;
if (nodeName == null)
{
nodeName = xmlMapper.ParentNodeName;
xmlNode = xmlNode.SelectSingleNode($"//{nodeName}");
}
}
foreach (PropertyInfo property in classMetaData.Properties)
{
string propertyName = property.Name;
System.Type propertyType = property.PropertyType;
if (classMetaData.HasPropertyAttributeContext<PropertyAttributeContext>(property))
{
PropertyAttributeContext propertyAttributeContext = classMetaData.GetAttributeContextForProperty<PropertyAttributeContext>(property);
if (propertyAttributeContext.HasIgnoreAttribute)
continue;
propertyName = propertyAttributeContext.HasNameAttribute ? propertyAttributeContext.NameAttribute.Name : propertyName;
}
object propertyValue;
if (xmlMappingOperation.Equals(XmlMappingOperation.NODE))
{
XmlNode propertyNode = xmlNode.SelectSingleNode($"//{nodeName}/{propertyName}");
propertyValue = propertyNode?.InnerText;
}
else // ATTRIBUTE
{
XmlNode propertyNode = xmlNode.SelectSingleNode($"//{nodeName}");
propertyValue = propertyNode.Attributes[propertyName]?.Value;
}
if (classMetaData.HasPropertyAttributeContext<XmlPropertyAttributeContext>(property))
{
XmlPropertyAttributeContext xmlPropertyAttributeContext = classMetaData.GetAttributeContextForProperty<XmlPropertyAttributeContext>(property);
if (xmlPropertyAttributeContext.HasXmlListAttribute)
{
XmlListAttribute xmlList = xmlPropertyAttributeContext.XmlListAttribute;
XmlNodeList results = xmlNode.SelectNodes($"//{nodeName}/{propertyName}/{xmlList.NodeName}");
if (results.Count > 0)
{
object childInstance = CollectionXmlNodeListToObject(results, propertyType);
property.SetValue(instance, childInstance);
}
continue;
}
else if (xmlPropertyAttributeContext.HasXmlDictionaryAttribute)
{
XmlDictionaryAttribute xmlList = xmlPropertyAttributeContext.XmlDictionaryAttribute;
XmlNodeList results = xmlNode.SelectNodes($"//{nodeName}/{propertyName}/*");
if (results.Count > 0)
{
object childInstance = DictionaryXmlNodeListToObject(results, propertyType);
property.SetValue(instance, childInstance);
}
continue;
}
else if (xmlPropertyAttributeContext.HasXmlFlattenHierarchyAttribute)
{
object childInstance = Activator.CreateInstance(propertyType);
XmlNode results = xmlNode.SelectSingleNode($"//{nodeName}/{propertyName}");
if (results != null)
{
childInstance = ToObject(childInstance, results, propertyName);
property.SetValue(instance, childInstance);
}
continue;
}
else if (xmlPropertyAttributeContext.HasXmlPropertyConverterAttribute && propertyValue != null)
{
XmlPropertyConverterAttribute converter = xmlPropertyAttributeContext.XmlPropertyConverterAttribute;
try
{
propertyValue = converter.ConvertToSourceType(propertyValue);
}
catch (Exception ex)
{
throw new XmlPropertyConverterException("XmlPropertyConverter threw an exception", ex);
}
}
}
else
{
if (propertyType.IsDictionary())
{
XmlNodeList results = xmlNode.SelectNodes($"//{nodeName}/{propertyName}/*");
if (results.Count > 0)
{
object childInstance = DictionaryXmlNodeListToObject(results, propertyType);
property.SetValue(instance, childInstance);
}
continue;
}
else if (propertyType.IsCollection())
{
string listItemNodeName = propertyType.GenericTypeArguments[0].Name;
XmlNodeList results = xmlNode.SelectNodes($"//{nodeName}/{propertyName}/{listItemNodeName}");
if (results.Count > 0)
{
object childInstance = CollectionXmlNodeListToObject(results, propertyType);
property.SetValue(instance, childInstance);
}
continue;
}
if (propertyType.IsClass && MetaDataCache.Contains(propertyType))
{
// TODO: Dont think this will work
object childInstance = Activator.CreateInstance(propertyType);
XmlNode results = xmlNode.SelectSingleNode($"//{nodeName}/{propertyName}");
if (results != null)
{
childInstance = ToObject(childInstance, results, propertyName);
property.SetValue(instance, childInstance);
}
continue;
}
}
property.SetValue(instance, UniversalTypeConverter.Convert(propertyValue, propertyType));
}
return instance;
}
private object CollectionXmlNodeListToObject(XmlNodeList nodeList, Type collectionType)
{
object collection = CreateInstanceOfType(collectionType);
Type containedType = collectionType.GetTypeInfo().GenericTypeArguments[0];
Type iCollectionType = typeof(ICollection<>).MakeGenericType(containedType);
foreach (XmlNode node in nodeList)
{
object value = CreateInstanceOfType(containedType);
if (containedType.IsClass && MetaDataCache.Contains(containedType))
value = ToObject(value, node, node.Name);
else
value = node.InnerText;
iCollectionType.GetMethod("Add").Invoke(collection, new { value });
}
return collection;
}
private object DictionaryXmlNodeListToObject(XmlNodeList nodeList, System.Type dictionaryType)
{
object dictionary = Activator.CreateInstance(dictionaryType);
Type keyType = dictionaryType.GetTypeInfo().GenericTypeArguments[0];
Type valueType = dictionaryType.GetTypeInfo().GenericTypeArguments[1];
foreach (XmlNode node in nodeList)
{
object key = node.Name; // will be replaced with dictionary mapper later
object value = CreateInstanceOfType(valueType);
if (valueType.IsClass && MetaDataCache.Contains(valueType))
value = ToObject(value, node, node.Name);
else
value = node.InnerText;
dictionaryType.GetMethod("Add").Invoke(dictionary, new { node.Name, value });
}
return dictionary;
}
private object CreateInstanceOfType(Type type)
{
if (!type.HasDefaultConstructor())
return null;
else
return Activator.CreateInstance(type);
}
private XDocument ToXmlWrapper(Type type, T instance)
{
XElement rootElement = ToXml(instance, type);
XDocument xDocument = new XDocument();
xDocument.Add(rootElement);
// will throw exception if fails to construct XmlDocument
if (xmlMapperAttribute.Validate)
xDocument.ToXmlDocument();
return xDocument;
}
private XElement ToXml(object instance, Type type, XElement element = null, string nodeName = null)
{
ClassMetaData classMetaData = MetaDataCache.Get(type);
XmlMappingOperation xmlMappingOperation = xmlMapperAttribute.MappingOperation;
bool ignoreNulls = xmlMapperAttribute.IgnoreNulls;
if (classMetaData.ClassAttributeContext.ContainsAttribute<XmlMapperAttribute>())
{
XmlMapperAttribute xmlMapper = classMetaData.ClassAttributeContext.GetAttribute<XmlMapperAttribute>();
xmlMappingOperation = xmlMapper.MappingOperation;
ignoreNulls = xmlMapper.IgnoreNulls;
if (element == null)
{
element = new XElement(xmlMapper.ParentNodeName);
nodeName = xmlMapper.ParentNodeName;
}
}
//element.Name = nodeName;
foreach (PropertyInfo property in classMetaData.Properties)
{
string propertyName = property.Name;
object propertyValue = property.GetValue(instance);
System.Type propertyType = property.PropertyType;
if (propertyValue == null && ignoreNulls)
continue;
if (classMetaData.HasPropertyAttributeContext<PropertyAttributeContext>(property))
{
PropertyAttributeContext propertyAttributeContext = classMetaData.GetAttributeContextForProperty<PropertyAttributeContext>(property);
if (propertyAttributeContext.HasIgnoreAttribute)
continue;
propertyName = propertyAttributeContext.HasNameAttribute ? propertyAttributeContext.NameAttribute.Name : propertyName;
}
if (classMetaData.HasPropertyAttributeContext<XmlPropertyAttributeContext>(property))
{
XmlPropertyAttributeContext xmlPropertyAttributeContext = classMetaData.GetAttributeContextForProperty<XmlPropertyAttributeContext>(property);
if (xmlPropertyAttributeContext.HasXmlPropertyConverterAttribute && propertyValue != null)
{
XmlPropertyConverterAttribute converter = xmlPropertyAttributeContext.XmlPropertyConverterAttribute;
try
{
propertyValue = converter.ConvertToDestinationType(propertyValue);
AddToXElement(element, xmlMappingOperation, propertyName, propertyValue);
continue;
}
catch (Exception ex)
{
throw new XmlPropertyConverterException("XmlPropertyConverter threw an exception", ex);
}
}
else if (xmlPropertyAttributeContext.HasXmlDictionaryAttribute)
{
XmlDictionaryAttribute xmlDictionary = xmlPropertyAttributeContext.XmlDictionaryAttribute;
element.Add(DictionaryToXElement(propertyValue, propertyName));
continue;
}
else if (xmlPropertyAttributeContext.HasXmlListAttribute)
{
XmlListAttribute xmlList = xmlPropertyAttributeContext.XmlListAttribute;
element.Add(CollectionToXElement(propertyValue, propertyName, xmlList.NodeName));
continue;
}
else if (xmlPropertyAttributeContext.HasXmlFlattenHierarchyAttribute)
{
element = ToXml(propertyValue, propertyType, element, propertyName);
continue;
}
}
else
{
if (propertyType.IsDictionary())
{
element.Add(DictionaryToXElement(propertyValue, propertyName));
continue;
}
else if (propertyType.IsCollection())
{
element.Add(CollectionToXElement(propertyValue, propertyName, propertyType.GenericTypeArguments[0].Name));
continue;
}
else if (propertyType.IsClass && MetaDataCache.Contains(propertyType))
{
XElement propertyElement = new XElement(propertyName);
propertyElement = ToXml(propertyValue, propertyType, propertyElement, propertyName);
element.Add(propertyElement);
continue;
}
}
AddToXElement(element, xmlMappingOperation, propertyName, propertyValue);
}
return element;
}
private void AddToXElement(XElement element, XmlMappingOperation xmlMappingOperation, string propertyName, object propertyValue)
{
if (xmlMappingOperation.Equals(XmlMappingOperation.ATTRIBUTE))
element.SetAttributeValue(propertyName, propertyValue);
else if (xmlMappingOperation.Equals(XmlMappingOperation.NODE))
element.Add(new XElement(propertyName, propertyValue));
}
private XElement CollectionToXElement(object collection, string parentNodeName, string childNodeName)
{
XElement propertyElement = new XElement(parentNodeName);
Type containedType = collection.GetType().GenericTypeArguments[0];
foreach (var item in (ICollection)collection)
{
XElement itemElement = new XElement(childNodeName);
if (containedType.IsClass && MetaDataCache.Contains(containedType))
itemElement = ToXml(item, item.GetType(), itemElement, childNodeName);
else
itemElement.SetValue(item);
propertyElement.Add(itemElement);
}
return propertyElement;
}
private XElement DictionaryToXElement(object dictionary, string parentNodeName)
{
XElement propertyElement = new XElement(parentNodeName);
Type keyType = dictionary.GetType().GenericTypeArguments[0];
Type valueType = dictionary.GetType().GenericTypeArguments[1];
foreach (DictionaryEntry kvp in (IDictionary)dictionary)
{
// TODO - this will call converter that converts Dictionary key to XElement
XElement itemElement = new XElement(kvp.Key.ToString());
if (valueType.IsClass && MetaDataCache.Contains(valueType))
itemElement = ToXml(kvp.Value, kvp.Value.GetType(), itemElement, itemElement.Name.LocalName);
else
itemElement.SetValue(kvp.Value);
propertyElement.Add(itemElement);
}
return propertyElement;
}
}
.net reflection framework
New contributor
$endgroup$
add a comment |
$begingroup$
This code is part of a larger mapping library I'm working on to address some business concerns of transforming data. I was inspired by mapstruct in Java, but opted for users to annotate / add attributes to a poco to control the mapping operation.
Because reflection can be an expensive operation, I opted for scanning all loaded assemblies at run time (and filtering on criteria) and caching the data in memory. Below I am fetching metadata for types by going to the cache and accessing both the common property context and xml property context to check for certain conditions.
Some code has been omitted to keep this focused, but if you're curious the rest of the repo is here: https://github.com/Igneous01/Integration
Sample of how code is exercised
public class BooleanConverter : IXmlPropertyConverter<bool>
{
public bool ConvertToSourceType(string source)
{
if ("y".Equals(source.ToLower()))
return true;
else
return false;
}
public string ConvertToDestinationType(bool source)
{
if (source)
return "Y";
else
return "N";
}
}
public class DateTimeConverter : IXmlPropertyConverter<DateTime>
{
private const string DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
public DateTime ConvertToSourceType(string source)
{
return DateTime.ParseExact(source, DATE_TIME_FORMAT, System.Globalization.CultureInfo.InvariantCulture);
}
public string ConvertToDestinationType(DateTime source)
{
return source.ToString(DATE_TIME_FORMAT);
}
}
[IncludeInScan]
public class UnMarked
{
public int UnmarkedFieldA { get; set; }
public double UnmarkedFieldB { get; set; }
}
[XmlMapper(ParentNodeName = "Applicant", MappingOperation = XmlMappingOperation.NODE)]
public class Applicant
{
[Name("name")]
public string FirstName { get; set; }
[Name("surname")]
public string LastName { get; set; }
}
[XmlMapper(ParentNodeName = "ApplicationNode",
MappingOperation = XmlMappingOperation.NODE,
IgnoreNulls = true)]
public class Application
{
public int ApplicationID { get; set; }
[XmlPropertyConverter(typeof(DateTimeConverter))]
[Name("date")]
public DateTime CreateDate { get; set; }
public string Name { get; set; }
public object AnotherNull { get; set; }
}
[XmlMapper(ParentNodeName = "Loan",
MappingOperation = XmlMappingOperation.NODE,
IgnoreNulls = false)]
public class Loan
{
[Ignore]
public string ProductCode { get; set; }
[Name("name")]
public string Name { get; set; }
[Name("amount")]
public int Amount { get; set; }
[Name("joint")]
[XmlPropertyConverter(typeof(BooleanConverter))]
public bool IsJointAccount { get; set; }
[Name("applicant")]
public Applicant Applicant { get; set; }
public Application Application { get; set; }
[Name("CustomList")]
[XmlList(NodeName = "ListingItem")]
public List<string> Listing { get; set; }
public object Null { get; set; }
}
[XmlMapper(ParentNodeName = "CustomLoan",
MappingOperation = XmlMappingOperation.ATTRIBUTE,
IgnoreNulls = true,
Formatting = System.Xml.Linq.SaveOptions.None)]
public class CustomLoan : Loan
{
public string CustomFieldA { get; set; }
public string CustomFieldB { get; set; }
[XmlFlattenHierarchy]
public UnMarked FlattenThisProperty { get; set; }
public Dictionary<string, UnMarked> Dictionary { get; set; }
public LinkedList<Applicant> JointApplicants { get; set; }
}
static void Main(string args)
{
List<CustomLoan> loans = new List<CustomLoan>();
for (int i = 0; i < 1000; i++)
{
loans.Add(new CustomLoan()
{
Name = "Revolving Loan",
Amount = 25000,
IsJointAccount = true,
ProductCode = "RLP",
Applicant = new Applicant()
{
FirstName = "George",
LastName = "Bush"
},
Application = new Application()
{
ApplicationID = 1,
CreateDate = new DateTime(2018, 5, 22),
Name = "New Application"
},
Listing = new List<string>() { "string", "another", "text" },
CustomFieldA = "Custom Data For Client Here",
CustomFieldB = "Custom Other Data For Client Here",
Dictionary = new Dictionary<string, UnMarked>()
{
{ "KeyA", new UnMarked(){ UnmarkedFieldA = 20, UnmarkedFieldB = 3.14564 } },
{ "KeyB", new UnMarked(){ UnmarkedFieldA = 77, UnmarkedFieldB = 16.981357 } },
{ "KeyC", new UnMarked(){ UnmarkedFieldA = 86486, UnmarkedFieldB = -1.1384 } }
},
JointApplicants = new LinkedList<Applicant>(new List<Applicant>
{
new Applicant() { FirstName = "Jemma", LastName = "Busher" },
new Applicant() { FirstName = "Harrison", LastName = "Bushmaster" }
})
});
}
var xmlMapper = new XmlMapper<CustomLoan>();
string values = "";
Stopwatch watch = Stopwatch.StartNew();
foreach (CustomLoan loan in loans)
values = xmlMapper.MapToXmlString(loan);
watch.Stop();
Console.WriteLine(watch.ElapsedMilliseconds);
Console.WriteLine(values);
XmlDocument xml = new XmlDocument();
xml.LoadXml(values);
CustomLoan mappedBackObject = xmlMapper.MapToObject(xml);
//Console.WriteLine("Serialization");
//Console.WriteLine(ToXML(loans[0]));
Console.ReadKey();
return;
}
Output
<CustomLoan CustomFieldA="Custom Data For Client Here" CustomFieldB="Custom Other Data For Client Here" name="Revolving Loan" amount="25000" joint="Y">
<Dictionary>
<KeyA UnmarkedFieldA="20" UnmarkedFieldB="3.14564" />
<KeyB UnmarkedFieldA="77" UnmarkedFieldB="16.981357" />
<KeyC UnmarkedFieldA="86486" UnmarkedFieldB="-1.1384" />
</Dictionary>
<JointApplicants>
<Applicant>
<name>Jemma</name>
<surname>Busher</surname>
</Applicant>
<Applicant>
<name>Harrison</name>
<surname>Bushmaster</surname>
</Applicant>
</JointApplicants>
<applicant>
<name>George</name>
<surname>Bush</surname>
</applicant>
<Application>
<ApplicationID>1</ApplicationID>
<date>2018-05-22 00:00:00</date>
<Name>New Application</Name>
</Application>
<CustomList>
<ListingItem>string</ListingItem>
<ListingItem>another</ListingItem>
<ListingItem>text</ListingItem>
</CustomList>
</CustomLoan>
I did a basic performance check and was averaging ~140ms to map 1000 objects of this complexity.
I am having issues simplifying some of the conditions in the ToObject and ToXml methods. I think I can wrap some of these into private methods to simplify things, but I find the logic difficult to follow at times.
You may have noticed that I am not doing any error checking (or checking that the attributes are compatible with the types in question) - I am validating all of this when constructing the MetaDataCache, where I throw exceptions on startup if attributes are improperly configured. However there are probably some spots I should be error checking but I missed.
Any tips on how to simplify the mapping code would be much appreciated.
public class XmlMapper<T> where T : new()
{
private const string XPATH_ROOT_NODE_EXPRESSION = "(/*)";
private Type type = typeof(T);
// this is a hacky way of asking the XmlMapperAttribute what are it's default values in the case that the class does not have this attribute on it
private XmlMapperAttribute xmlMapperAttribute = new XmlMapperAttribute();
public XmlMapper()
{
if (MetaDataCache.Contains<T>())
{
ClassMetaData metaData = MetaDataCache.Get<T>();
if (metaData.ClassAttributeContext.ContainsAttribute<XmlMapperAttribute>())
xmlMapperAttribute = metaData.ClassAttributeContext.GetAttribute<XmlMapperAttribute>();
}
}
public T MapToObject(XmlDocument xml)
{
T instance = new T();
instance = (T)ToObject(instance, xml.SelectSingleNode(XPATH_ROOT_NODE_EXPRESSION));
return instance;
}
public string MapToXmlString(T instance)
{
return ToXmlWrapper(type, instance).ToString(xmlMapperAttribute.Formatting);
}
public XmlDocument MapToXmlDocument(T instance)
{
return ToXmlWrapper(type, instance).ToXmlDocument();
}
private object ToObject(object instance, XmlNode xmlNode, string nodeName = null)
{
System.Type type = instance.GetType();
ClassMetaData classMetaData = MetaDataCache.Get(type);
XmlMappingOperation xmlMappingOperation = xmlMapperAttribute.MappingOperation;
if (classMetaData.ClassAttributeContext.ContainsAttribute<XmlMapperAttribute>())
{
XmlMapperAttribute xmlMapper = classMetaData.ClassAttributeContext.GetAttribute<XmlMapperAttribute>();
xmlMappingOperation = xmlMapper.MappingOperation;
if (nodeName == null)
{
nodeName = xmlMapper.ParentNodeName;
xmlNode = xmlNode.SelectSingleNode($"//{nodeName}");
}
}
foreach (PropertyInfo property in classMetaData.Properties)
{
string propertyName = property.Name;
System.Type propertyType = property.PropertyType;
if (classMetaData.HasPropertyAttributeContext<PropertyAttributeContext>(property))
{
PropertyAttributeContext propertyAttributeContext = classMetaData.GetAttributeContextForProperty<PropertyAttributeContext>(property);
if (propertyAttributeContext.HasIgnoreAttribute)
continue;
propertyName = propertyAttributeContext.HasNameAttribute ? propertyAttributeContext.NameAttribute.Name : propertyName;
}
object propertyValue;
if (xmlMappingOperation.Equals(XmlMappingOperation.NODE))
{
XmlNode propertyNode = xmlNode.SelectSingleNode($"//{nodeName}/{propertyName}");
propertyValue = propertyNode?.InnerText;
}
else // ATTRIBUTE
{
XmlNode propertyNode = xmlNode.SelectSingleNode($"//{nodeName}");
propertyValue = propertyNode.Attributes[propertyName]?.Value;
}
if (classMetaData.HasPropertyAttributeContext<XmlPropertyAttributeContext>(property))
{
XmlPropertyAttributeContext xmlPropertyAttributeContext = classMetaData.GetAttributeContextForProperty<XmlPropertyAttributeContext>(property);
if (xmlPropertyAttributeContext.HasXmlListAttribute)
{
XmlListAttribute xmlList = xmlPropertyAttributeContext.XmlListAttribute;
XmlNodeList results = xmlNode.SelectNodes($"//{nodeName}/{propertyName}/{xmlList.NodeName}");
if (results.Count > 0)
{
object childInstance = CollectionXmlNodeListToObject(results, propertyType);
property.SetValue(instance, childInstance);
}
continue;
}
else if (xmlPropertyAttributeContext.HasXmlDictionaryAttribute)
{
XmlDictionaryAttribute xmlList = xmlPropertyAttributeContext.XmlDictionaryAttribute;
XmlNodeList results = xmlNode.SelectNodes($"//{nodeName}/{propertyName}/*");
if (results.Count > 0)
{
object childInstance = DictionaryXmlNodeListToObject(results, propertyType);
property.SetValue(instance, childInstance);
}
continue;
}
else if (xmlPropertyAttributeContext.HasXmlFlattenHierarchyAttribute)
{
object childInstance = Activator.CreateInstance(propertyType);
XmlNode results = xmlNode.SelectSingleNode($"//{nodeName}/{propertyName}");
if (results != null)
{
childInstance = ToObject(childInstance, results, propertyName);
property.SetValue(instance, childInstance);
}
continue;
}
else if (xmlPropertyAttributeContext.HasXmlPropertyConverterAttribute && propertyValue != null)
{
XmlPropertyConverterAttribute converter = xmlPropertyAttributeContext.XmlPropertyConverterAttribute;
try
{
propertyValue = converter.ConvertToSourceType(propertyValue);
}
catch (Exception ex)
{
throw new XmlPropertyConverterException("XmlPropertyConverter threw an exception", ex);
}
}
}
else
{
if (propertyType.IsDictionary())
{
XmlNodeList results = xmlNode.SelectNodes($"//{nodeName}/{propertyName}/*");
if (results.Count > 0)
{
object childInstance = DictionaryXmlNodeListToObject(results, propertyType);
property.SetValue(instance, childInstance);
}
continue;
}
else if (propertyType.IsCollection())
{
string listItemNodeName = propertyType.GenericTypeArguments[0].Name;
XmlNodeList results = xmlNode.SelectNodes($"//{nodeName}/{propertyName}/{listItemNodeName}");
if (results.Count > 0)
{
object childInstance = CollectionXmlNodeListToObject(results, propertyType);
property.SetValue(instance, childInstance);
}
continue;
}
if (propertyType.IsClass && MetaDataCache.Contains(propertyType))
{
// TODO: Dont think this will work
object childInstance = Activator.CreateInstance(propertyType);
XmlNode results = xmlNode.SelectSingleNode($"//{nodeName}/{propertyName}");
if (results != null)
{
childInstance = ToObject(childInstance, results, propertyName);
property.SetValue(instance, childInstance);
}
continue;
}
}
property.SetValue(instance, UniversalTypeConverter.Convert(propertyValue, propertyType));
}
return instance;
}
private object CollectionXmlNodeListToObject(XmlNodeList nodeList, Type collectionType)
{
object collection = CreateInstanceOfType(collectionType);
Type containedType = collectionType.GetTypeInfo().GenericTypeArguments[0];
Type iCollectionType = typeof(ICollection<>).MakeGenericType(containedType);
foreach (XmlNode node in nodeList)
{
object value = CreateInstanceOfType(containedType);
if (containedType.IsClass && MetaDataCache.Contains(containedType))
value = ToObject(value, node, node.Name);
else
value = node.InnerText;
iCollectionType.GetMethod("Add").Invoke(collection, new { value });
}
return collection;
}
private object DictionaryXmlNodeListToObject(XmlNodeList nodeList, System.Type dictionaryType)
{
object dictionary = Activator.CreateInstance(dictionaryType);
Type keyType = dictionaryType.GetTypeInfo().GenericTypeArguments[0];
Type valueType = dictionaryType.GetTypeInfo().GenericTypeArguments[1];
foreach (XmlNode node in nodeList)
{
object key = node.Name; // will be replaced with dictionary mapper later
object value = CreateInstanceOfType(valueType);
if (valueType.IsClass && MetaDataCache.Contains(valueType))
value = ToObject(value, node, node.Name);
else
value = node.InnerText;
dictionaryType.GetMethod("Add").Invoke(dictionary, new { node.Name, value });
}
return dictionary;
}
private object CreateInstanceOfType(Type type)
{
if (!type.HasDefaultConstructor())
return null;
else
return Activator.CreateInstance(type);
}
private XDocument ToXmlWrapper(Type type, T instance)
{
XElement rootElement = ToXml(instance, type);
XDocument xDocument = new XDocument();
xDocument.Add(rootElement);
// will throw exception if fails to construct XmlDocument
if (xmlMapperAttribute.Validate)
xDocument.ToXmlDocument();
return xDocument;
}
private XElement ToXml(object instance, Type type, XElement element = null, string nodeName = null)
{
ClassMetaData classMetaData = MetaDataCache.Get(type);
XmlMappingOperation xmlMappingOperation = xmlMapperAttribute.MappingOperation;
bool ignoreNulls = xmlMapperAttribute.IgnoreNulls;
if (classMetaData.ClassAttributeContext.ContainsAttribute<XmlMapperAttribute>())
{
XmlMapperAttribute xmlMapper = classMetaData.ClassAttributeContext.GetAttribute<XmlMapperAttribute>();
xmlMappingOperation = xmlMapper.MappingOperation;
ignoreNulls = xmlMapper.IgnoreNulls;
if (element == null)
{
element = new XElement(xmlMapper.ParentNodeName);
nodeName = xmlMapper.ParentNodeName;
}
}
//element.Name = nodeName;
foreach (PropertyInfo property in classMetaData.Properties)
{
string propertyName = property.Name;
object propertyValue = property.GetValue(instance);
System.Type propertyType = property.PropertyType;
if (propertyValue == null && ignoreNulls)
continue;
if (classMetaData.HasPropertyAttributeContext<PropertyAttributeContext>(property))
{
PropertyAttributeContext propertyAttributeContext = classMetaData.GetAttributeContextForProperty<PropertyAttributeContext>(property);
if (propertyAttributeContext.HasIgnoreAttribute)
continue;
propertyName = propertyAttributeContext.HasNameAttribute ? propertyAttributeContext.NameAttribute.Name : propertyName;
}
if (classMetaData.HasPropertyAttributeContext<XmlPropertyAttributeContext>(property))
{
XmlPropertyAttributeContext xmlPropertyAttributeContext = classMetaData.GetAttributeContextForProperty<XmlPropertyAttributeContext>(property);
if (xmlPropertyAttributeContext.HasXmlPropertyConverterAttribute && propertyValue != null)
{
XmlPropertyConverterAttribute converter = xmlPropertyAttributeContext.XmlPropertyConverterAttribute;
try
{
propertyValue = converter.ConvertToDestinationType(propertyValue);
AddToXElement(element, xmlMappingOperation, propertyName, propertyValue);
continue;
}
catch (Exception ex)
{
throw new XmlPropertyConverterException("XmlPropertyConverter threw an exception", ex);
}
}
else if (xmlPropertyAttributeContext.HasXmlDictionaryAttribute)
{
XmlDictionaryAttribute xmlDictionary = xmlPropertyAttributeContext.XmlDictionaryAttribute;
element.Add(DictionaryToXElement(propertyValue, propertyName));
continue;
}
else if (xmlPropertyAttributeContext.HasXmlListAttribute)
{
XmlListAttribute xmlList = xmlPropertyAttributeContext.XmlListAttribute;
element.Add(CollectionToXElement(propertyValue, propertyName, xmlList.NodeName));
continue;
}
else if (xmlPropertyAttributeContext.HasXmlFlattenHierarchyAttribute)
{
element = ToXml(propertyValue, propertyType, element, propertyName);
continue;
}
}
else
{
if (propertyType.IsDictionary())
{
element.Add(DictionaryToXElement(propertyValue, propertyName));
continue;
}
else if (propertyType.IsCollection())
{
element.Add(CollectionToXElement(propertyValue, propertyName, propertyType.GenericTypeArguments[0].Name));
continue;
}
else if (propertyType.IsClass && MetaDataCache.Contains(propertyType))
{
XElement propertyElement = new XElement(propertyName);
propertyElement = ToXml(propertyValue, propertyType, propertyElement, propertyName);
element.Add(propertyElement);
continue;
}
}
AddToXElement(element, xmlMappingOperation, propertyName, propertyValue);
}
return element;
}
private void AddToXElement(XElement element, XmlMappingOperation xmlMappingOperation, string propertyName, object propertyValue)
{
if (xmlMappingOperation.Equals(XmlMappingOperation.ATTRIBUTE))
element.SetAttributeValue(propertyName, propertyValue);
else if (xmlMappingOperation.Equals(XmlMappingOperation.NODE))
element.Add(new XElement(propertyName, propertyValue));
}
private XElement CollectionToXElement(object collection, string parentNodeName, string childNodeName)
{
XElement propertyElement = new XElement(parentNodeName);
Type containedType = collection.GetType().GenericTypeArguments[0];
foreach (var item in (ICollection)collection)
{
XElement itemElement = new XElement(childNodeName);
if (containedType.IsClass && MetaDataCache.Contains(containedType))
itemElement = ToXml(item, item.GetType(), itemElement, childNodeName);
else
itemElement.SetValue(item);
propertyElement.Add(itemElement);
}
return propertyElement;
}
private XElement DictionaryToXElement(object dictionary, string parentNodeName)
{
XElement propertyElement = new XElement(parentNodeName);
Type keyType = dictionary.GetType().GenericTypeArguments[0];
Type valueType = dictionary.GetType().GenericTypeArguments[1];
foreach (DictionaryEntry kvp in (IDictionary)dictionary)
{
// TODO - this will call converter that converts Dictionary key to XElement
XElement itemElement = new XElement(kvp.Key.ToString());
if (valueType.IsClass && MetaDataCache.Contains(valueType))
itemElement = ToXml(kvp.Value, kvp.Value.GetType(), itemElement, itemElement.Name.LocalName);
else
itemElement.SetValue(kvp.Value);
propertyElement.Add(itemElement);
}
return propertyElement;
}
}
.net reflection framework
New contributor
$endgroup$
add a comment |
$begingroup$
This code is part of a larger mapping library I'm working on to address some business concerns of transforming data. I was inspired by mapstruct in Java, but opted for users to annotate / add attributes to a poco to control the mapping operation.
Because reflection can be an expensive operation, I opted for scanning all loaded assemblies at run time (and filtering on criteria) and caching the data in memory. Below I am fetching metadata for types by going to the cache and accessing both the common property context and xml property context to check for certain conditions.
Some code has been omitted to keep this focused, but if you're curious the rest of the repo is here: https://github.com/Igneous01/Integration
Sample of how code is exercised
public class BooleanConverter : IXmlPropertyConverter<bool>
{
public bool ConvertToSourceType(string source)
{
if ("y".Equals(source.ToLower()))
return true;
else
return false;
}
public string ConvertToDestinationType(bool source)
{
if (source)
return "Y";
else
return "N";
}
}
public class DateTimeConverter : IXmlPropertyConverter<DateTime>
{
private const string DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
public DateTime ConvertToSourceType(string source)
{
return DateTime.ParseExact(source, DATE_TIME_FORMAT, System.Globalization.CultureInfo.InvariantCulture);
}
public string ConvertToDestinationType(DateTime source)
{
return source.ToString(DATE_TIME_FORMAT);
}
}
[IncludeInScan]
public class UnMarked
{
public int UnmarkedFieldA { get; set; }
public double UnmarkedFieldB { get; set; }
}
[XmlMapper(ParentNodeName = "Applicant", MappingOperation = XmlMappingOperation.NODE)]
public class Applicant
{
[Name("name")]
public string FirstName { get; set; }
[Name("surname")]
public string LastName { get; set; }
}
[XmlMapper(ParentNodeName = "ApplicationNode",
MappingOperation = XmlMappingOperation.NODE,
IgnoreNulls = true)]
public class Application
{
public int ApplicationID { get; set; }
[XmlPropertyConverter(typeof(DateTimeConverter))]
[Name("date")]
public DateTime CreateDate { get; set; }
public string Name { get; set; }
public object AnotherNull { get; set; }
}
[XmlMapper(ParentNodeName = "Loan",
MappingOperation = XmlMappingOperation.NODE,
IgnoreNulls = false)]
public class Loan
{
[Ignore]
public string ProductCode { get; set; }
[Name("name")]
public string Name { get; set; }
[Name("amount")]
public int Amount { get; set; }
[Name("joint")]
[XmlPropertyConverter(typeof(BooleanConverter))]
public bool IsJointAccount { get; set; }
[Name("applicant")]
public Applicant Applicant { get; set; }
public Application Application { get; set; }
[Name("CustomList")]
[XmlList(NodeName = "ListingItem")]
public List<string> Listing { get; set; }
public object Null { get; set; }
}
[XmlMapper(ParentNodeName = "CustomLoan",
MappingOperation = XmlMappingOperation.ATTRIBUTE,
IgnoreNulls = true,
Formatting = System.Xml.Linq.SaveOptions.None)]
public class CustomLoan : Loan
{
public string CustomFieldA { get; set; }
public string CustomFieldB { get; set; }
[XmlFlattenHierarchy]
public UnMarked FlattenThisProperty { get; set; }
public Dictionary<string, UnMarked> Dictionary { get; set; }
public LinkedList<Applicant> JointApplicants { get; set; }
}
static void Main(string args)
{
List<CustomLoan> loans = new List<CustomLoan>();
for (int i = 0; i < 1000; i++)
{
loans.Add(new CustomLoan()
{
Name = "Revolving Loan",
Amount = 25000,
IsJointAccount = true,
ProductCode = "RLP",
Applicant = new Applicant()
{
FirstName = "George",
LastName = "Bush"
},
Application = new Application()
{
ApplicationID = 1,
CreateDate = new DateTime(2018, 5, 22),
Name = "New Application"
},
Listing = new List<string>() { "string", "another", "text" },
CustomFieldA = "Custom Data For Client Here",
CustomFieldB = "Custom Other Data For Client Here",
Dictionary = new Dictionary<string, UnMarked>()
{
{ "KeyA", new UnMarked(){ UnmarkedFieldA = 20, UnmarkedFieldB = 3.14564 } },
{ "KeyB", new UnMarked(){ UnmarkedFieldA = 77, UnmarkedFieldB = 16.981357 } },
{ "KeyC", new UnMarked(){ UnmarkedFieldA = 86486, UnmarkedFieldB = -1.1384 } }
},
JointApplicants = new LinkedList<Applicant>(new List<Applicant>
{
new Applicant() { FirstName = "Jemma", LastName = "Busher" },
new Applicant() { FirstName = "Harrison", LastName = "Bushmaster" }
})
});
}
var xmlMapper = new XmlMapper<CustomLoan>();
string values = "";
Stopwatch watch = Stopwatch.StartNew();
foreach (CustomLoan loan in loans)
values = xmlMapper.MapToXmlString(loan);
watch.Stop();
Console.WriteLine(watch.ElapsedMilliseconds);
Console.WriteLine(values);
XmlDocument xml = new XmlDocument();
xml.LoadXml(values);
CustomLoan mappedBackObject = xmlMapper.MapToObject(xml);
//Console.WriteLine("Serialization");
//Console.WriteLine(ToXML(loans[0]));
Console.ReadKey();
return;
}
Output
<CustomLoan CustomFieldA="Custom Data For Client Here" CustomFieldB="Custom Other Data For Client Here" name="Revolving Loan" amount="25000" joint="Y">
<Dictionary>
<KeyA UnmarkedFieldA="20" UnmarkedFieldB="3.14564" />
<KeyB UnmarkedFieldA="77" UnmarkedFieldB="16.981357" />
<KeyC UnmarkedFieldA="86486" UnmarkedFieldB="-1.1384" />
</Dictionary>
<JointApplicants>
<Applicant>
<name>Jemma</name>
<surname>Busher</surname>
</Applicant>
<Applicant>
<name>Harrison</name>
<surname>Bushmaster</surname>
</Applicant>
</JointApplicants>
<applicant>
<name>George</name>
<surname>Bush</surname>
</applicant>
<Application>
<ApplicationID>1</ApplicationID>
<date>2018-05-22 00:00:00</date>
<Name>New Application</Name>
</Application>
<CustomList>
<ListingItem>string</ListingItem>
<ListingItem>another</ListingItem>
<ListingItem>text</ListingItem>
</CustomList>
</CustomLoan>
I did a basic performance check and was averaging ~140ms to map 1000 objects of this complexity.
I am having issues simplifying some of the conditions in the ToObject and ToXml methods. I think I can wrap some of these into private methods to simplify things, but I find the logic difficult to follow at times.
You may have noticed that I am not doing any error checking (or checking that the attributes are compatible with the types in question) - I am validating all of this when constructing the MetaDataCache, where I throw exceptions on startup if attributes are improperly configured. However there are probably some spots I should be error checking but I missed.
Any tips on how to simplify the mapping code would be much appreciated.
public class XmlMapper<T> where T : new()
{
private const string XPATH_ROOT_NODE_EXPRESSION = "(/*)";
private Type type = typeof(T);
// this is a hacky way of asking the XmlMapperAttribute what are it's default values in the case that the class does not have this attribute on it
private XmlMapperAttribute xmlMapperAttribute = new XmlMapperAttribute();
public XmlMapper()
{
if (MetaDataCache.Contains<T>())
{
ClassMetaData metaData = MetaDataCache.Get<T>();
if (metaData.ClassAttributeContext.ContainsAttribute<XmlMapperAttribute>())
xmlMapperAttribute = metaData.ClassAttributeContext.GetAttribute<XmlMapperAttribute>();
}
}
public T MapToObject(XmlDocument xml)
{
T instance = new T();
instance = (T)ToObject(instance, xml.SelectSingleNode(XPATH_ROOT_NODE_EXPRESSION));
return instance;
}
public string MapToXmlString(T instance)
{
return ToXmlWrapper(type, instance).ToString(xmlMapperAttribute.Formatting);
}
public XmlDocument MapToXmlDocument(T instance)
{
return ToXmlWrapper(type, instance).ToXmlDocument();
}
private object ToObject(object instance, XmlNode xmlNode, string nodeName = null)
{
System.Type type = instance.GetType();
ClassMetaData classMetaData = MetaDataCache.Get(type);
XmlMappingOperation xmlMappingOperation = xmlMapperAttribute.MappingOperation;
if (classMetaData.ClassAttributeContext.ContainsAttribute<XmlMapperAttribute>())
{
XmlMapperAttribute xmlMapper = classMetaData.ClassAttributeContext.GetAttribute<XmlMapperAttribute>();
xmlMappingOperation = xmlMapper.MappingOperation;
if (nodeName == null)
{
nodeName = xmlMapper.ParentNodeName;
xmlNode = xmlNode.SelectSingleNode($"//{nodeName}");
}
}
foreach (PropertyInfo property in classMetaData.Properties)
{
string propertyName = property.Name;
System.Type propertyType = property.PropertyType;
if (classMetaData.HasPropertyAttributeContext<PropertyAttributeContext>(property))
{
PropertyAttributeContext propertyAttributeContext = classMetaData.GetAttributeContextForProperty<PropertyAttributeContext>(property);
if (propertyAttributeContext.HasIgnoreAttribute)
continue;
propertyName = propertyAttributeContext.HasNameAttribute ? propertyAttributeContext.NameAttribute.Name : propertyName;
}
object propertyValue;
if (xmlMappingOperation.Equals(XmlMappingOperation.NODE))
{
XmlNode propertyNode = xmlNode.SelectSingleNode($"//{nodeName}/{propertyName}");
propertyValue = propertyNode?.InnerText;
}
else // ATTRIBUTE
{
XmlNode propertyNode = xmlNode.SelectSingleNode($"//{nodeName}");
propertyValue = propertyNode.Attributes[propertyName]?.Value;
}
if (classMetaData.HasPropertyAttributeContext<XmlPropertyAttributeContext>(property))
{
XmlPropertyAttributeContext xmlPropertyAttributeContext = classMetaData.GetAttributeContextForProperty<XmlPropertyAttributeContext>(property);
if (xmlPropertyAttributeContext.HasXmlListAttribute)
{
XmlListAttribute xmlList = xmlPropertyAttributeContext.XmlListAttribute;
XmlNodeList results = xmlNode.SelectNodes($"//{nodeName}/{propertyName}/{xmlList.NodeName}");
if (results.Count > 0)
{
object childInstance = CollectionXmlNodeListToObject(results, propertyType);
property.SetValue(instance, childInstance);
}
continue;
}
else if (xmlPropertyAttributeContext.HasXmlDictionaryAttribute)
{
XmlDictionaryAttribute xmlList = xmlPropertyAttributeContext.XmlDictionaryAttribute;
XmlNodeList results = xmlNode.SelectNodes($"//{nodeName}/{propertyName}/*");
if (results.Count > 0)
{
object childInstance = DictionaryXmlNodeListToObject(results, propertyType);
property.SetValue(instance, childInstance);
}
continue;
}
else if (xmlPropertyAttributeContext.HasXmlFlattenHierarchyAttribute)
{
object childInstance = Activator.CreateInstance(propertyType);
XmlNode results = xmlNode.SelectSingleNode($"//{nodeName}/{propertyName}");
if (results != null)
{
childInstance = ToObject(childInstance, results, propertyName);
property.SetValue(instance, childInstance);
}
continue;
}
else if (xmlPropertyAttributeContext.HasXmlPropertyConverterAttribute && propertyValue != null)
{
XmlPropertyConverterAttribute converter = xmlPropertyAttributeContext.XmlPropertyConverterAttribute;
try
{
propertyValue = converter.ConvertToSourceType(propertyValue);
}
catch (Exception ex)
{
throw new XmlPropertyConverterException("XmlPropertyConverter threw an exception", ex);
}
}
}
else
{
if (propertyType.IsDictionary())
{
XmlNodeList results = xmlNode.SelectNodes($"//{nodeName}/{propertyName}/*");
if (results.Count > 0)
{
object childInstance = DictionaryXmlNodeListToObject(results, propertyType);
property.SetValue(instance, childInstance);
}
continue;
}
else if (propertyType.IsCollection())
{
string listItemNodeName = propertyType.GenericTypeArguments[0].Name;
XmlNodeList results = xmlNode.SelectNodes($"//{nodeName}/{propertyName}/{listItemNodeName}");
if (results.Count > 0)
{
object childInstance = CollectionXmlNodeListToObject(results, propertyType);
property.SetValue(instance, childInstance);
}
continue;
}
if (propertyType.IsClass && MetaDataCache.Contains(propertyType))
{
// TODO: Dont think this will work
object childInstance = Activator.CreateInstance(propertyType);
XmlNode results = xmlNode.SelectSingleNode($"//{nodeName}/{propertyName}");
if (results != null)
{
childInstance = ToObject(childInstance, results, propertyName);
property.SetValue(instance, childInstance);
}
continue;
}
}
property.SetValue(instance, UniversalTypeConverter.Convert(propertyValue, propertyType));
}
return instance;
}
private object CollectionXmlNodeListToObject(XmlNodeList nodeList, Type collectionType)
{
object collection = CreateInstanceOfType(collectionType);
Type containedType = collectionType.GetTypeInfo().GenericTypeArguments[0];
Type iCollectionType = typeof(ICollection<>).MakeGenericType(containedType);
foreach (XmlNode node in nodeList)
{
object value = CreateInstanceOfType(containedType);
if (containedType.IsClass && MetaDataCache.Contains(containedType))
value = ToObject(value, node, node.Name);
else
value = node.InnerText;
iCollectionType.GetMethod("Add").Invoke(collection, new { value });
}
return collection;
}
private object DictionaryXmlNodeListToObject(XmlNodeList nodeList, System.Type dictionaryType)
{
object dictionary = Activator.CreateInstance(dictionaryType);
Type keyType = dictionaryType.GetTypeInfo().GenericTypeArguments[0];
Type valueType = dictionaryType.GetTypeInfo().GenericTypeArguments[1];
foreach (XmlNode node in nodeList)
{
object key = node.Name; // will be replaced with dictionary mapper later
object value = CreateInstanceOfType(valueType);
if (valueType.IsClass && MetaDataCache.Contains(valueType))
value = ToObject(value, node, node.Name);
else
value = node.InnerText;
dictionaryType.GetMethod("Add").Invoke(dictionary, new { node.Name, value });
}
return dictionary;
}
private object CreateInstanceOfType(Type type)
{
if (!type.HasDefaultConstructor())
return null;
else
return Activator.CreateInstance(type);
}
private XDocument ToXmlWrapper(Type type, T instance)
{
XElement rootElement = ToXml(instance, type);
XDocument xDocument = new XDocument();
xDocument.Add(rootElement);
// will throw exception if fails to construct XmlDocument
if (xmlMapperAttribute.Validate)
xDocument.ToXmlDocument();
return xDocument;
}
private XElement ToXml(object instance, Type type, XElement element = null, string nodeName = null)
{
ClassMetaData classMetaData = MetaDataCache.Get(type);
XmlMappingOperation xmlMappingOperation = xmlMapperAttribute.MappingOperation;
bool ignoreNulls = xmlMapperAttribute.IgnoreNulls;
if (classMetaData.ClassAttributeContext.ContainsAttribute<XmlMapperAttribute>())
{
XmlMapperAttribute xmlMapper = classMetaData.ClassAttributeContext.GetAttribute<XmlMapperAttribute>();
xmlMappingOperation = xmlMapper.MappingOperation;
ignoreNulls = xmlMapper.IgnoreNulls;
if (element == null)
{
element = new XElement(xmlMapper.ParentNodeName);
nodeName = xmlMapper.ParentNodeName;
}
}
//element.Name = nodeName;
foreach (PropertyInfo property in classMetaData.Properties)
{
string propertyName = property.Name;
object propertyValue = property.GetValue(instance);
System.Type propertyType = property.PropertyType;
if (propertyValue == null && ignoreNulls)
continue;
if (classMetaData.HasPropertyAttributeContext<PropertyAttributeContext>(property))
{
PropertyAttributeContext propertyAttributeContext = classMetaData.GetAttributeContextForProperty<PropertyAttributeContext>(property);
if (propertyAttributeContext.HasIgnoreAttribute)
continue;
propertyName = propertyAttributeContext.HasNameAttribute ? propertyAttributeContext.NameAttribute.Name : propertyName;
}
if (classMetaData.HasPropertyAttributeContext<XmlPropertyAttributeContext>(property))
{
XmlPropertyAttributeContext xmlPropertyAttributeContext = classMetaData.GetAttributeContextForProperty<XmlPropertyAttributeContext>(property);
if (xmlPropertyAttributeContext.HasXmlPropertyConverterAttribute && propertyValue != null)
{
XmlPropertyConverterAttribute converter = xmlPropertyAttributeContext.XmlPropertyConverterAttribute;
try
{
propertyValue = converter.ConvertToDestinationType(propertyValue);
AddToXElement(element, xmlMappingOperation, propertyName, propertyValue);
continue;
}
catch (Exception ex)
{
throw new XmlPropertyConverterException("XmlPropertyConverter threw an exception", ex);
}
}
else if (xmlPropertyAttributeContext.HasXmlDictionaryAttribute)
{
XmlDictionaryAttribute xmlDictionary = xmlPropertyAttributeContext.XmlDictionaryAttribute;
element.Add(DictionaryToXElement(propertyValue, propertyName));
continue;
}
else if (xmlPropertyAttributeContext.HasXmlListAttribute)
{
XmlListAttribute xmlList = xmlPropertyAttributeContext.XmlListAttribute;
element.Add(CollectionToXElement(propertyValue, propertyName, xmlList.NodeName));
continue;
}
else if (xmlPropertyAttributeContext.HasXmlFlattenHierarchyAttribute)
{
element = ToXml(propertyValue, propertyType, element, propertyName);
continue;
}
}
else
{
if (propertyType.IsDictionary())
{
element.Add(DictionaryToXElement(propertyValue, propertyName));
continue;
}
else if (propertyType.IsCollection())
{
element.Add(CollectionToXElement(propertyValue, propertyName, propertyType.GenericTypeArguments[0].Name));
continue;
}
else if (propertyType.IsClass && MetaDataCache.Contains(propertyType))
{
XElement propertyElement = new XElement(propertyName);
propertyElement = ToXml(propertyValue, propertyType, propertyElement, propertyName);
element.Add(propertyElement);
continue;
}
}
AddToXElement(element, xmlMappingOperation, propertyName, propertyValue);
}
return element;
}
private void AddToXElement(XElement element, XmlMappingOperation xmlMappingOperation, string propertyName, object propertyValue)
{
if (xmlMappingOperation.Equals(XmlMappingOperation.ATTRIBUTE))
element.SetAttributeValue(propertyName, propertyValue);
else if (xmlMappingOperation.Equals(XmlMappingOperation.NODE))
element.Add(new XElement(propertyName, propertyValue));
}
private XElement CollectionToXElement(object collection, string parentNodeName, string childNodeName)
{
XElement propertyElement = new XElement(parentNodeName);
Type containedType = collection.GetType().GenericTypeArguments[0];
foreach (var item in (ICollection)collection)
{
XElement itemElement = new XElement(childNodeName);
if (containedType.IsClass && MetaDataCache.Contains(containedType))
itemElement = ToXml(item, item.GetType(), itemElement, childNodeName);
else
itemElement.SetValue(item);
propertyElement.Add(itemElement);
}
return propertyElement;
}
private XElement DictionaryToXElement(object dictionary, string parentNodeName)
{
XElement propertyElement = new XElement(parentNodeName);
Type keyType = dictionary.GetType().GenericTypeArguments[0];
Type valueType = dictionary.GetType().GenericTypeArguments[1];
foreach (DictionaryEntry kvp in (IDictionary)dictionary)
{
// TODO - this will call converter that converts Dictionary key to XElement
XElement itemElement = new XElement(kvp.Key.ToString());
if (valueType.IsClass && MetaDataCache.Contains(valueType))
itemElement = ToXml(kvp.Value, kvp.Value.GetType(), itemElement, itemElement.Name.LocalName);
else
itemElement.SetValue(kvp.Value);
propertyElement.Add(itemElement);
}
return propertyElement;
}
}
.net reflection framework
New contributor
$endgroup$
This code is part of a larger mapping library I'm working on to address some business concerns of transforming data. I was inspired by mapstruct in Java, but opted for users to annotate / add attributes to a poco to control the mapping operation.
Because reflection can be an expensive operation, I opted for scanning all loaded assemblies at run time (and filtering on criteria) and caching the data in memory. Below I am fetching metadata for types by going to the cache and accessing both the common property context and xml property context to check for certain conditions.
Some code has been omitted to keep this focused, but if you're curious the rest of the repo is here: https://github.com/Igneous01/Integration
Sample of how code is exercised
public class BooleanConverter : IXmlPropertyConverter<bool>
{
public bool ConvertToSourceType(string source)
{
if ("y".Equals(source.ToLower()))
return true;
else
return false;
}
public string ConvertToDestinationType(bool source)
{
if (source)
return "Y";
else
return "N";
}
}
public class DateTimeConverter : IXmlPropertyConverter<DateTime>
{
private const string DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
public DateTime ConvertToSourceType(string source)
{
return DateTime.ParseExact(source, DATE_TIME_FORMAT, System.Globalization.CultureInfo.InvariantCulture);
}
public string ConvertToDestinationType(DateTime source)
{
return source.ToString(DATE_TIME_FORMAT);
}
}
[IncludeInScan]
public class UnMarked
{
public int UnmarkedFieldA { get; set; }
public double UnmarkedFieldB { get; set; }
}
[XmlMapper(ParentNodeName = "Applicant", MappingOperation = XmlMappingOperation.NODE)]
public class Applicant
{
[Name("name")]
public string FirstName { get; set; }
[Name("surname")]
public string LastName { get; set; }
}
[XmlMapper(ParentNodeName = "ApplicationNode",
MappingOperation = XmlMappingOperation.NODE,
IgnoreNulls = true)]
public class Application
{
public int ApplicationID { get; set; }
[XmlPropertyConverter(typeof(DateTimeConverter))]
[Name("date")]
public DateTime CreateDate { get; set; }
public string Name { get; set; }
public object AnotherNull { get; set; }
}
[XmlMapper(ParentNodeName = "Loan",
MappingOperation = XmlMappingOperation.NODE,
IgnoreNulls = false)]
public class Loan
{
[Ignore]
public string ProductCode { get; set; }
[Name("name")]
public string Name { get; set; }
[Name("amount")]
public int Amount { get; set; }
[Name("joint")]
[XmlPropertyConverter(typeof(BooleanConverter))]
public bool IsJointAccount { get; set; }
[Name("applicant")]
public Applicant Applicant { get; set; }
public Application Application { get; set; }
[Name("CustomList")]
[XmlList(NodeName = "ListingItem")]
public List<string> Listing { get; set; }
public object Null { get; set; }
}
[XmlMapper(ParentNodeName = "CustomLoan",
MappingOperation = XmlMappingOperation.ATTRIBUTE,
IgnoreNulls = true,
Formatting = System.Xml.Linq.SaveOptions.None)]
public class CustomLoan : Loan
{
public string CustomFieldA { get; set; }
public string CustomFieldB { get; set; }
[XmlFlattenHierarchy]
public UnMarked FlattenThisProperty { get; set; }
public Dictionary<string, UnMarked> Dictionary { get; set; }
public LinkedList<Applicant> JointApplicants { get; set; }
}
static void Main(string args)
{
List<CustomLoan> loans = new List<CustomLoan>();
for (int i = 0; i < 1000; i++)
{
loans.Add(new CustomLoan()
{
Name = "Revolving Loan",
Amount = 25000,
IsJointAccount = true,
ProductCode = "RLP",
Applicant = new Applicant()
{
FirstName = "George",
LastName = "Bush"
},
Application = new Application()
{
ApplicationID = 1,
CreateDate = new DateTime(2018, 5, 22),
Name = "New Application"
},
Listing = new List<string>() { "string", "another", "text" },
CustomFieldA = "Custom Data For Client Here",
CustomFieldB = "Custom Other Data For Client Here",
Dictionary = new Dictionary<string, UnMarked>()
{
{ "KeyA", new UnMarked(){ UnmarkedFieldA = 20, UnmarkedFieldB = 3.14564 } },
{ "KeyB", new UnMarked(){ UnmarkedFieldA = 77, UnmarkedFieldB = 16.981357 } },
{ "KeyC", new UnMarked(){ UnmarkedFieldA = 86486, UnmarkedFieldB = -1.1384 } }
},
JointApplicants = new LinkedList<Applicant>(new List<Applicant>
{
new Applicant() { FirstName = "Jemma", LastName = "Busher" },
new Applicant() { FirstName = "Harrison", LastName = "Bushmaster" }
})
});
}
var xmlMapper = new XmlMapper<CustomLoan>();
string values = "";
Stopwatch watch = Stopwatch.StartNew();
foreach (CustomLoan loan in loans)
values = xmlMapper.MapToXmlString(loan);
watch.Stop();
Console.WriteLine(watch.ElapsedMilliseconds);
Console.WriteLine(values);
XmlDocument xml = new XmlDocument();
xml.LoadXml(values);
CustomLoan mappedBackObject = xmlMapper.MapToObject(xml);
//Console.WriteLine("Serialization");
//Console.WriteLine(ToXML(loans[0]));
Console.ReadKey();
return;
}
Output
<CustomLoan CustomFieldA="Custom Data For Client Here" CustomFieldB="Custom Other Data For Client Here" name="Revolving Loan" amount="25000" joint="Y">
<Dictionary>
<KeyA UnmarkedFieldA="20" UnmarkedFieldB="3.14564" />
<KeyB UnmarkedFieldA="77" UnmarkedFieldB="16.981357" />
<KeyC UnmarkedFieldA="86486" UnmarkedFieldB="-1.1384" />
</Dictionary>
<JointApplicants>
<Applicant>
<name>Jemma</name>
<surname>Busher</surname>
</Applicant>
<Applicant>
<name>Harrison</name>
<surname>Bushmaster</surname>
</Applicant>
</JointApplicants>
<applicant>
<name>George</name>
<surname>Bush</surname>
</applicant>
<Application>
<ApplicationID>1</ApplicationID>
<date>2018-05-22 00:00:00</date>
<Name>New Application</Name>
</Application>
<CustomList>
<ListingItem>string</ListingItem>
<ListingItem>another</ListingItem>
<ListingItem>text</ListingItem>
</CustomList>
</CustomLoan>
I did a basic performance check and was averaging ~140ms to map 1000 objects of this complexity.
I am having issues simplifying some of the conditions in the ToObject and ToXml methods. I think I can wrap some of these into private methods to simplify things, but I find the logic difficult to follow at times.
You may have noticed that I am not doing any error checking (or checking that the attributes are compatible with the types in question) - I am validating all of this when constructing the MetaDataCache, where I throw exceptions on startup if attributes are improperly configured. However there are probably some spots I should be error checking but I missed.
Any tips on how to simplify the mapping code would be much appreciated.
public class XmlMapper<T> where T : new()
{
private const string XPATH_ROOT_NODE_EXPRESSION = "(/*)";
private Type type = typeof(T);
// this is a hacky way of asking the XmlMapperAttribute what are it's default values in the case that the class does not have this attribute on it
private XmlMapperAttribute xmlMapperAttribute = new XmlMapperAttribute();
public XmlMapper()
{
if (MetaDataCache.Contains<T>())
{
ClassMetaData metaData = MetaDataCache.Get<T>();
if (metaData.ClassAttributeContext.ContainsAttribute<XmlMapperAttribute>())
xmlMapperAttribute = metaData.ClassAttributeContext.GetAttribute<XmlMapperAttribute>();
}
}
public T MapToObject(XmlDocument xml)
{
T instance = new T();
instance = (T)ToObject(instance, xml.SelectSingleNode(XPATH_ROOT_NODE_EXPRESSION));
return instance;
}
public string MapToXmlString(T instance)
{
return ToXmlWrapper(type, instance).ToString(xmlMapperAttribute.Formatting);
}
public XmlDocument MapToXmlDocument(T instance)
{
return ToXmlWrapper(type, instance).ToXmlDocument();
}
private object ToObject(object instance, XmlNode xmlNode, string nodeName = null)
{
System.Type type = instance.GetType();
ClassMetaData classMetaData = MetaDataCache.Get(type);
XmlMappingOperation xmlMappingOperation = xmlMapperAttribute.MappingOperation;
if (classMetaData.ClassAttributeContext.ContainsAttribute<XmlMapperAttribute>())
{
XmlMapperAttribute xmlMapper = classMetaData.ClassAttributeContext.GetAttribute<XmlMapperAttribute>();
xmlMappingOperation = xmlMapper.MappingOperation;
if (nodeName == null)
{
nodeName = xmlMapper.ParentNodeName;
xmlNode = xmlNode.SelectSingleNode($"//{nodeName}");
}
}
foreach (PropertyInfo property in classMetaData.Properties)
{
string propertyName = property.Name;
System.Type propertyType = property.PropertyType;
if (classMetaData.HasPropertyAttributeContext<PropertyAttributeContext>(property))
{
PropertyAttributeContext propertyAttributeContext = classMetaData.GetAttributeContextForProperty<PropertyAttributeContext>(property);
if (propertyAttributeContext.HasIgnoreAttribute)
continue;
propertyName = propertyAttributeContext.HasNameAttribute ? propertyAttributeContext.NameAttribute.Name : propertyName;
}
object propertyValue;
if (xmlMappingOperation.Equals(XmlMappingOperation.NODE))
{
XmlNode propertyNode = xmlNode.SelectSingleNode($"//{nodeName}/{propertyName}");
propertyValue = propertyNode?.InnerText;
}
else // ATTRIBUTE
{
XmlNode propertyNode = xmlNode.SelectSingleNode($"//{nodeName}");
propertyValue = propertyNode.Attributes[propertyName]?.Value;
}
if (classMetaData.HasPropertyAttributeContext<XmlPropertyAttributeContext>(property))
{
XmlPropertyAttributeContext xmlPropertyAttributeContext = classMetaData.GetAttributeContextForProperty<XmlPropertyAttributeContext>(property);
if (xmlPropertyAttributeContext.HasXmlListAttribute)
{
XmlListAttribute xmlList = xmlPropertyAttributeContext.XmlListAttribute;
XmlNodeList results = xmlNode.SelectNodes($"//{nodeName}/{propertyName}/{xmlList.NodeName}");
if (results.Count > 0)
{
object childInstance = CollectionXmlNodeListToObject(results, propertyType);
property.SetValue(instance, childInstance);
}
continue;
}
else if (xmlPropertyAttributeContext.HasXmlDictionaryAttribute)
{
XmlDictionaryAttribute xmlList = xmlPropertyAttributeContext.XmlDictionaryAttribute;
XmlNodeList results = xmlNode.SelectNodes($"//{nodeName}/{propertyName}/*");
if (results.Count > 0)
{
object childInstance = DictionaryXmlNodeListToObject(results, propertyType);
property.SetValue(instance, childInstance);
}
continue;
}
else if (xmlPropertyAttributeContext.HasXmlFlattenHierarchyAttribute)
{
object childInstance = Activator.CreateInstance(propertyType);
XmlNode results = xmlNode.SelectSingleNode($"//{nodeName}/{propertyName}");
if (results != null)
{
childInstance = ToObject(childInstance, results, propertyName);
property.SetValue(instance, childInstance);
}
continue;
}
else if (xmlPropertyAttributeContext.HasXmlPropertyConverterAttribute && propertyValue != null)
{
XmlPropertyConverterAttribute converter = xmlPropertyAttributeContext.XmlPropertyConverterAttribute;
try
{
propertyValue = converter.ConvertToSourceType(propertyValue);
}
catch (Exception ex)
{
throw new XmlPropertyConverterException("XmlPropertyConverter threw an exception", ex);
}
}
}
else
{
if (propertyType.IsDictionary())
{
XmlNodeList results = xmlNode.SelectNodes($"//{nodeName}/{propertyName}/*");
if (results.Count > 0)
{
object childInstance = DictionaryXmlNodeListToObject(results, propertyType);
property.SetValue(instance, childInstance);
}
continue;
}
else if (propertyType.IsCollection())
{
string listItemNodeName = propertyType.GenericTypeArguments[0].Name;
XmlNodeList results = xmlNode.SelectNodes($"//{nodeName}/{propertyName}/{listItemNodeName}");
if (results.Count > 0)
{
object childInstance = CollectionXmlNodeListToObject(results, propertyType);
property.SetValue(instance, childInstance);
}
continue;
}
if (propertyType.IsClass && MetaDataCache.Contains(propertyType))
{
// TODO: Dont think this will work
object childInstance = Activator.CreateInstance(propertyType);
XmlNode results = xmlNode.SelectSingleNode($"//{nodeName}/{propertyName}");
if (results != null)
{
childInstance = ToObject(childInstance, results, propertyName);
property.SetValue(instance, childInstance);
}
continue;
}
}
property.SetValue(instance, UniversalTypeConverter.Convert(propertyValue, propertyType));
}
return instance;
}
private object CollectionXmlNodeListToObject(XmlNodeList nodeList, Type collectionType)
{
object collection = CreateInstanceOfType(collectionType);
Type containedType = collectionType.GetTypeInfo().GenericTypeArguments[0];
Type iCollectionType = typeof(ICollection<>).MakeGenericType(containedType);
foreach (XmlNode node in nodeList)
{
object value = CreateInstanceOfType(containedType);
if (containedType.IsClass && MetaDataCache.Contains(containedType))
value = ToObject(value, node, node.Name);
else
value = node.InnerText;
iCollectionType.GetMethod("Add").Invoke(collection, new { value });
}
return collection;
}
private object DictionaryXmlNodeListToObject(XmlNodeList nodeList, System.Type dictionaryType)
{
object dictionary = Activator.CreateInstance(dictionaryType);
Type keyType = dictionaryType.GetTypeInfo().GenericTypeArguments[0];
Type valueType = dictionaryType.GetTypeInfo().GenericTypeArguments[1];
foreach (XmlNode node in nodeList)
{
object key = node.Name; // will be replaced with dictionary mapper later
object value = CreateInstanceOfType(valueType);
if (valueType.IsClass && MetaDataCache.Contains(valueType))
value = ToObject(value, node, node.Name);
else
value = node.InnerText;
dictionaryType.GetMethod("Add").Invoke(dictionary, new { node.Name, value });
}
return dictionary;
}
private object CreateInstanceOfType(Type type)
{
if (!type.HasDefaultConstructor())
return null;
else
return Activator.CreateInstance(type);
}
private XDocument ToXmlWrapper(Type type, T instance)
{
XElement rootElement = ToXml(instance, type);
XDocument xDocument = new XDocument();
xDocument.Add(rootElement);
// will throw exception if fails to construct XmlDocument
if (xmlMapperAttribute.Validate)
xDocument.ToXmlDocument();
return xDocument;
}
private XElement ToXml(object instance, Type type, XElement element = null, string nodeName = null)
{
ClassMetaData classMetaData = MetaDataCache.Get(type);
XmlMappingOperation xmlMappingOperation = xmlMapperAttribute.MappingOperation;
bool ignoreNulls = xmlMapperAttribute.IgnoreNulls;
if (classMetaData.ClassAttributeContext.ContainsAttribute<XmlMapperAttribute>())
{
XmlMapperAttribute xmlMapper = classMetaData.ClassAttributeContext.GetAttribute<XmlMapperAttribute>();
xmlMappingOperation = xmlMapper.MappingOperation;
ignoreNulls = xmlMapper.IgnoreNulls;
if (element == null)
{
element = new XElement(xmlMapper.ParentNodeName);
nodeName = xmlMapper.ParentNodeName;
}
}
//element.Name = nodeName;
foreach (PropertyInfo property in classMetaData.Properties)
{
string propertyName = property.Name;
object propertyValue = property.GetValue(instance);
System.Type propertyType = property.PropertyType;
if (propertyValue == null && ignoreNulls)
continue;
if (classMetaData.HasPropertyAttributeContext<PropertyAttributeContext>(property))
{
PropertyAttributeContext propertyAttributeContext = classMetaData.GetAttributeContextForProperty<PropertyAttributeContext>(property);
if (propertyAttributeContext.HasIgnoreAttribute)
continue;
propertyName = propertyAttributeContext.HasNameAttribute ? propertyAttributeContext.NameAttribute.Name : propertyName;
}
if (classMetaData.HasPropertyAttributeContext<XmlPropertyAttributeContext>(property))
{
XmlPropertyAttributeContext xmlPropertyAttributeContext = classMetaData.GetAttributeContextForProperty<XmlPropertyAttributeContext>(property);
if (xmlPropertyAttributeContext.HasXmlPropertyConverterAttribute && propertyValue != null)
{
XmlPropertyConverterAttribute converter = xmlPropertyAttributeContext.XmlPropertyConverterAttribute;
try
{
propertyValue = converter.ConvertToDestinationType(propertyValue);
AddToXElement(element, xmlMappingOperation, propertyName, propertyValue);
continue;
}
catch (Exception ex)
{
throw new XmlPropertyConverterException("XmlPropertyConverter threw an exception", ex);
}
}
else if (xmlPropertyAttributeContext.HasXmlDictionaryAttribute)
{
XmlDictionaryAttribute xmlDictionary = xmlPropertyAttributeContext.XmlDictionaryAttribute;
element.Add(DictionaryToXElement(propertyValue, propertyName));
continue;
}
else if (xmlPropertyAttributeContext.HasXmlListAttribute)
{
XmlListAttribute xmlList = xmlPropertyAttributeContext.XmlListAttribute;
element.Add(CollectionToXElement(propertyValue, propertyName, xmlList.NodeName));
continue;
}
else if (xmlPropertyAttributeContext.HasXmlFlattenHierarchyAttribute)
{
element = ToXml(propertyValue, propertyType, element, propertyName);
continue;
}
}
else
{
if (propertyType.IsDictionary())
{
element.Add(DictionaryToXElement(propertyValue, propertyName));
continue;
}
else if (propertyType.IsCollection())
{
element.Add(CollectionToXElement(propertyValue, propertyName, propertyType.GenericTypeArguments[0].Name));
continue;
}
else if (propertyType.IsClass && MetaDataCache.Contains(propertyType))
{
XElement propertyElement = new XElement(propertyName);
propertyElement = ToXml(propertyValue, propertyType, propertyElement, propertyName);
element.Add(propertyElement);
continue;
}
}
AddToXElement(element, xmlMappingOperation, propertyName, propertyValue);
}
return element;
}
private void AddToXElement(XElement element, XmlMappingOperation xmlMappingOperation, string propertyName, object propertyValue)
{
if (xmlMappingOperation.Equals(XmlMappingOperation.ATTRIBUTE))
element.SetAttributeValue(propertyName, propertyValue);
else if (xmlMappingOperation.Equals(XmlMappingOperation.NODE))
element.Add(new XElement(propertyName, propertyValue));
}
private XElement CollectionToXElement(object collection, string parentNodeName, string childNodeName)
{
XElement propertyElement = new XElement(parentNodeName);
Type containedType = collection.GetType().GenericTypeArguments[0];
foreach (var item in (ICollection)collection)
{
XElement itemElement = new XElement(childNodeName);
if (containedType.IsClass && MetaDataCache.Contains(containedType))
itemElement = ToXml(item, item.GetType(), itemElement, childNodeName);
else
itemElement.SetValue(item);
propertyElement.Add(itemElement);
}
return propertyElement;
}
private XElement DictionaryToXElement(object dictionary, string parentNodeName)
{
XElement propertyElement = new XElement(parentNodeName);
Type keyType = dictionary.GetType().GenericTypeArguments[0];
Type valueType = dictionary.GetType().GenericTypeArguments[1];
foreach (DictionaryEntry kvp in (IDictionary)dictionary)
{
// TODO - this will call converter that converts Dictionary key to XElement
XElement itemElement = new XElement(kvp.Key.ToString());
if (valueType.IsClass && MetaDataCache.Contains(valueType))
itemElement = ToXml(kvp.Value, kvp.Value.GetType(), itemElement, itemElement.Name.LocalName);
else
itemElement.SetValue(kvp.Value);
propertyElement.Add(itemElement);
}
return propertyElement;
}
}
.net reflection framework
.net reflection framework
New contributor
New contributor
New contributor
asked 6 mins ago
Igneous01Igneous01
101
101
New contributor
New contributor
add a comment |
add a comment |
0
active
oldest
votes
Your Answer
StackExchange.ifUsing("editor", function () {
return StackExchange.using("mathjaxEditing", function () {
StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix) {
StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
});
});
}, "mathjax-editing");
StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");
StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "196"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});
function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: false,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: null,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});
}
});
Igneous01 is a new contributor. Be nice, and check out our Code of Conduct.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f213010%2fgeneric-object-to-xml-mapper%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
0
active
oldest
votes
0
active
oldest
votes
active
oldest
votes
active
oldest
votes
Igneous01 is a new contributor. Be nice, and check out our Code of Conduct.
Igneous01 is a new contributor. Be nice, and check out our Code of Conduct.
Igneous01 is a new contributor. Be nice, and check out our Code of Conduct.
Igneous01 is a new contributor. Be nice, and check out our Code of Conduct.
Thanks for contributing an answer to Code Review Stack Exchange!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
Use MathJax to format equations. MathJax reference.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f213010%2fgeneric-object-to-xml-mapper%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown