Last active
January 29, 2016 16:13
-
-
Save esskar/5358547 to your computer and use it in GitHub Desktop.
Objects to XML and back to Dynamics
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
internal class CustomDynamicObject : DynamicObject | |
{ | |
private readonly IDictionary<string, object> _values; | |
public CustomDynamicObject() | |
{ | |
_values = new Dictionary<string, object>(); | |
} | |
public virtual bool HasDynamicMember(string name) | |
{ | |
return _values.ContainsKey(name); | |
} | |
internal object this[string name] | |
{ | |
get { return _values[name]; } | |
set { _values[name] = value; } | |
} | |
public override bool TryGetMember(GetMemberBinder binder, out object result) | |
{ | |
return _values.TryGetValue(binder.Name, out result); | |
} | |
public override bool TrySetMember(SetMemberBinder binder, object value) | |
{ | |
_values[binder.Name] = value; | |
return true; | |
} | |
public override IEnumerable<string> GetDynamicMemberNames() | |
{ | |
return _values.Keys; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class ObjectXmlSerializer | |
{ | |
private readonly List<PropertyInfo> _ignoreProperties; | |
private const string AttributeNameType = "type"; | |
private const string AttributeNameClassType = "classType"; | |
private const string AttributeNameIsNullable = "isNullable"; | |
private const string AttributeNameIsArray = "isArray"; | |
private const string AttributeNameIsList = "isList"; | |
private const string AttributeNameIsDictionary = "isDictionary"; | |
public ObjectXmlSerializer() | |
{ | |
_ignoreProperties = new List<PropertyInfo>(); | |
} | |
public void AddPropertyToIgnoreList(PropertyInfo property) | |
{ | |
Assert.IfNull(property, "property"); | |
_ignoreProperties.Add(property); | |
} | |
public void AddPropertyExpressionToIgnoreList<TIn, TOut>(Expression<Func<TIn, TOut>> expression) | |
{ | |
Assert.IfNull(expression, "expression"); | |
var memberExpression = expression.Body as MemberExpression; | |
if (memberExpression == null) | |
throw new ArgumentException("Expression body is not a MemberExpression."); | |
var property = memberExpression.Member as PropertyInfo; | |
if (property == null) | |
throw new ArgumentException("MemberExpression is not a PropertyExpression."); | |
this.AddPropertyToIgnoreList(property); | |
} | |
public void AddPropertyExpressionsToIgnoreList<TIn, TOut>(params Expression<Func<TIn, TOut>>[] expressions) | |
{ | |
foreach (var expression in expressions) | |
this.AddPropertyExpressionToIgnoreList(expression); | |
} | |
public bool SerializeEnumAsString { get; set; } | |
public bool SerializeCharAsString { get; set; } | |
public XElement Serialize(object @object) | |
{ | |
return this.Serialize(@object, null); | |
} | |
public dynamic Deserialize(string xmlString) | |
{ | |
return this.Deserialize(XDocument.Parse(xmlString).Root); | |
} | |
public dynamic Deserialize(XElement element) | |
{ | |
if (element == null) | |
return null; | |
var result = this.DeserializeElement(element); | |
if (result != null) | |
return result; | |
var classTypeAttr = element.Attributes() | |
.FirstOrDefault(a => a.Name.ToString().Equals(ObjectXmlSerializer.AttributeNameClassType)); | |
var dynamic = new CustomDynamicObject(); | |
dynamic["XmlNodeName"] = element.Name.ToString(); | |
dynamic["XmlNodeClassType"] = classTypeAttr != null ? FindType(classTypeAttr.Value, false) : null; | |
foreach (var childElement in element.Nodes().OfType<XElement>()) | |
{ | |
dynamic[childElement.Name.ToString()] = this.DeserializeElement(childElement) | |
?? this.Deserialize(childElement); | |
} | |
return dynamic; | |
} | |
private dynamic DeserializeArrayElement(XElement element) | |
{ | |
if (!element.HasAttributeWithValue(AttributeNameIsArray, "true") && !element.HasAttributeWithValue(AttributeNameIsList, "true")) | |
return null; | |
if (element.HasAttributeWithValue(AttributeNameType, this.GetElementTypeFromType(typeof(byte)))) | |
return Convert.FromBase64String(element.Value); | |
return element.Nodes().OfType<XElement>().Select(childElement => this.Deserialize(childElement)).ToArray(); | |
} | |
private static Type FindType(string stringType, bool throwOnError = true) | |
{ | |
var type = Type.GetType(stringType); | |
if (type != null) | |
return type; | |
var comma = stringType.IndexOf(",", System.StringComparison.Ordinal); | |
if (comma > -1) | |
{ | |
if (TypeResolver.Instance.Lookup(stringType.Substring(0, comma).Split('.').Last(), out type)) | |
return type; | |
} | |
if(throwOnError) | |
throw new TypeAccessException(string.Format("Failed to access type from string '{0}'", stringType)); | |
return null; | |
} | |
private dynamic DeserializeElement(XElement element) | |
{ | |
var result = this.DeserializeArrayElement(element); | |
if (result != null) | |
return result; | |
var typeAttr = element.Attributes() | |
.FirstOrDefault(a => a.Name.ToString().Equals(ObjectXmlSerializer.AttributeNameType)); | |
if (typeAttr == null) | |
return null; | |
var isNullable = element.Attributes() | |
.Any(a => a.Name.ToString().Equals(ObjectXmlSerializer.AttributeNameIsNullable) && a.Value.Equals("true")); | |
var type = FindType(typeAttr.Value); | |
var value = StringConverter.FromString(element.Value, type); | |
if (!isNullable) | |
return value; | |
var nullableType = typeof(Nullable<>).MakeGenericType(type); | |
return Activator.CreateInstance(nullableType, value); | |
} | |
private static string GetElementNameFromTypeAnalyser(TypeAnalyser analyser) | |
{ | |
return analyser.IsAnonymous | |
? "AnonymousObject" | |
: analyser.IsArray | |
? analyser.Type.GetElementType().Name + "Array" | |
: analyser.IsList | |
? analyser.Type.GetElementType().Name + "List" | |
: analyser.Type.Name; | |
} | |
private string GetElementTypeFromType(Type type) | |
{ | |
return this.GetElementTypeFromTypeAnalyser(new TypeAnalyser(type)); | |
} | |
private string GetElementTypeFromTypeAnalyser(TypeAnalyser analyser) | |
{ | |
if (analyser.IsArray || analyser.IsList) | |
return this.GetElementTypeFromType(analyser.Type.GetElementType()) + "[]"; | |
return analyser.IsAnonymous ? "AnonymousType" : analyser.Type.Serialize(); | |
} | |
protected virtual bool IsIgnoreProperty(PropertyInfo property) | |
{ | |
return _ignoreProperties.Contains(property) | |
|| Attribute.IsDefined(property, typeof(XmlIgnoreAttribute)) | |
|| Attribute.IsDefined(property, typeof(IgnoreAttribute)); | |
} | |
private XElement Serialize(object @object, string elementName, bool isNullable = false) | |
{ | |
if (@object == null) | |
return null; | |
var analyser = new TypeAnalyser(@object.GetType()); | |
if (string.IsNullOrWhiteSpace(elementName)) | |
elementName = GetElementNameFromTypeAnalyser(analyser); | |
if (analyser.IsSimpleType || analyser.IsEnum) | |
{ | |
var elements = new ArrayList {new XAttribute(AttributeNameType, this.GetElementTypeFromTypeAnalyser(analyser))}; | |
if(isNullable) | |
elements.Add(new XAttribute(ObjectXmlSerializer.AttributeNameIsNullable, true)); | |
var serializeAsString = (!analyser.IsEnum || this.SerializeEnumAsString) | |
&& (!analyser.IsChar || this.SerializeCharAsString); | |
if (serializeAsString) | |
{ | |
var stringValue = StringConverter.ToString(@object); | |
if(analyser.IsCDataType) | |
elements.Add(new XCData(stringValue)); | |
else | |
elements.Add(stringValue); | |
} | |
else | |
{ | |
elements.Add(Convert.ToInt64(@object)); | |
} | |
return new XElement(elementName, elements.ToArray()); | |
} | |
if (analyser.IsArray) | |
return this.SerializeArrayElement(elementName, @object as Array); | |
if (analyser.IsList) | |
return this.SerializeListElement(elementName, @object as IList); | |
if(analyser.IsDictionary) | |
return this.SerializeDictionaryElement(elementName, @object as IDictionary); | |
var element = new XElement(XmlConvert.EncodeName(elementName), new XAttribute(AttributeNameClassType, this.GetElementTypeFromTypeAnalyser(analyser))); | |
foreach (var property in TypePropertyCache.Get(@object.GetType()).Where(property => !IsIgnoreProperty(property))) | |
element.Add(this.SerializeElement(property, @object)); | |
return element; | |
} | |
private XElement SerializeElement(PropertyInfo property, object @object) | |
{ | |
var value = property.GetValue(@object, null); | |
return this.Serialize( | |
value, | |
property.Name, | |
TypeAnalyser.Analyse(property.PropertyType).IsNullable); | |
} | |
private XElement SerializeDictionaryElement(string elementName, IDictionary dictionary) | |
{ | |
var rootElement = new XElement(XmlConvert.EncodeName(elementName), | |
new XAttribute(ObjectXmlSerializer.AttributeNameIsDictionary, true)); | |
var iter = dictionary.GetEnumerator(); | |
while (iter.MoveNext()) | |
{ | |
// TODO | |
} | |
return rootElement; | |
} | |
private XElement SerializeEnumerableElement(XElement rootElement, IEnumerable list) | |
{ | |
foreach (var value in list) | |
{ | |
var analyser = new TypeAnalyser(value.GetType()); | |
var childElement = analyser.IsSimpleType | |
? this.Serialize(value, rootElement.Name + "Child") | |
: this.Serialize(value); | |
rootElement.Add(childElement); | |
} | |
return rootElement; | |
} | |
private XElement SerializeListElement(string elementName, IEnumerable list) | |
{ | |
var rootElement = new XElement(XmlConvert.EncodeName(elementName), | |
new XAttribute(ObjectXmlSerializer.AttributeNameIsList, true)); | |
return this.SerializeEnumerableElement(rootElement, list); | |
} | |
private XElement SerializeArrayElement(string elementName, Array array) | |
{ | |
var rootElement = new XElement(XmlConvert.EncodeName(elementName), | |
new XAttribute(ObjectXmlSerializer.AttributeNameIsArray, true)); | |
if (array.Length > 0 && array.GetValue(0) is byte) | |
{ | |
rootElement.Add(new XAttribute(AttributeNameType, this.GetElementTypeFromType(typeof(byte)))); | |
rootElement.SetValue(Convert.ToBase64String((byte[])array)); | |
return rootElement; | |
} | |
return this.SerializeEnumerableElement(rootElement, array); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public static class StringConverter | |
{ | |
private static readonly IDictionary<Type, Func<string, object>> __parsers = new Dictionary<Type, Func<string, object>> | |
{ | |
{ typeof(byte), s => byte.Parse(s, NumberStyles.Any, CultureInfo.InvariantCulture) }, | |
{ typeof(sbyte), s => sbyte.Parse(s, NumberStyles.Any, CultureInfo.InvariantCulture) }, | |
{ typeof(short), s => short.Parse(s, NumberStyles.Any, CultureInfo.InvariantCulture) }, | |
{ typeof(ushort), s => ushort.Parse(s, NumberStyles.Any, CultureInfo.InvariantCulture) }, | |
{ typeof(int), s => int.Parse(s, NumberStyles.Any, CultureInfo.InvariantCulture) }, | |
{ typeof(uint), s => uint.Parse(s, NumberStyles.Any, CultureInfo.InvariantCulture) }, | |
{ typeof(long), s => long.Parse(s, NumberStyles.Any, CultureInfo.InvariantCulture) }, | |
{ typeof(ulong), s => ulong.Parse(s, NumberStyles.Any, CultureInfo.InvariantCulture) }, | |
{ typeof(double), s => double.Parse(s, NumberStyles.Any, CultureInfo.InvariantCulture) }, | |
{ typeof(float), s => float.Parse(s, NumberStyles.Any, CultureInfo.InvariantCulture) }, | |
{ typeof(decimal), s => decimal.Parse(s, NumberStyles.Any, CultureInfo.InvariantCulture) }, | |
{ typeof(DateTime), s => { | |
var dt = DateTime.Parse(s, CultureInfo.InvariantCulture, DateTimeStyles.None); | |
if (dt.Kind == DateTimeKind.Unspecified) | |
dt = DateTime.SpecifyKind(dt, DateTimeKind.Utc); | |
else if (dt.Kind == DateTimeKind.Local) | |
dt = dt.ToUniversalTime(); | |
return dt; | |
} }, | |
{ typeof(TimeSpan), s => TimeSpan.Parse(s, CultureInfo.InvariantCulture) }, | |
{ typeof(bool), s => bool.Parse(s) }, | |
{ typeof(Guid), s => Guid.Parse(s) }, | |
{ typeof(char), s => Convert.ToChar(Convert.ToInt64(s)) }, | |
}; | |
public static object FromString(string value, Type type) | |
{ | |
if (value == null) | |
return null; | |
Assert.IfNull(type, "type"); | |
if (type == typeof(string)) | |
return value; | |
if (type.IsEnum) | |
{ | |
try | |
{ | |
return Enum.Parse(type, value); | |
} | |
catch (Exception ex) | |
{ | |
var values = Enum.GetValues(type); | |
if(values.Length <= 0) | |
throw new InvalidOperationException("Unable to convert string to enum type " + type, ex); | |
return values.GetValue(0); | |
} | |
} | |
Func<string, object> parser; | |
if (!__parsers.TryGetValue(type, out parser)) | |
throw new InvalidOperationException("Unable to convert string to " + type); | |
return parser(value); | |
} | |
public static string ToString(object value) | |
{ | |
if (value is DateTime) | |
return ((DateTime)value).ToString("o", CultureInfo.InvariantCulture); | |
return Convert.ToString(value, CultureInfo.InvariantCulture); | |
} | |
public static string ToString(XElement xml) | |
{ | |
return ToString(xml, SaveOptions.None); | |
} | |
public static string ToString(XElement xml, SaveOptions saveOptions) | |
{ | |
return xml != null ? xml.ToString(saveOptions) : null; | |
} | |
public static XElement ToXml(string value) | |
{ | |
return !string.IsNullOrWhiteSpace(value) ? XDocument.Parse(value).Root : null; | |
} | |
private static readonly char[] __forBase36 = "0123456789abcdefghijklmnopqrstuvwxyz".ToCharArray(); | |
public static string ToBase36String(long value) | |
{ | |
if (value < 0) | |
throw new ArgumentOutOfRangeException("value", value, "Value cannot be negative."); | |
var result = new Stack<char>(); | |
while (value != 0) | |
{ | |
result.Push(__forBase36[value % 36]); | |
value /= 36; | |
} | |
return new string(result.ToArray()); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class TypeAnalyser | |
{ | |
private readonly Type _type; | |
public static TypeAnalyser Analyse(Type type) | |
{ | |
return new TypeAnalyser(type); | |
} | |
public TypeAnalyser(Type type) | |
{ | |
Assert.IfNull(type, "type"); | |
_type = type; | |
} | |
public Type Type | |
{ | |
get { return _type; } | |
} | |
public Type GetElementType() | |
{ | |
if (this.Type == typeof(object)) | |
return null; | |
if (this.Type.HasElementType) | |
return this.Type.GetElementType(); | |
var genericTypes = this.Type.GetGenericArguments(); | |
if (genericTypes.Length == 1) | |
return genericTypes[0]; | |
var baseAnalyer = new TypeAnalyser(this.Type.BaseType); | |
return baseAnalyer.GetElementType(); | |
} | |
public bool IsAnonymous | |
{ | |
get | |
{ | |
return _type.GetCustomAttributes(typeof(CompilerGeneratedAttribute), false).Any() | |
&& _type.FullName != null | |
&& _type.FullName.Contains("AnonymousType"); | |
} | |
} | |
public bool IsArray | |
{ | |
get { return _type.IsArray; } | |
} | |
public bool IsChildType | |
{ | |
get | |
{ | |
return !this.IsSimpleType | |
&& (this.IsClass || this.IsArray || this.IsDictionary || this.IsList || this.IsStruct); | |
} | |
} | |
public bool IsString | |
{ | |
get { return _type == typeof (string); } | |
} | |
public bool IsClass | |
{ | |
get { return _type.IsClass; } | |
} | |
public bool IsInterface | |
{ | |
get { return _type.IsInterface; } | |
} | |
public bool IsDictionary | |
{ | |
get { return typeof(IDictionary).IsAssignableFrom(_type); } | |
} | |
public bool IsEnum | |
{ | |
get { return _type.IsEnum; } | |
} | |
public bool IsChar | |
{ | |
get { return _type == typeof (char); } | |
} | |
public bool IsList | |
{ | |
get { return typeof(IList).IsAssignableFrom(_type); } | |
} | |
public bool IsIndexedCollection | |
{ | |
get { return this.IsArray || this.IsList; } | |
} | |
public bool IsPointer | |
{ | |
get { return _type == typeof(IntPtr) || _type == typeof(UIntPtr); } | |
} | |
public bool IsComparableType | |
{ | |
get | |
{ | |
return this.IsSimpleType || typeof(IComparable).IsAssignableFrom(_type); | |
} | |
} | |
public bool IsSimpleType | |
{ | |
get | |
{ | |
var type = this.IsNullable ? Nullable.GetUnderlyingType(_type) : _type; | |
return type.IsPrimitive | |
|| type == typeof(DateTime) | |
|| type == typeof(TimeSpan) | |
|| type == typeof(decimal) | |
|| type == typeof(string) | |
|| type == typeof(Guid); | |
} | |
} | |
public bool IsNullable | |
{ | |
get { return _type.IsGenericType && _type.GetGenericTypeDefinition() == typeof (Nullable<>); } | |
} | |
public bool IsStruct | |
{ | |
get { return _type.IsValueType && !this.IsSimpleType; } | |
} | |
public bool IsTimeSpan | |
{ | |
get { return _type == typeof(TimeSpan); } | |
} | |
public bool IsValidStructSubType | |
{ | |
get | |
{ | |
if (this.IsNullable) | |
{ | |
var underlyingType = Nullable.GetUnderlyingType(this.Type); | |
return Analyse(underlyingType).IsValidStructSubType; | |
} | |
return this.IsSimpleType | |
|| this.IsEnum | |
|| this.IsArray | |
|| this.IsClass | |
|| this.IsInterface | |
|| this.IsDictionary | |
|| this.IsTimeSpan | |
|| this.IsList; | |
} | |
} | |
public bool IsCDataType | |
{ | |
get { return _type == typeof(string) || _type == typeof(char); } | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public static class XElementExtensions | |
{ | |
public static bool HasAttributeWithValue(this XElement element, string name, string value) | |
{ | |
return element.Attributes().Any(a => a.Name.ToString().Equals(name) && a.Value.Equals(value)); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment