More options to serialize dictionaries
This commit is contained in:
1 parent
61a0d4fdf1
commit
0c5dcd9c74
3 files changed
+129
-27
No files matched your search
@@ -93,6 +93,23 @@ namespace VocaluxeLib.Xml
|
|||||||
private static readonly Dictionary<Type, List<SFieldInfo>> _CacheFields = new Dictionary<Type, List<SFieldInfo>>();
|
private static readonly Dictionary<Type, List<SFieldInfo>> _CacheFields = new Dictionary<Type, List<SFieldInfo>>();
|
||||||
private static readonly Dictionary<Type, String> _CacheTypeName = new Dictionary<Type, string>();
|
private static readonly Dictionary<Type, String> _CacheTypeName = new Dictionary<Type, string>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the name of the subType (e.g. list/array element) if any
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="type">Xml type name of the subType or null if no subType</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static string GetSubTypeName(this Type type)
|
||||||
|
{
|
||||||
|
Type subType;
|
||||||
|
if (type.IsGenericType)
|
||||||
|
subType = type.GetGenericArguments().Last();
|
||||||
|
else if (type.IsArray)
|
||||||
|
subType = type.GetElementType();
|
||||||
|
else
|
||||||
|
return null;
|
||||||
|
return subType.GetTypeName();
|
||||||
|
}
|
||||||
|
|
||||||
private static void _FillInfo(ref SFieldInfo info, MemberInfo field)
|
private static void _FillInfo(ref SFieldInfo info, MemberInfo field)
|
||||||
{
|
{
|
||||||
XmlAttributeAttribute attribute = field.GetAttribute<XmlAttributeAttribute>();
|
XmlAttributeAttribute attribute = field.GetAttribute<XmlAttributeAttribute>();
|
||||||
@@ -126,7 +143,7 @@ namespace VocaluxeLib.Xml
|
|||||||
|
|
||||||
if (info.Type.IsGenericType)
|
if (info.Type.IsGenericType)
|
||||||
info.SubType = info.Type.GetGenericArguments().Last();
|
info.SubType = info.Type.GetGenericArguments().Last();
|
||||||
if (info.Type.IsArray)
|
else if (info.Type.IsArray)
|
||||||
info.SubType = info.Type.GetElementType();
|
info.SubType = info.Type.GetElementType();
|
||||||
if (info.Type.IsList() || info.Type.IsArray)
|
if (info.Type.IsList() || info.Type.IsArray)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ namespace VocaluxeLib.Xml
|
|||||||
#endregion Debug Helpers
|
#endregion Debug Helpers
|
||||||
|
|
||||||
#region Deserialization
|
#region Deserialization
|
||||||
private object _GetValue(XmlNode node, Type type, string arrayItemName = null)
|
private object _GetValue(XmlNode node, Type type, string arrayItemName = null, object result = null)
|
||||||
{
|
{
|
||||||
if (type.IsEnum)
|
if (type.IsEnum)
|
||||||
{
|
{
|
||||||
@@ -106,35 +106,55 @@ namespace VocaluxeLib.Xml
|
|||||||
if (!node.HasChildNodes)
|
if (!node.HasChildNodes)
|
||||||
return null;
|
return null;
|
||||||
Type subType = type.GetGenericArguments()[0];
|
Type subType = type.GetGenericArguments()[0];
|
||||||
return _GetValue(node, subType);
|
return _GetValue(node, subType, arrayItemName, result);
|
||||||
}
|
}
|
||||||
if (type.IsList() || type.IsArray)
|
if (type.IsList() || type.IsArray)
|
||||||
{
|
{
|
||||||
Type subType = type.IsArray ? type.GetElementType() : type.GetGenericArguments()[0];
|
Type subType = type.IsArray ? type.GetElementType() : type.GetGenericArguments()[0];
|
||||||
String subName = arrayItemName ?? subType.GetTypeName();
|
String subName = arrayItemName ?? subType.GetTypeName();
|
||||||
|
subName = subName.ToLowerInvariant();
|
||||||
List<object> subValues = new List<object>();
|
List<object> subValues = new List<object>();
|
||||||
foreach (XmlNode subNode in node.ChildNodes)
|
foreach (XmlNode subNode in node.ChildNodes)
|
||||||
{
|
{
|
||||||
if (subNode is XmlComment)
|
if (subNode is XmlComment)
|
||||||
continue;
|
continue;
|
||||||
if (subName != subNode.Name && !subNode.Name.StartsWith(subName))
|
if (subName == subNode.Name.ToLowerInvariant() && !subNode.Name.ToLowerInvariant().StartsWith(subName))
|
||||||
throw new XmlException("Invalid list entry '" + subNode.Name + "' in " + _GetXPath(node) + "; Expected: " + subName);
|
throw new XmlException("Invalid list entry '" + subNode.Name + "' in " + _GetXPath(node) + "; Expected: " + subName);
|
||||||
object subValue = _GetValue(subNode, subType);
|
object subValue = _GetValue(subNode, subType);
|
||||||
subValues.Add(subValue);
|
subValues.Add(subValue);
|
||||||
}
|
}
|
||||||
return _CreateList(type, subValues);
|
if (result == null)
|
||||||
|
return _CreateList(type, subValues);
|
||||||
|
_FillList(result, type, subValues);
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
if (type.IsDictionary())
|
if (type.IsDictionary())
|
||||||
{
|
{
|
||||||
Type subType = type.GetGenericArguments()[1];
|
Type subType = type.GetGenericArguments()[1];
|
||||||
object dict = Activator.CreateInstance(type);
|
object dict = result ?? Activator.CreateInstance(type);
|
||||||
MethodInfo add = type.GetMethod("Add");
|
MethodInfo add = type.GetMethod("Add");
|
||||||
|
if (arrayItemName != null)
|
||||||
|
arrayItemName = arrayItemName.ToLowerInvariant();
|
||||||
foreach (XmlNode subNode in node.ChildNodes)
|
foreach (XmlNode subNode in node.ChildNodes)
|
||||||
{
|
{
|
||||||
if (subNode is XmlComment)
|
if (subNode is XmlComment)
|
||||||
continue;
|
continue;
|
||||||
object subValue = _GetValue(subNode, subType);
|
object subValue = _GetValue(subNode, subType);
|
||||||
add.Invoke(dict, new object[] {subNode.Name, subValue});
|
string subName;
|
||||||
|
if (arrayItemName != null)
|
||||||
|
{
|
||||||
|
if (arrayItemName != subNode.Name.ToLowerInvariant() && !subNode.Name.ToLowerInvariant().StartsWith(arrayItemName))
|
||||||
|
throw new XmlException("Invalid dictionary entry '" + subNode.Name + "' in " + _GetXPath(node) + "; Expected: " + arrayItemName);
|
||||||
|
if (subNode.Attributes == null)
|
||||||
|
throw new XmlException("'name' attribute is missing in " + _GetXPath(subNode));
|
||||||
|
XmlNode nameAtt = subNode.Attributes.GetNamedItem("name");
|
||||||
|
if (nameAtt == null)
|
||||||
|
throw new XmlException("'name' attribute is missing in " + _GetXPath(subNode));
|
||||||
|
subName = nameAtt.Value;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
subName = subNode.Name;
|
||||||
|
add.Invoke(dict, new object[] {subName, subValue});
|
||||||
}
|
}
|
||||||
return dict;
|
return dict;
|
||||||
}
|
}
|
||||||
@@ -142,7 +162,7 @@ namespace VocaluxeLib.Xml
|
|||||||
object value;
|
object value;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
value = Activator.CreateInstance(type);
|
value = result ?? Activator.CreateInstance(type);
|
||||||
}
|
}
|
||||||
catch (Exception)
|
catch (Exception)
|
||||||
{
|
{
|
||||||
@@ -193,23 +213,29 @@ namespace VocaluxeLib.Xml
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static object _CreateList(Type type, ICollection values)
|
private static void _FillList(object list, Type type, ICollection values)
|
||||||
{
|
{
|
||||||
|
if (values.Count <= 0)
|
||||||
|
return;
|
||||||
if (type.IsArray)
|
if (type.IsArray)
|
||||||
{
|
{
|
||||||
Array array = Array.CreateInstance(type.GetElementType(), values.Count);
|
Array array = (Array)list;
|
||||||
int i = 0;
|
int i = 0;
|
||||||
foreach (object value in values)
|
foreach (object value in values)
|
||||||
array.SetValue(value, i++);
|
array.SetValue(value, i++);
|
||||||
return array;
|
|
||||||
}
|
}
|
||||||
object list = Activator.CreateInstance(type, new object[] {values.Count});
|
else
|
||||||
if (values.Count > 0)
|
|
||||||
{
|
{
|
||||||
MethodInfo addMethod = type.GetMethod("Add");
|
MethodInfo addMethod = type.GetMethod("Add");
|
||||||
foreach (object value in values)
|
foreach (object value in values)
|
||||||
addMethod.Invoke(list, new object[] {value});
|
addMethod.Invoke(list, new object[] {value});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static object _CreateList(Type type, ICollection values)
|
||||||
|
{
|
||||||
|
object list = type.IsArray ? Array.CreateInstance(type.GetElementType(), values.Count) : Activator.CreateInstance(type, new object[] {values.Count});
|
||||||
|
_FillList(list, type, values);
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -315,7 +341,7 @@ namespace VocaluxeLib.Xml
|
|||||||
#endregion Deserialization
|
#endregion Deserialization
|
||||||
|
|
||||||
#region Serialization
|
#region Serialization
|
||||||
private void _WriteNode(XmlWriter writer, string name, Type type, object value, bool isAttribute, string arrayItemName = null)
|
private void _WriteNode(XmlWriter writer, string name, Type type, object value, bool isAttribute, string arrayItemName = null, string nameAttribute = null)
|
||||||
{
|
{
|
||||||
if (!isAttribute && _GetCommentCallback != null)
|
if (!isAttribute && _GetCommentCallback != null)
|
||||||
{
|
{
|
||||||
@@ -337,14 +363,23 @@ namespace VocaluxeLib.Xml
|
|||||||
if (isAttribute)
|
if (isAttribute)
|
||||||
writer.WriteAttributeString(name, strVal);
|
writer.WriteAttributeString(name, strVal);
|
||||||
else
|
else
|
||||||
writer.WriteElementString(name, strVal);
|
{
|
||||||
|
writer.WriteStartElement(name);
|
||||||
|
if (nameAttribute != null)
|
||||||
|
writer.WriteAttributeString("name", nameAttribute);
|
||||||
|
if (!string.IsNullOrEmpty(strVal))
|
||||||
|
writer.WriteValue(strVal);
|
||||||
|
writer.WriteEndElement();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (type.IsNullable())
|
else if (type.IsNullable())
|
||||||
_WriteNode(writer, name, type.GetGenericArguments()[0], value, isAttribute, arrayItemName);
|
_WriteNode(writer, name, type.GetGenericArguments()[0], value, isAttribute, arrayItemName, nameAttribute);
|
||||||
else if (type.IsList() || type.IsArray)
|
else if (type.IsList() || type.IsArray)
|
||||||
{
|
{
|
||||||
Debug.Assert(!isAttribute, "Lists cannot be attributes");
|
Debug.Assert(!isAttribute, "Lists cannot be attributes");
|
||||||
writer.WriteStartElement(name);
|
writer.WriteStartElement(name);
|
||||||
|
if (nameAttribute != null)
|
||||||
|
writer.WriteAttributeString("name", nameAttribute);
|
||||||
Type subType = type.IsArray ? type.GetElementType() : type.GetGenericArguments()[0];
|
Type subType = type.IsArray ? type.GetElementType() : type.GetGenericArguments()[0];
|
||||||
String subName = arrayItemName ?? subType.GetTypeName();
|
String subName = arrayItemName ?? subType.GetTypeName();
|
||||||
IEnumerable list = (IEnumerable)value;
|
IEnumerable list = (IEnumerable)value;
|
||||||
@@ -356,16 +391,34 @@ namespace VocaluxeLib.Xml
|
|||||||
{
|
{
|
||||||
Debug.Assert(!isAttribute, "Dictionaries cannot be attributes");
|
Debug.Assert(!isAttribute, "Dictionaries cannot be attributes");
|
||||||
writer.WriteStartElement(name);
|
writer.WriteStartElement(name);
|
||||||
|
if (nameAttribute != null)
|
||||||
|
writer.WriteAttributeString("name", nameAttribute);
|
||||||
Type subType = type.GetGenericArguments()[1];
|
Type subType = type.GetGenericArguments()[1];
|
||||||
IDictionary dict = (IDictionary)value;
|
IDictionary dict = (IDictionary)value;
|
||||||
foreach (DictionaryEntry entry in dict)
|
foreach (DictionaryEntry entry in dict)
|
||||||
_WriteNode(writer, (string)entry.Key, subType, entry.Value, false);
|
{
|
||||||
|
string subName;
|
||||||
|
string subNameAttribute;
|
||||||
|
if (arrayItemName == null)
|
||||||
|
{
|
||||||
|
subName = (string)entry.Key;
|
||||||
|
subNameAttribute = null;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
subName = arrayItemName;
|
||||||
|
subNameAttribute = (string)entry.Key;
|
||||||
|
}
|
||||||
|
_WriteNode(writer, subName, subType, entry.Value, false, null, subNameAttribute);
|
||||||
|
}
|
||||||
writer.WriteEndElement();
|
writer.WriteEndElement();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Debug.Assert(!isAttribute, "Complex types cannot be attributes");
|
Debug.Assert(!isAttribute, "Complex types cannot be attributes");
|
||||||
writer.WriteStartElement(name);
|
writer.WriteStartElement(name);
|
||||||
|
if (nameAttribute != null)
|
||||||
|
writer.WriteAttributeString("name", nameAttribute);
|
||||||
if (value != null)
|
if (value != null)
|
||||||
_WriteChildNodes(writer, value);
|
_WriteChildNodes(writer, value);
|
||||||
writer.WriteEndElement();
|
writer.WriteEndElement();
|
||||||
@@ -405,7 +458,7 @@ namespace VocaluxeLib.Xml
|
|||||||
throw new XmlException("No root element found!");
|
throw new XmlException("No root element found!");
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_ReadChildNodes(xDoc.DocumentElement, ref result, false);
|
result = _GetValue(xDoc.DocumentElement, typeof(T), typeof(T).GetSubTypeName(), result);
|
||||||
}
|
}
|
||||||
catch (TargetInvocationException e)
|
catch (TargetInvocationException e)
|
||||||
{
|
{
|
||||||
@@ -476,12 +529,16 @@ namespace VocaluxeLib.Xml
|
|||||||
else
|
else
|
||||||
name = "root";
|
name = "root";
|
||||||
}
|
}
|
||||||
writer.WriteStartDocument();
|
try
|
||||||
writer.WriteStartElement(name);
|
{
|
||||||
_WriteChildNodes(writer, o);
|
writer.WriteStartDocument();
|
||||||
writer.WriteEndElement();
|
_WriteNode(writer, name, o.GetType(), o, false, o.GetType().GetSubTypeName());
|
||||||
writer.WriteEndDocument();
|
writer.WriteEndDocument();
|
||||||
writer.Flush();
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
writer.Flush();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Serialize(string filePath, object o)
|
public void Serialize(string filePath, object o)
|
||||||
|
|||||||
@@ -393,6 +393,26 @@ namespace VocaluxeTests
|
|||||||
Assert.AreEqual(_XmlIgnore, xml.Serialize(bar2));
|
Assert.AreEqual(_XmlIgnore, xml.Serialize(bar2));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void TestOnlyList()
|
||||||
|
{
|
||||||
|
const string s = _Head + @"<root>
|
||||||
|
<String>2</String>
|
||||||
|
<String>3</String>
|
||||||
|
</root>";
|
||||||
|
_AssertSerDeserMatch<List<string>>(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void TestOnlyDict()
|
||||||
|
{
|
||||||
|
const string s = _Head + @"<root>
|
||||||
|
<String name=""val1"">2</String>
|
||||||
|
<String name=""val2"">3</String>
|
||||||
|
</root>";
|
||||||
|
_AssertSerDeserMatch<Dictionary<string, string>>(s);
|
||||||
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void TestRealFiles()
|
public void TestRealFiles()
|
||||||
{
|
{
|
||||||
@@ -402,11 +422,19 @@ namespace VocaluxeTests
|
|||||||
{
|
{
|
||||||
string xmlPath = Path.Combine(filePath, type.Name + ".xml");
|
string xmlPath = Path.Combine(filePath, type.Name + ".xml");
|
||||||
var xml = new CXmlSerializer(type == typeof(CConfig.SConfig));
|
var xml = new CXmlSerializer(type == typeof(CConfig.SConfig));
|
||||||
object foo = xml.Deserialize(xmlPath, Activator.CreateInstance(type));
|
object foo = null;
|
||||||
Assert.IsInstanceOfType(foo, type);
|
try
|
||||||
|
{
|
||||||
|
foo = xml.Deserialize(xmlPath, Activator.CreateInstance(type));
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Assert.Fail("Exception with " + type.Name + ": {0}", new object[] {e.Message});
|
||||||
|
}
|
||||||
|
Assert.IsInstanceOfType(foo, type, "Wrong type with " + type.Name);
|
||||||
string newXml = xml.Serialize(foo);
|
string newXml = xml.Serialize(foo);
|
||||||
string oldXml = File.ReadAllText(xmlPath);
|
string oldXml = File.ReadAllText(xmlPath);
|
||||||
Assert.AreEqual(oldXml, newXml, "Error in " + type.Name);
|
Assert.AreEqual(oldXml, newXml, "Error with " + type.Name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user