Skip to content

Instantly share code, notes, and snippets.

@esskar
Last active January 29, 2016 16:13
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save esskar/5358547 to your computer and use it in GitHub Desktop.
Save esskar/5358547 to your computer and use it in GitHub Desktop.
Objects to XML and back to Dynamics
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;
}
}
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);
}
}
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());
}
}
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); }
}
}
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