From 5fa3416bd5feb3a7de7d11d92644c6f6cf0a424c Mon Sep 17 00:00:00 2001 From: Carnagion Date: Tue, 17 May 2022 10:20:47 +0100 Subject: [PATCH 1/8] Fix collections assuming items to be of the same (base) type --- Specialized/CollectionSerializer.cs | 8 ++++++-- Specialized/DictionarySerializer.cs | 22 +++++++++++++++------- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/Specialized/CollectionSerializer.cs b/Specialized/CollectionSerializer.cs index 6bb7af7..a4916ea 100644 --- a/Specialized/CollectionSerializer.cs +++ b/Specialized/CollectionSerializer.cs @@ -43,7 +43,11 @@ public virtual XmlNode Serialize(object instance, Type? collectionType = null) foreach (object item in (IEnumerable)instance) { XmlElement itemElement = context.CreateElement("item"); - serializer.Serialize(item, itemType).ChildNodes + if (item.GetType() != itemType) + { + itemElement.SetAttribute("Type", item.GetType().FullName); + } + serializer.Serialize(item, item.GetType()).ChildNodes .Cast() .ForEach(node => itemElement.AppendChild(node)); collectionElement.AppendChild(context.ImportNode(itemElement, true)); @@ -92,7 +96,7 @@ where child.NodeType is XmlNodeType.Element { throw new SerializationException(child, "Invalid XML node (all nodes in a collection must be named \"item\")"); } - add.Invoke(collection, new[] {serializer.Deserialize(child, itemType),}); + add.Invoke(collection, new[] {serializer.Deserialize(child, child.GetTypeToDeserialize() ?? itemType),}); } return collection; } diff --git a/Specialized/DictionarySerializer.cs b/Specialized/DictionarySerializer.cs index b376c5a..768ce11 100644 --- a/Specialized/DictionarySerializer.cs +++ b/Specialized/DictionarySerializer.cs @@ -48,23 +48,31 @@ public XmlNode Serialize(object instance, Type? dictionaryType = null) foreach (object item in (IEnumerable)instance) { - XmlElement itemElement = context.CreateElement("item"); - XmlElement keyElement = context.CreateElement("key"); - XmlElement valueElement = context.CreateElement("value"); - object key = keyProperty.GetValue(item)!; object value = valueProperty.GetValue(item)!; - serializer.Serialize(key, keyType).ChildNodes + XmlElement keyElement = context.CreateElement("key"); + if (key.GetType() != keyType) + { + keyElement.SetAttribute("Type", key.GetType().FullName); + } + serializer.Serialize(key, key.GetType()).ChildNodes .Cast() .ForEach(node => keyElement.AppendChild(context.ImportNode(node, true))); - serializer.Serialize(value, valueType).ChildNodes + XmlElement valueElement = context.CreateElement("value"); + if (value.GetType() != valueType) + { + valueElement.SetAttribute("Type", value.GetType().FullName); + } + serializer.Serialize(value, value.GetType()).ChildNodes .Cast() .ForEach(node => valueElement.AppendChild(context.ImportNode(node, true))); + XmlElement itemElement = context.CreateElement("item"); itemElement.AppendChild(keyElement); itemElement.AppendChild(valueElement); + dictionaryElement.AppendChild(itemElement); } @@ -122,7 +130,7 @@ where child.NodeType is XmlNodeType.Element .Cast() .SingleOrDefault(grandchild => grandchild.Name == "value") ?? throw new SerializationException(child, "No value node present"); - add.Invoke(dictionary, new[] {serializer.Deserialize(key, keyType), serializer.Deserialize(value, valueType),}); + add.Invoke(dictionary, new[] {serializer.Deserialize(key, key.GetTypeToDeserialize() ?? keyType), serializer.Deserialize(value, value.GetTypeToDeserialize() ?? valueType),}); } return dictionary; } From 3fdddc63445106bc1a58619464c1d357bc95d866 Mon Sep 17 00:00:00 2001 From: Carnagion Date: Tue, 17 May 2022 10:20:59 +0100 Subject: [PATCH 2/8] Update README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d29ba5f..ac90906 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ GDSerializer is available as a [NuGet package](https://www.nuget.org/packages/GD Simply include the following lines in a Godot project's `.csproj` file (either by editing the file manually or letting an IDE install the package): ```xml - + ``` From adc88dcb467737556d5f3b3402ba3988795baae5 Mon Sep 17 00:00:00 2001 From: Carnagion Date: Tue, 17 May 2022 19:34:00 +0100 Subject: [PATCH 3/8] Refactor CollectionSerializer's item (de)serialization --- Specialized/CollectionSerializer.cs | 71 ++++++++++++++++++----------- Specialized/EnumerableSerializer.cs | 13 +----- 2 files changed, 45 insertions(+), 39 deletions(-) diff --git a/Specialized/CollectionSerializer.cs b/Specialized/CollectionSerializer.cs index a4916ea..e8b78d4 100644 --- a/Specialized/CollectionSerializer.cs +++ b/Specialized/CollectionSerializer.cs @@ -37,21 +37,7 @@ public virtual XmlNode Serialize(object instance, Type? collectionType = null) XmlDocument context = new(); XmlElement collectionElement = context.CreateElement("Collection"); collectionElement.SetAttribute("Type", collectionType.FullName); - - Serializer serializer = new(); - - foreach (object item in (IEnumerable)instance) - { - XmlElement itemElement = context.CreateElement("item"); - if (item.GetType() != itemType) - { - itemElement.SetAttribute("Type", item.GetType().FullName); - } - serializer.Serialize(item, item.GetType()).ChildNodes - .Cast() - .ForEach(node => itemElement.AppendChild(node)); - collectionElement.AppendChild(context.ImportNode(itemElement, true)); - } + CollectionSerializer.SerializeItems(instance, itemType).ForEach(node => collectionElement.AppendChild(context.ImportNode(node, true))); return collectionElement; } catch (Exception exception) when (exception is not SerializationException) @@ -84,20 +70,9 @@ public virtual object Deserialize(XmlNode node, Type? collectionType = null) { collectionType = typeof(List<>).MakeGenericType(itemType); } - - Serializer serializer = new(); object collection = Activator.CreateInstance(collectionType, true) ?? throw new SerializationException(node, $"Unable to instantiate {collectionType.GetDisplayName()}"); - foreach (XmlNode child in from XmlNode child in node.ChildNodes - where child.NodeType is XmlNodeType.Element - select child) - { - if (child.Name != "item") - { - throw new SerializationException(child, "Invalid XML node (all nodes in a collection must be named \"item\")"); - } - add.Invoke(collection, new[] {serializer.Deserialize(child, child.GetTypeToDeserialize() ?? itemType),}); - } + CollectionSerializer.DeserializeItems(node, itemType).ForEach(item => add.Invoke(collection, new[] {item,})); return collection; } catch (Exception exception) when (exception is not SerializationException) @@ -105,5 +80,47 @@ where child.NodeType is XmlNodeType.Element throw new SerializationException(node, exception); } } + + /// + /// Serializes all items in the collection . It must implement . + /// + /// The collection to serialize. + /// The of items in . + /// An of the serialized versions of the items in . + protected static IEnumerable SerializeItems(object instance, Type itemType) + { + XmlDocument context = new(); + Serializer serializer = new(); + foreach (object item in (IEnumerable)instance) + { + XmlElement itemElement = context.CreateElement("item"); + if (item.GetType() != itemType) + { + itemElement.SetAttribute("Type", item.GetType().FullName); + } + serializer.Serialize(item, item.GetType()).ChildNodes + .Cast() + .ForEach(node => itemElement.AppendChild(node)); + yield return itemElement; + } + } + + /// + /// Deserializes the children of as items of an . + /// + /// The whose children are to be deserialized. + /// The of items in the collection. + /// An of the deserialized children nodes of . + /// Thrown if one of the child nodes in is not named "item". + protected static IEnumerable DeserializeItems(XmlNode node, Type itemType) + { + Serializer serializer = new(); + foreach (XmlNode child in from XmlNode child in node.ChildNodes + where child.NodeType is XmlNodeType.Element + select child) + { + yield return child.Name is "item" ? serializer.Deserialize(child, child.GetTypeToDeserialize() ?? itemType) : throw new SerializationException(child, "Invalid XML node (all nodes in a collection must be named \"item\")"); + } + } } } \ No newline at end of file diff --git a/Specialized/EnumerableSerializer.cs b/Specialized/EnumerableSerializer.cs index a6fa0bd..23e9fbe 100644 --- a/Specialized/EnumerableSerializer.cs +++ b/Specialized/EnumerableSerializer.cs @@ -1,5 +1,4 @@ using System; -using System.Collections; using System.Collections.Generic; using System.Linq; using System.Xml; @@ -36,17 +35,7 @@ public override XmlNode Serialize(object instance, Type? enumerableType = null) XmlDocument context = new(); XmlElement enumerableElement = context.CreateElement("Enumerable"); enumerableElement.SetAttribute("Type", enumerableType.FullName); - - Serializer serializer = new(); - - foreach (object item in (IEnumerable)instance) - { - XmlElement itemElement = context.CreateElement("item"); - serializer.Serialize(item, itemType).ChildNodes - .Cast() - .ForEach(node => itemElement.AppendChild(context.ImportNode(node, true))); - enumerableElement.AppendChild(itemElement); - } + EnumerableSerializer.SerializeItems(instance, itemType).ForEach(node => enumerableElement.AppendChild(context.ImportNode(node, true))); return enumerableElement; } catch (Exception exception) when (exception is not SerializationException) From 4188b5e136c510d5f4b2d7bbba62e63cd28f2d99 Mon Sep 17 00:00:00 2001 From: Carnagion Date: Tue, 17 May 2022 19:35:35 +0100 Subject: [PATCH 4/8] Refactor method for finding special serializer --- Serializer.cs | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/Serializer.cs b/Serializer.cs index 29d2934..2c53d06 100644 --- a/Serializer.cs +++ b/Serializer.cs @@ -234,22 +234,15 @@ where attribute is not null && attribute.Serializable private static ISerializer? GetSpecialSerializerForType(Type type) { - ISerializer? serializer; + ISerializer? serializer = Serializer.Specialized.GetValueOrDefault(type); + if (serializer is not null) + { + return serializer; + } if (type.IsGenericType) { - serializer = Serializer.Specialized.GetValueOrDefault(type); - if (serializer is not null) - { - return serializer; - } - Type? match = Serializer.Specialized.Keys - .FirstOrDefault(type.IsExactlyGenericType); - if (match is not null) - { - return Serializer.Specialized[match]; - } - match = Serializer.Specialized.Keys - .FirstOrDefault(type.DerivesFromGenericType); + Type? match = Serializer.Specialized.Keys.FirstOrDefault(type.IsExactlyGenericType); + match ??= Serializer.Specialized.Keys.FirstOrDefault(type.DerivesFromGenericType); if (match is not null) { return Serializer.Specialized[match]; @@ -257,7 +250,11 @@ where attribute is not null && attribute.Serializable } else { - Serializer.Specialized.TryGetValue(type, out serializer); + Type? match = Serializer.Specialized.Keys.FirstOrDefault(key => key.IsAssignableFrom(type)); + if (match is not null) + { + serializer = Serializer.Specialized[match]; + } } return serializer; } From f44b59f8d931998eebead4a5bf82ff0ebfbd9084 Mon Sep 17 00:00:00 2001 From: Carnagion Date: Tue, 17 May 2022 19:35:51 +0100 Subject: [PATCH 5/8] Add serializer for arrays --- Serializer.cs | 1 + Specialized/ArraySerializer.cs | 64 ++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+) create mode 100644 Specialized/ArraySerializer.cs diff --git a/Serializer.cs b/Serializer.cs index 2c53d06..ac683a5 100644 --- a/Serializer.cs +++ b/Serializer.cs @@ -40,6 +40,7 @@ public class Serializer : ISerializer {typeof(float), new SimpleSerializer()}, {typeof(double), new SimpleSerializer()}, {typeof(decimal), new SimpleSerializer()}, + {typeof(Array), new ArraySerializer()}, {typeof(IDictionary<,>), new DictionarySerializer()}, {typeof(ICollection<>), new CollectionSerializer()}, {typeof(IEnumerable<>), new EnumerableSerializer()}, diff --git a/Specialized/ArraySerializer.cs b/Specialized/ArraySerializer.cs new file mode 100644 index 0000000..df98a44 --- /dev/null +++ b/Specialized/ArraySerializer.cs @@ -0,0 +1,64 @@ +using System; +using System.Collections; +using System.Linq; +using System.Xml; + +using Godot.Serialization.Utility.Exceptions; +using Godot.Serialization.Utility.Extensions; + +namespace Godot.Serialization.Specialized +{ + public class ArraySerializer : CollectionSerializer + { + public override XmlNode Serialize(object instance, Type? arrayType = null) + { + arrayType ??= instance.GetType(); + if (!arrayType.IsArray) + { + throw new SerializationException(instance, $"\"{arrayType.GetDisplayName()}\" cannot be serialized by {typeof(ArraySerializer).GetDisplayName()}"); + } + + try + { + Type itemType = arrayType.GetElementType()!; + + XmlDocument context = new(); + XmlElement arrayElement = context.CreateElement("Array"); + arrayElement.SetAttribute("Type", arrayType.FullName); + ArraySerializer.SerializeItems(instance, itemType).ForEach(node => arrayElement.AppendChild(context.ImportNode(node, true))); + return arrayElement; + } + catch (Exception exception) when (exception is not SerializationException) + { + throw new SerializationException(instance, exception); + } + } + + public override object Deserialize(XmlNode node, Type? arrayType = null) + { + arrayType ??= node.GetTypeToDeserialize() ?? throw new SerializationException(node, $"No {nameof(Type)} found to instantiate"); + if (!arrayType.IsArray) + { + throw new SerializationException(node, $"\"{arrayType.GetDisplayName()}\" cannot be deserialized by {typeof(ArraySerializer).GetDisplayName()}"); + } + + try + { + Type itemType = arrayType.GetElementType()!; + + IList array = Array.CreateInstance(itemType, node.ChildNodes.Count); + int index = 0; + foreach (object? item in ArraySerializer.DeserializeItems(node, itemType)) + { + array[index] = item; + index += 1; + } + return array; + } + catch (Exception exception) when (exception is not SerializationException) + { + throw new SerializationException(node, exception); + } + } + } +} \ No newline at end of file From a9306145de508e868c7248ef2882310863128ae5 Mon Sep 17 00:00:00 2001 From: Carnagion Date: Tue, 17 May 2022 19:49:09 +0100 Subject: [PATCH 6/8] Add documentation comments for ArraySerializer --- Specialized/ArraySerializer.cs | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/Specialized/ArraySerializer.cs b/Specialized/ArraySerializer.cs index df98a44..1781a40 100644 --- a/Specialized/ArraySerializer.cs +++ b/Specialized/ArraySerializer.cs @@ -8,8 +8,18 @@ namespace Godot.Serialization.Specialized { + /// + /// A (de)serializer for arrays. + /// public class ArraySerializer : CollectionSerializer { + /// + /// Serializes into an . + /// + /// The to serialize. It must be an array. + /// The to serialize as. It must be an array type. + /// An that represents and the serializable data stored in it. + /// Thrown if could not be serialized due to unexpected errors or invalid input. public override XmlNode Serialize(object instance, Type? arrayType = null) { arrayType ??= instance.GetType(); @@ -33,7 +43,14 @@ public override XmlNode Serialize(object instance, Type? arrayType = null) throw new SerializationException(instance, exception); } } - + + /// + /// Deserializes into an . + /// + /// The to deserialize. + /// The of to deserialize the node as. It must be an array type + /// An that represents the serialized data stored in . + /// Thrown if a could not be inferred from or was invalid, an instance of the could not be created, contained invalid properties/fields, or could not be deserialized due to unexpected errors or invalid data. public override object Deserialize(XmlNode node, Type? arrayType = null) { arrayType ??= node.GetTypeToDeserialize() ?? throw new SerializationException(node, $"No {nameof(Type)} found to instantiate"); From 65f45ce83ad1b03ad09938e35cb628a769c9ca30 Mon Sep 17 00:00:00 2001 From: Carnagion Date: Wed, 18 May 2022 08:45:45 +0100 Subject: [PATCH 7/8] Tweak and refactor CollectionSerializer code --- Specialized/CollectionSerializer.cs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/Specialized/CollectionSerializer.cs b/Specialized/CollectionSerializer.cs index e8b78d4..220ecdb 100644 --- a/Specialized/CollectionSerializer.cs +++ b/Specialized/CollectionSerializer.cs @@ -100,7 +100,7 @@ protected static IEnumerable SerializeItems(object instance, Type itemT } serializer.Serialize(item, item.GetType()).ChildNodes .Cast() - .ForEach(node => itemElement.AppendChild(node)); + .ForEach(node => itemElement.AppendChild(context.ImportNode(node, true))); yield return itemElement; } } @@ -115,12 +115,9 @@ protected static IEnumerable SerializeItems(object instance, Type itemT protected static IEnumerable DeserializeItems(XmlNode node, Type itemType) { Serializer serializer = new(); - foreach (XmlNode child in from XmlNode child in node.ChildNodes - where child.NodeType is XmlNodeType.Element - select child) - { - yield return child.Name is "item" ? serializer.Deserialize(child, child.GetTypeToDeserialize() ?? itemType) : throw new SerializationException(child, "Invalid XML node (all nodes in a collection must be named \"item\")"); - } + return from child in node.ChildNodes.Cast() + where child.NodeType is XmlNodeType.Element + select child.Name is "item" ? serializer.Deserialize(child, child.GetTypeToDeserialize() ?? itemType) : throw new SerializationException(child, "Invalid XML node (all nodes in a collection must be named \"item\")"); } } } \ No newline at end of file From 897e8fb7fa312da1b141acb7e8d0bb0ade048d6e Mon Sep 17 00:00:00 2001 From: Carnagion Date: Wed, 18 May 2022 16:12:37 +0100 Subject: [PATCH 8/8] Update README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ac90906..453ae51 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ GDSerializer is available as a [NuGet package](https://www.nuget.org/packages/GD Simply include the following lines in a Godot project's `.csproj` file (either by editing the file manually or letting an IDE install the package): ```xml - + ```