Skip to content

Commit

Permalink
Merge pull request #9 from Carnagion/development
Browse files Browse the repository at this point in the history
Merge v1.1.0 into stable
  • Loading branch information
Carnagion authored Jun 25, 2022
2 parents 399612e + 89172ff commit 989fb37
Show file tree
Hide file tree
Showing 11 changed files with 72 additions and 39 deletions.
12 changes: 2 additions & 10 deletions GDSerializer.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,17 @@
<RootNamespace>Godot.Serialization</RootNamespace>
<LangVersion>default</LangVersion>
<Nullable>enable</Nullable>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<!-- Workaround as Godot does not know how to properly load NuGet packages -->
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<PackageVersion>1.0.0</PackageVersion>
<PackageVersion>1.1.0</PackageVersion>
<Title>GDSerializer</Title>
<Authors>Carnagion</Authors>
<Description>An XML (de)serialization framework for Godot's C# API.</Description>
<RepositoryUrl>https://github.com/Carnagion/GDSerializer</RepositoryUrl>
<PackageLicenseFile>LICENSE</PackageLicenseFile>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<DocumentationFile>.\.mono\temp\bin\Debug\GDSerializer.xml</DocumentationFile>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'ExportDebug' ">
<DocumentationFile>.\.mono\temp\bin\ExportDebug\GDSerializer.xml</DocumentationFile>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'ExportRelease' ">
<DocumentationFile>.\.mono\temp\bin\ExportRelease\GDSerializer.xml</DocumentationFile>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Carnagion.MoreLinq" Version="1.3.0" />
<PackageReference Include="System.CodeDom" Version="6.0.0" />
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ It supports (de)serialization of almost any C# type including collections and ma
**GDSerializer** is available as a [NuGet package](https://www.nuget.org/packages/GDSerializer/), which can be installed either through an IDE or by manually including the following lines in a Godot project's `.csproj` file:
```xml
<ItemGroup>
<PackageReference Include="GDSerializer" Version="1.0.0" />
<PackageReference Include="GDSerializer" Version="1.1.0" />
</ItemGroup>
```
Its dependencies may need to be installed as well, in a similar fashion.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

using Godot.Serialization.Utility.Extensions;

namespace Godot.Serialization.Utility.Exceptions
namespace Godot.Serialization
{
/// <summary>
/// The exception that is thrown when there is a failed attempt at serializing an <see cref="object"/> or deserializing an <see cref="XmlNode"/>.
Expand Down
84 changes: 67 additions & 17 deletions Serializer.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;
using System.Xml;

using Godot.Serialization.Specialized;
using Godot.Serialization.Utility;
using Godot.Serialization.Utility.Exceptions;
using Godot.Serialization.Utility.Extensions;

namespace Godot.Serialization
Expand All @@ -19,7 +19,8 @@ public class Serializer : ISerializer
/// <summary>
/// Initialises a new <see cref="Serializer"/> with the default specialized serializers.
/// </summary>
public Serializer()
/// <param name="referenceSources">An <see cref="IEnumerable{T}"/> of <see cref="XmlNode"/>s to use when deserializing <see cref="XmlNode"/>s that refer other <see cref="XmlNode"/>s through an ID.</param>
public Serializer(IEnumerable<XmlNode>? referenceSources = null)
{
this.Specialized = new(19)
{
Expand All @@ -45,15 +46,20 @@ public Serializer()
{typeof(Vector3), Serializer.vector},
{typeof(Enum), new EnumSerializer()},
};
this.ReferenceSources = referenceSources?.ToHashSet();
this.referenceStorage = this.ReferenceSources is null ? null : new();
}

/// <summary>
/// Initialises a new <see cref="Serializer"/> with the specified parameters.
/// </summary>
/// <param name="specializedSerializers">The specialized serializers to use when (de)serializing specific <see cref="Type"/>s.</param>
public Serializer(OrderedDictionary<Type, ISerializer> specializedSerializers)
/// <param name="referenceSources">An <see cref="IEnumerable{T}"/> of <see cref="XmlNode"/>s to use when deserializing <see cref="XmlNode"/>s that refer other <see cref="XmlNode"/>s through an ID.</param>
public Serializer(OrderedDictionary<Type, ISerializer> specializedSerializers, IEnumerable<XmlNode>? referenceSources = null)
{
this.Specialized = specializedSerializers;
this.ReferenceSources = referenceSources?.ToHashSet();
this.referenceStorage = this.ReferenceSources is null ? null : new();
}

private const BindingFlags instanceBindingFlags = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public;
Expand All @@ -62,14 +68,24 @@ public Serializer(OrderedDictionary<Type, ISerializer> specializedSerializers)

private static readonly VectorSerializer vector = new();

private readonly Dictionary<string, object?>? referenceStorage;

/// <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.
/// 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 OrderedDictionary<Type, ISerializer> Specialized
{
get;
}

/// <summary>
/// A <see cref="HashSet{T}"/> of <see cref="XmlNode"/>s that contain <see cref="XmlNode"/>s with IDs referenced by other <see cref="XmlNode"/>s.
/// </summary>
public HashSet<XmlNode>? ReferenceSources
{
get;
}

/// <summary>
/// Serializes <paramref name="instance"/> into an <see cref="XmlNode"/>.
/// </summary>
Expand All @@ -82,8 +98,7 @@ public XmlNode Serialize(object instance, Type? type = null)
type ??= instance.GetType();

// Use a more specialized serializer if possible
ISerializer? serializer = this.GetSpecialSerializerForType(type);
if (serializer is not null)
if (this.TryGetSpecialSerializerForType(type, out ISerializer? serializer))
{
return serializer.Serialize(instance, type);
}
Expand Down Expand Up @@ -170,10 +185,15 @@ where method.GetCustomAttribute<AfterSerializationAttribute>() is not null
}

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

// Use a previously deserialized node if referenced
if (this.TryDeserializeReferencedNode(node, out object? referenced))
{
return referenced;
}

// Use a more specialized deserializer if possible
ISerializer? serializer = this.GetSpecialSerializerForType(type);
if (serializer is not null)
if (this.TryGetSpecialSerializerForType(type, out ISerializer? serializer))
{
return serializer.Deserialize(node, type);
}
Expand Down Expand Up @@ -235,6 +255,13 @@ where attribute is not null && attribute.Serializable
where method.GetCustomAttribute<AfterDeserializationAttribute>() is not null
select method).ForEach(method => method.Invoke(method.IsStatic ? null : instance, null));

// Add deserialized instance to reference storage if it has an ID
string? id = node.Attributes?["Id"]?.InnerText;
if (id is not null)
{
this.referenceStorage?.Add(id, instance);
}

return instance;
}
catch (Exception exception) when (exception is not SerializationException)
Expand Down Expand Up @@ -265,31 +292,54 @@ public XmlNode Serialize<T>(T instance) where T : notnull
return (T?)this.Deserialize(node, typeof(T));
}

private ISerializer? GetSpecialSerializerForType(Type type)
private bool TryGetSpecialSerializerForType(Type type, [NotNullWhen(true)] out ISerializer? serializer)
{
ISerializer? serializer = this.Specialized.GetValueOrDefault(type);
if (serializer is not null)
if (this.Specialized.TryGetValue(type, out serializer))
{
return serializer;
return true;
}
if (type.IsGenericType)
{
Type? match = this.Specialized.Keys.FirstOrDefault(type.IsExactlyGenericType);
match ??= this.Specialized.Keys.FirstOrDefault(type.DerivesFromGenericType);
if (match is not null)
if (match is null)
{
return this.Specialized[match];
return false;
}
serializer = this.Specialized[match];
}
else
{
Type? match = this.Specialized.Keys.FirstOrDefault(key => key.IsAssignableFrom(type));
if (match is not null)
if (match is null)
{
serializer = this.Specialized[match];
return false;
}
serializer = this.Specialized[match];
}
return true;
}

private bool TryDeserializeReferencedNode(XmlNode node, out object? instance)
{
instance = null;
if (this.referenceStorage is null || this.ReferenceSources is null)
{
return false;
}
string? referencedId = node.Attributes?["Refer"]?.InnerText;
if (referencedId is null)
{
return false;
}
if (this.referenceStorage.TryGetValue(referencedId, out instance))
{
return true;
}
return serializer;
XmlNode referencedNode = (from source in this.ReferenceSources
select source.SelectSingleNode($"*[@Id='{referencedId}']")).FirstOrDefault() ?? throw new SerializationException(node, $"Referenced XML node with ID \"{referencedId}\" not found");
instance = this.Deserialize(referencedNode);
return true;
}
}
}
1 change: 0 additions & 1 deletion Specialized/ArraySerializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
using System.Linq;
using System.Xml;

using Godot.Serialization.Utility.Exceptions;
using Godot.Serialization.Utility.Extensions;

namespace Godot.Serialization.Specialized
Expand Down
1 change: 0 additions & 1 deletion Specialized/CollectionSerializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
using System.Reflection;
using System.Xml;

using Godot.Serialization.Utility.Exceptions;
using Godot.Serialization.Utility.Extensions;

namespace Godot.Serialization.Specialized
Expand Down
5 changes: 1 addition & 4 deletions Specialized/DictionarySerializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
using System.Reflection;
using System.Xml;

using Godot.Serialization.Utility.Exceptions;
using Godot.Serialization.Utility.Extensions;

namespace Godot.Serialization.Specialized
Expand Down Expand Up @@ -119,8 +118,6 @@ public object Deserialize(XmlNode node, Type? dictionaryType = null)
{
dictionaryType = typeof(Dictionary<,>).MakeGenericType(keyType, valueType);
}

Serializer serializer = new();

object dictionary = Activator.CreateInstance(dictionaryType, true) ?? throw new SerializationException(node, $"Unable to instantiate {dictionaryType.GetDisplayName()}");
foreach (XmlNode child in from XmlNode child in node.ChildNodes
Expand All @@ -139,7 +136,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, key.GetTypeToDeserialize() ?? keyType), serializer.Deserialize(value, value.GetTypeToDeserialize() ?? valueType),});
add.Invoke(dictionary, new[] {this.itemSerializer.Deserialize(key, key.GetTypeToDeserialize() ?? keyType), this.itemSerializer.Deserialize(value, value.GetTypeToDeserialize() ?? valueType),});
}
return dictionary;
}
Expand Down
1 change: 0 additions & 1 deletion Specialized/EnumSerializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
using System.Xml;

using Godot.Serialization.Utility.Extensions;
using Godot.Serialization.Utility.Exceptions;

namespace Godot.Serialization.Specialized
{
Expand Down
1 change: 0 additions & 1 deletion Specialized/EnumerableSerializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
using System.Linq;
using System.Xml;

using Godot.Serialization.Utility.Exceptions;
using Godot.Serialization.Utility.Extensions;

namespace Godot.Serialization.Specialized
Expand Down
1 change: 0 additions & 1 deletion Specialized/SimpleSerializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
using System.Linq;
using System.Xml;

using Godot.Serialization.Utility.Exceptions;
using Godot.Serialization.Utility.Extensions;

namespace Godot.Serialization.Specialized
Expand Down
1 change: 0 additions & 1 deletion Specialized/VectorSerializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
using System.Text.RegularExpressions;
using System.Xml;

using Godot.Serialization.Utility.Exceptions;
using Godot.Serialization.Utility.Extensions;

namespace Godot.Serialization.Specialized
Expand Down

0 comments on commit 989fb37

Please sign in to comment.