Skip to content

Commit

Permalink
Merge pull request #4 from Carnagion/development
Browse files Browse the repository at this point in the history
Merge v0.1.2 into stable
  • Loading branch information
Carnagion authored May 16, 2022
2 parents bb75ca6 + 7c9b5bb commit 1e51d5a
Show file tree
Hide file tree
Showing 11 changed files with 185 additions and 106 deletions.
2 changes: 1 addition & 1 deletion GDSerializer.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<!-- Workaround as Godot does not know how to properly load NuGet packages -->
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<PackageVersion>0.1.1</PackageVersion>
<PackageVersion>0.1.2</PackageVersion>
<Title>GDSerializer</Title>
<Authors>Carnagion</Authors>
<Description>An XML (de)serialization framework for Godot's C# API.</Description>
Expand Down
6 changes: 3 additions & 3 deletions ISerializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,16 @@ public interface ISerializer
/// Serializes <paramref name="instance"/> into an <see cref="XmlNode"/>.
/// </summary>
/// <param name="instance">The <see cref="object"/> to serialize.</param>
/// <param name="context">The <see cref="XmlDocument"/> to use when creating new <see cref="XmlNode"/>s that will be returned as part of result.</param>
/// <param name="type">The <see cref="Type"/> to serialize <paramref name="instance"/> as, in case it is different from <paramref name="instance"/>'s <see cref="Type"/>.</param>
/// <returns>An <see cref="XmlNode"/> that represents <paramref name="instance"/> and the serializable data stored in it.</returns>
XmlNode Serialize(object instance, XmlDocument? context = null);
XmlNode Serialize(object instance, Type? type = null);

/// <summary>
/// Deserializes <paramref name="node"/> into an <see cref="object"/>.
/// </summary>
/// <param name="node">The <see cref="XmlNode"/> to deserialize.</param>
/// <param name="type">The <see cref="Type"/> of <see cref="object"/> to deserialize the node as, in case it is not apparent from <paramref name="node"/>'s attributes.</param>
/// <returns>An <see cref="object"/> that represents the serialized data stored in <paramref name="node"/>.</returns>
object Deserialize(XmlNode node, Type? type = null);
object? Deserialize(XmlNode node, Type? type = null);
}
}
63 changes: 34 additions & 29 deletions Serializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public class Serializer : ISerializer
/// <summary>
/// An <see cref="OrderedDictionary{TKey,TValue}"/> of specialized <see cref="ISerializer"/>s for specific <see cref="Type"/>s. These serializers will be used by the <see cref="Serializer"/> when possible.
/// </summary>
public static OrderedDictionary<Type, ISerializer> Specialized // Must be static; making it instance will cause a stack overflow due to it being recursively created in inheriting classes
public static OrderedDictionary<Type, ISerializer> Specialized // Must be static since other serializers create new Serializer instances, and they all need access to the same dictionary
{
get;
} = new(19)
Expand Down Expand Up @@ -51,23 +51,23 @@ public class Serializer : ISerializer
/// Serializes <paramref name="instance"/> into an <see cref="XmlNode"/>.
/// </summary>
/// <param name="instance">The <see cref="object"/> to serialize.</param>
/// <param name="context">The <see cref="XmlDocument"/> to use when creating new <see cref="XmlNode"/>s that will be returned as part of result.</param>
/// <param name="type">The <see cref="Type"/> to serialize <paramref name="instance"/> as, in case it is different from <paramref name="instance"/>'s <see cref="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 virtual XmlNode Serialize(object instance, XmlDocument? context = null)
public XmlNode Serialize(object instance, Type? type = null)
{
Type type = instance.GetType();
type ??= instance.GetType();

// Use a more specialized serializer if possible
ISerializer? serializer = Serializer.GetSpecialSerializerForType(type);
if (serializer is not null)
{
return serializer.Serialize(instance, context);
return serializer.Serialize(instance, type);
}

try
{
context ??= new();
XmlDocument context = new();
XmlElement element;
// Use the "Type" attribute if generic or nested type as ` and + are not allowed as XML node names
if (type.IsGenericType)
Expand All @@ -86,7 +86,7 @@ public virtual XmlNode Serialize(object instance, XmlDocument? context = null)
}

// Recursively serialize properties
foreach (PropertyInfo property in from property in type.GetProperties(Serializer.instanceBindingFlags)
foreach (PropertyInfo property in from property in type.GetAllProperties(Serializer.instanceBindingFlags)
where property.IsSerializable()
select property)
{
Expand All @@ -96,14 +96,14 @@ where property.IsSerializable()
continue;
}
XmlNode node = context.CreateElement(property.Name);
this.Serialize(value, context).ChildNodes
this.Serialize(value, property.PropertyType).ChildNodes
.Cast<XmlNode>()
.ForEach(child => node.AppendChild(child));
.ForEach(child => node.AppendChild(context.ImportNode(child, true)));
element.AppendChild(node);
}

// Recursively serialize fields
foreach (FieldInfo field in from field in type.GetFields(Serializer.instanceBindingFlags)
foreach (FieldInfo field in from field in type.GetAllFields(Serializer.instanceBindingFlags)
where field.IsSerializable()
select field)
{
Expand All @@ -113,9 +113,9 @@ where field.IsSerializable()
continue;
}
XmlNode node= context.CreateElement(field.Name);
this.Serialize(value, context).ChildNodes
this.Serialize(value, field.FieldType).ChildNodes
.Cast<XmlNode>()
.ForEach(child => node.AppendChild(child));
.ForEach(child => node.AppendChild(context.ImportNode(child, true)));
element.AppendChild(node);
}

Expand All @@ -127,16 +127,32 @@ where field.IsSerializable()
}
}

/// <summary>
/// Serializes <paramref name="instance"/> into an <see cref="XmlNode"/>.
/// </summary>
/// <param name="instance">The <see cref="object"/> to serialize.</param>
/// <typeparam name="T">The <see cref="Type"/> to serialize <paramref name="instance"/> as.</typeparam>
/// <returns>An <see cref="XmlNode"/> that represents <paramref name="instance"/> and the serializable data stored in it.</returns>
public XmlNode Serialize<T>(T instance) where T : notnull
{
return this.Serialize(instance, typeof(T));
}

/// <summary>
/// Deserializes <paramref name="node"/> into an <see cref="object"/>.
/// </summary>
/// <param name="node">The <see cref="XmlNode"/> to deserialize.</param>
/// <param name="type">The <see cref="Type"/> of <see cref="object"/> to deserialize the node as, in case it is not apparent from <paramref name="node"/>'s attributes.</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 virtual object Deserialize(XmlNode node, Type? type = null)
public object? Deserialize(XmlNode node, Type? type = null)
{
type ??= Serializer.GetTypeToDeserialize(node) ?? throw new SerializationException(node, $"No {nameof(Type)} found to instantiate");
if (node.Attributes?["Null"]?.InnerText is "True")
{
return null;
}

type ??= node.GetTypeToDeserialize() ?? throw new SerializationException(node, $"No {nameof(Type)} found to instantiate");

// Use a more specialized deserializer if possible
ISerializer? serializer = Serializer.GetSpecialSerializerForType(type);
Expand All @@ -155,7 +171,7 @@ where child.NodeType is XmlNodeType.Element
select child)
{
// Recursively deserialize property
PropertyInfo? property = type.GetProperty(child.Name, Serializer.instanceBindingFlags);
PropertyInfo? property = type.FindProperty(child.Name, Serializer.instanceBindingFlags);
if (property is not null)
{
if (!property.CanWrite)
Expand All @@ -172,7 +188,7 @@ where child.NodeType is XmlNodeType.Element
}

// Recursively deserialize field
FieldInfo? field = type.GetField(child.Name, Serializer.instanceBindingFlags);
FieldInfo? field = type.FindField(child.Name, Serializer.instanceBindingFlags);
if (field is not null)
{
if (!field.GetCustomAttribute<SerializeAttribute>()?.Serializable ?? false)
Expand Down Expand Up @@ -211,20 +227,9 @@ where attribute is not null && attribute.Serializable
/// <param name="node">The <see cref="XmlNode"/> to deserialize.</param>
/// <typeparam name="T">The <see cref="Type"/> of <see cref="object"/> to deserialize <paramref name="node"/> as.</typeparam>
/// <returns>An <see cref="object"/> that represents the serialized data stored in <paramref name="node"/>.</returns>
public T Deserialize<T>(XmlNode node)
{
return (T)this.Deserialize(node, typeof(T));
}

private static Type? GetTypeToDeserialize(XmlNode node)
public T? Deserialize<T>(XmlNode node)
{
string name = (node.Attributes?["Type"]?.InnerText ?? node.Name)
.Replace("&lt;", "<")
.Replace("&gt;", ">");
return (from assembly in AppDomain.CurrentDomain.GetAssemblies().Distinct()
select assembly.GetType(name))
.NotNull()
.FirstOrDefault();
return (T?)this.Deserialize(node, typeof(T));
}

private static ISerializer? GetSpecialSerializerForType(Type type)
Expand Down
33 changes: 18 additions & 15 deletions Specialized/CollectionSerializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,35 +13,40 @@ namespace Godot.Serialization.Specialized
/// <summary>
/// A (de)serializer for types that implement <see cref="ICollection{T}"/>.
/// </summary>
public class CollectionSerializer : Serializer
public class CollectionSerializer : ISerializer
{
/// <summary>
/// Serializes <paramref name="instance"/> into an <see cref="XmlNode"/>.
/// </summary>
/// <param name="instance">The <see cref="object"/> to serialize. It must implement <see cref="ICollection{T}"/>.</param>
/// <param name="context">The <see cref="XmlDocument"/> to use when creating new <see cref="XmlNode"/>s that will be returned as part of result.</param>
/// <param name="collectionType">The <see cref="Type"/> to serialize <paramref name="instance"/> as.</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, XmlDocument? context = null)
public virtual XmlNode Serialize(object instance, Type? collectionType = null)
{
Type collectionType = instance.GetType();
collectionType ??= instance.GetType();
if (!collectionType.DerivesFromGenericType(typeof(ICollection<>)))
{
throw new SerializationException(instance, $"\"{collectionType.GetDisplayName()}\" cannot be (de)serialized by {typeof(CollectionSerializer).GetDisplayName()}");
}

try
{
context ??= new();
Type itemType = collectionType.GenericTypeArguments[0];

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");
base.Serialize(item, context).ChildNodes
serializer.Serialize(item, itemType).ChildNodes
.Cast<XmlNode>()
.ForEach(node => itemElement.AppendChild(node));
collectionElement.AppendChild(itemElement);
collectionElement.AppendChild(context.ImportNode(itemElement, true));
}
return collectionElement;
}
Expand All @@ -58,13 +63,9 @@ public override XmlNode Serialize(object instance, XmlDocument? context = null)
/// <param name="collectionType">The <see cref="Type"/> of <see cref="object"/> to deserialize the node as. It must implement <see cref="ICollection{T}"/>.</param>
/// <returns>An <see cref="object"/> that represents the serialized data stored in <paramref name="node"/>.</returns>
/// <exception cref="SerializationException">Thrown if <paramref name="node"/> could not be deserialized due to unexpected errors or invalid input.</exception>
public override object Deserialize(XmlNode node, Type? collectionType = null)
public virtual object Deserialize(XmlNode node, Type? collectionType = null)
{
if (collectionType is null)
{
throw new SerializationException(node, $"{nameof(Type)} not provided");
}

collectionType ??= node.GetTypeToDeserialize() ?? throw new SerializationException(node, $"No {nameof(Type)} found to instantiate");
if (!collectionType.DerivesFromGenericType(typeof(ICollection<>)))
{
throw new SerializationException(node, $"\"{collectionType.GetDisplayName()}\" cannot be (de)serialized by {typeof(CollectionSerializer).GetDisplayName()}");
Expand All @@ -79,6 +80,8 @@ public override 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
Expand All @@ -89,7 +92,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[] {base.Deserialize(child, itemType),});
add.Invoke(collection, new[] {serializer.Deserialize(child, itemType),});
}
return collection;
}
Expand Down
Loading

0 comments on commit 1e51d5a

Please sign in to comment.