Generic Object to Xml Mapper












0












$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;
}
}








share







New contributor




Igneous01 is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.







$endgroup$

















    0












    $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;
    }
    }








    share







    New contributor




    Igneous01 is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
    Check out our Code of Conduct.







    $endgroup$















      0












      0








      0





      $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;
      }
      }








      share







      New contributor




      Igneous01 is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
      Check out our Code of Conduct.







      $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





      share







      New contributor




      Igneous01 is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
      Check out our Code of Conduct.










      share







      New contributor




      Igneous01 is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
      Check out our Code of Conduct.








      share



      share






      New contributor




      Igneous01 is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
      Check out our Code of Conduct.









      asked 6 mins ago









      Igneous01Igneous01

      101




      101




      New contributor




      Igneous01 is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
      Check out our Code of Conduct.





      New contributor





      Igneous01 is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
      Check out our Code of Conduct.






      Igneous01 is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
      Check out our Code of Conduct.






















          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.










          draft saved

          draft discarded


















          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.










          draft saved

          draft discarded


















          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.




          draft saved


          draft discarded














          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





















































          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







          Popular posts from this blog

          404 Error Contact Form 7 ajax form submitting

          How to know if a Active Directory user can login interactively

          Refactoring coordinates for Minecraft Pi buildings written in Python