Add AbstractDictionarySerializer (#4276)

This commit is contained in:
Leon Friedrich
2023-08-23 20:53:13 +12:00
committed by GitHub
parent f05ed96461
commit d2311c193f
2 changed files with 164 additions and 0 deletions

View File

@@ -0,0 +1,72 @@
using System;
using System.Collections.Generic;
using Robust.Shared.IoC;
using Robust.Shared.Serialization.Manager;
using Robust.Shared.Serialization.Markdown;
using Robust.Shared.Serialization.Markdown.Mapping;
using Robust.Shared.Serialization.Markdown.Validation;
using Robust.Shared.Serialization.Markdown.Value;
using Robust.Shared.Serialization.TypeSerializers.Interfaces;
namespace Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
/// <summary>
/// A custom type serializer for reading a set of types that inherit from some base type.
/// </summary>
public sealed class AbstractDictionarySerializer<TValue> : ITypeSerializer<Dictionary<Type, TValue>, MappingDataNode>
where TValue : notnull
{
public ValidationNode Validate(ISerializationManager serializationManager, MappingDataNode node,
IDependencyCollection dependencies, ISerializationContext? context = null)
{
var mapping = new Dictionary<ValidationNode, ValidationNode>();
foreach (var (keyNode, valueNode) in node.Children)
{
if (keyNode is not ValueDataNode key)
{
mapping.Add(new ErrorNode(keyNode, $"Expected {nameof(ValueDataNode)} but was {keyNode.GetType()}"), new ValidatedValueNode(valueNode));
continue;
}
var type = serializationManager.ReflectionManager.YamlTypeTagLookup(typeof(TValue), key.Value);
if (type == null)
{
mapping.Add(new ErrorNode(keyNode, $"Could not resolve type: {key.Value}"), new ValidatedValueNode(valueNode));
continue;
}
mapping.Add(new ValidatedValueNode(key), serializationManager.ValidateNode(type, valueNode, context));
}
return new ValidatedMappingNode(mapping);
}
public Dictionary<Type, TValue> Read(ISerializationManager serializationManager, MappingDataNode node, IDependencyCollection dependencies,
SerializationHookContext hookCtx, ISerializationContext? context = null, ISerializationManager.InstantiationDelegate<Dictionary<Type, TValue>>? instanceProvider = null)
{
var dict = instanceProvider != null ? instanceProvider() : new Dictionary<Type, TValue>();
foreach (var (keyNode, valueNode) in node.Children)
{
var key = (ValueDataNode) keyNode;
var type = serializationManager.ReflectionManager.YamlTypeTagLookup(typeof(TValue), key.Value)!;
var value = (TValue) serializationManager.Read(type, valueNode, hookCtx, context, notNullableOverride:true)!;
dict.Add(type, value);
}
return dict;
}
public DataNode Write(ISerializationManager serializationManager, Dictionary<Type, TValue> value, IDependencyCollection dependencies,
bool alwaysWrite = false, ISerializationContext? context = null)
{
var mappingNode = new MappingDataNode();
foreach (var (key, val) in value)
{
mappingNode.Add(
serializationManager.WriteValue(key.Name, alwaysWrite, context, notNullableOverride:true),
serializationManager.WriteValue(key, val, alwaysWrite, context));
}
return mappingNode;
}
}

View File

@@ -0,0 +1,92 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using NUnit.Framework;
using Robust.Shared.IoC;
using Robust.Shared.Serialization.Manager;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.Serialization.Markdown;
using Robust.Shared.Serialization.Markdown.Mapping;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
using YamlDotNet.RepresentationModel;
namespace Robust.UnitTesting.Shared.Serialization.TypeSerializers.Custom;
[TestFixture]
[TestOf(typeof(AbstractDictionarySerializer<>))]
public sealed class AbstractDictionarySerializerTest : RobustUnitTest
{
private const string TestYaml = @"
SealedTestTypeA:
x: 1
y: 2
SealedTestTypeB:
x: 3
z: 4
";
[Test]
public void SerializationTest()
{
var seri = IoCManager.Resolve<ISerializationManager>();
seri.Initialize();
var stream = new YamlStream();
stream.Load(new StringReader(TestYaml));
var node = (MappingDataNode)stream.Documents[0].RootNode.ToDataNode();
var validation = seri.ValidateNode<Dictionary<Type, AbstractTestData>,
MappingDataNode,
AbstractDictionarySerializer<AbstractTestData>>
(node);
Assert.That(validation.GetErrors().Count(), Is.EqualTo(0));
var data = seri.Read<Dictionary<Type, AbstractTestData>,
MappingDataNode,
AbstractDictionarySerializer<AbstractTestData>>
(node, notNullableOverride:true);
Assert.NotNull(data);
Assert.That(data.Count, Is.EqualTo(2));
Assert.That(data.ContainsKey(typeof(SealedTestTypeA)));
Assert.That(data.ContainsKey(typeof(SealedTestTypeB)));
var a = data[typeof(SealedTestTypeA)] as SealedTestTypeA;
var b = data[typeof(SealedTestTypeB)] as SealedTestTypeB;
Assert.NotNull(a);
Assert.NotNull(b);
Assert.That(a!.X, Is.EqualTo(1));
Assert.That(a.Y, Is.EqualTo(2));
Assert.That(b!.X, Is.EqualTo(3));
Assert.That(b.Z, Is.EqualTo(4));
var newNode = (MappingDataNode)seri.WriteValue<Dictionary<Type, AbstractTestData>,
AbstractDictionarySerializer<AbstractTestData>>
(data, notNullableOverride:true);
Assert.Null(node.Except(newNode));
validation = seri.ValidateNode<Dictionary<Type, AbstractTestData>,
MappingDataNode,
AbstractDictionarySerializer<AbstractTestData>>
(newNode);
Assert.That(validation.GetErrors().Count(), Is.EqualTo(0));
}
[ImplicitDataDefinitionForInheritors]
private abstract class AbstractTestData
{
[DataField("x")] public int X;
}
private sealed class SealedTestTypeA : AbstractTestData
{
[DataField("y")] public int Y;
}
private sealed class SealedTestTypeB : AbstractTestData
{
[DataField("z")] public int Z;
}
}