Skip to content

Commit

Permalink
Merge pull request #7 from Carnagion/development
Browse files Browse the repository at this point in the history
Merge v0.2.0 into stable
  • Loading branch information
Carnagion authored May 18, 2022
2 parents 1e51d5a + 897e8fb commit 70726e7
Show file tree
Hide file tree
Showing 6 changed files with 152 additions and 58 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
<ItemGroup>
<PackageReference Include="GDSerializer" Version="0.1.1" />
<PackageReference Include="GDSerializer" Version="0.2.0" />
</ItemGroup>
```

Expand Down
28 changes: 13 additions & 15 deletions Serializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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()},
Expand Down Expand Up @@ -234,30 +235,27 @@ 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];
}
}
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;
}
Expand Down
81 changes: 81 additions & 0 deletions Specialized/ArraySerializer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
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
{
/// <summary>
/// A (de)serializer for arrays.
/// </summary>
public class ArraySerializer : CollectionSerializer
{
/// <summary>
/// Serializes <paramref name="instance"/> into an <see cref="XmlNode"/>.
/// </summary>
/// <param name="instance">The <see cref="object"/> to serialize. It must be an array.</param>
/// <param name="arrayType">The <see cref="Type"/> to serialize <paramref name="instance"/> as. It must be an array type.</param>
/// <returns>An <see cref="XmlNode"/> that represents <paramref name="instance"/> and the serializable data stored in it.</returns>
/// <exception cref="SerializationException">Thrown if <paramref name="instance"/> could not be serialized due to unexpected errors or invalid input.</exception>
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);
}
}

/// <summary>
/// Deserializes <paramref name="node"/> into an <see cref="object"/>.
/// </summary>
/// <param name="node">The <see cref="XmlNode"/> to deserialize.</param>
/// <param name="arrayType">The <see cref="Type"/> of <see cref="object"/> to deserialize the node as. It must be an array type</param>
/// <returns>An <see cref="object"/> that represents the serialized data stored in <paramref name="node"/>.</returns>
/// <exception cref="SerializationException">Thrown if a <see cref="Type"/> could not be inferred from <paramref name="node"/> or was invalid, an instance of the <see cref="Type"/> could not be created, <paramref name="node"/> contained invalid properties/fields, or <paramref name="node"/> could not be deserialized due to unexpected errors or invalid data.</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);
}
}
}
}
64 changes: 41 additions & 23 deletions Specialized/CollectionSerializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,17 +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");
serializer.Serialize(item, itemType).ChildNodes
.Cast<XmlNode>()
.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)
Expand Down Expand Up @@ -80,26 +70,54 @@ 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, itemType),});
}
CollectionSerializer.DeserializeItems(node, itemType).ForEach(item => add.Invoke(collection, new[] {item,}));
return collection;
}
catch (Exception exception) when (exception is not SerializationException)
{
throw new SerializationException(node, exception);
}
}

/// <summary>
/// Serializes all items in the collection <paramref name="instance"/>. It must implement <see cref="ICollection{T}"/>.
/// </summary>
/// <param name="instance">The collection to serialize.</param>
/// <param name="itemType">The <see cref="Type"/> of items in <paramref name="instance"/>.</param>
/// <returns>An <see cref="IEnumerable{T}"/> of the serialized versions of the items in <paramref name="instance"/>.</returns>
protected static IEnumerable<XmlNode> 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<XmlNode>()
.ForEach(node => itemElement.AppendChild(context.ImportNode(node, true)));
yield return itemElement;
}
}

/// <summary>
/// Deserializes the children of <paramref name="node"/> as items of an <see cref="ICollection{T}"/>.
/// </summary>
/// <param name="node">The <see cref="XmlNode"/> whose children are to be deserialized.</param>
/// <param name="itemType">The <see cref="Type"/> of items in the collection.</param>
/// <returns>An <see cref="IEnumerable{T}"/> of the deserialized children nodes of <paramref name="node"/>.</returns>
/// <exception cref="SerializationException">Thrown if one of the child nodes in <paramref name="node"/> is not named "item".</exception>
protected static IEnumerable<object?> DeserializeItems(XmlNode node, Type itemType)
{
Serializer serializer = new();
return from child in node.ChildNodes.Cast<XmlNode>()
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\")");
}
}
}
22 changes: 15 additions & 7 deletions Specialized/DictionarySerializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<XmlNode>()
.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<XmlNode>()
.ForEach(node => valueElement.AppendChild(context.ImportNode(node, true)));

XmlElement itemElement = context.CreateElement("item");
itemElement.AppendChild(keyElement);
itemElement.AppendChild(valueElement);

dictionaryElement.AppendChild(itemElement);
}

Expand Down Expand Up @@ -122,7 +130,7 @@ where child.NodeType is XmlNodeType.Element
.Cast<XmlNode>()
.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;
}
Expand Down
13 changes: 1 addition & 12 deletions Specialized/EnumerableSerializer.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Xml;
Expand Down Expand Up @@ -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<XmlNode>()
.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)
Expand Down

0 comments on commit 70726e7

Please sign in to comment.