From 2d89ee440206354c59ea2c993b48e81f8c278cc0 Mon Sep 17 00:00:00 2001
From: LucStr <25279790+LucStr@users.noreply.github.com>
Date: Sun, 14 Jan 2024 18:18:16 +0100
Subject: [PATCH 1/2] Add support for root interface serialization
---
.../Serialization/BsonSerializationArgs.cs | 16 ++-
.../InterfaceDiscriminatorConvention.cs | 108 +++++++++++++++
.../Serializers/BsonClassMapSerializer.cs | 8 +-
.../DiscriminatedInterfaceSerializer.cs | 23 +--
...nterfaceHierarchyWithoutAttributesTests.cs | 131 ++++++++++++++++++
5 files changed, 264 insertions(+), 22 deletions(-)
create mode 100644 src/MongoDB.Bson/Serialization/Conventions/InterfaceDiscriminatorConvention.cs
create mode 100644 tests/MongoDB.Bson.Tests/Serialization/Serializers/AnimalInterfaceHierarchyWithoutAttributesTests.cs
diff --git a/src/MongoDB.Bson/Serialization/BsonSerializationArgs.cs b/src/MongoDB.Bson/Serialization/BsonSerializationArgs.cs
index b16d5a3e1b1..e02daf88828 100644
--- a/src/MongoDB.Bson/Serialization/BsonSerializationArgs.cs
+++ b/src/MongoDB.Bson/Serialization/BsonSerializationArgs.cs
@@ -14,6 +14,7 @@
*/
using System;
+using MongoDB.Bson.Serialization.Conventions;
namespace MongoDB.Bson.Serialization
{
@@ -24,6 +25,7 @@ public struct BsonSerializationArgs
{
// private fields
private Type _nominalType;
+ private IDiscriminatorConvention _discriminatorConvention;
private bool _serializeAsNominalType;
private bool _serializeIdFirst;
@@ -34,14 +36,17 @@ public struct BsonSerializationArgs
/// The nominal type.
/// Whether to serialize as the nominal type.
/// Whether to serialize the id first.
+ /// The discriminator convention to be used.
public BsonSerializationArgs(
Type nominalType,
bool serializeAsNominalType,
- bool serializeIdFirst)
+ bool serializeIdFirst,
+ IDiscriminatorConvention discriminatorConvention)
{
_nominalType = nominalType;
_serializeAsNominalType = serializeAsNominalType;
_serializeIdFirst = serializeIdFirst;
+ _discriminatorConvention = discriminatorConvention;
}
// public properties
@@ -57,6 +62,15 @@ public Type NominalType
set { _nominalType = value; }
}
+ ///
+ /// Gets or sets the discriminator convention.
+ ///
+ public IDiscriminatorConvention DiscriminatorConvention
+ {
+ get { return _discriminatorConvention; }
+ set { _discriminatorConvention = value; }
+ }
+
///
/// Gets or sets a value indicating whether to serialize the value as if it were an instance of the nominal type.
///
diff --git a/src/MongoDB.Bson/Serialization/Conventions/InterfaceDiscriminatorConvention.cs b/src/MongoDB.Bson/Serialization/Conventions/InterfaceDiscriminatorConvention.cs
new file mode 100644
index 00000000000..e1f2aee5f37
--- /dev/null
+++ b/src/MongoDB.Bson/Serialization/Conventions/InterfaceDiscriminatorConvention.cs
@@ -0,0 +1,108 @@
+/* Copyright 2010-present MongoDB Inc.
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace MongoDB.Bson.Serialization.Conventions
+{
+ ///
+ /// Represents a discriminator convention where the discriminator is an array of all the discriminators provided by the class maps of the root class down to the actual type.
+ ///
+ public class InterfaceDiscriminatorConvention : StandardDiscriminatorConvention
+ {
+ private readonly IDictionary _discriminators = new Dictionary();
+ // constructors
+ ///
+ /// Initializes a new instance of the HierarchicalDiscriminatorConvention class.
+ ///
+ /// The element name.
+ public InterfaceDiscriminatorConvention(string elementName)
+ : base(elementName)
+ {
+ PrecomputeDiscriminators();
+ }
+
+ // public methods
+ ///
+ /// Gets the discriminator value for an actual type.
+ ///
+ /// The nominal type.
+ /// The actual type.
+ /// The discriminator value.
+ public override BsonValue GetDiscriminator(Type nominalType, Type actualType)
+ {
+ if (nominalType != typeof(TInterface))
+ {
+ return null;
+ }
+
+ return _discriminators.TryGetValue(actualType, out var discriminator) ? discriminator : null;
+ }
+
+ private void PrecomputeDiscriminators()
+ {
+ var interfaceType = typeof(TInterface);
+
+ if (!interfaceType.IsInterface)
+ {
+ throw new ArgumentException(" must be an interface", nameof(TInterface));
+ }
+
+ var dependents = interfaceType.Assembly.GetTypes().Where(x => interfaceType.IsAssignableFrom(x) && x.IsClass);
+
+ foreach (var dependent in dependents)
+ {
+ var interfaces = OrderInterfaces(dependent.GetInterfaces().ToList());
+ var discriminator = new BsonArray(interfaces.Select(x => x.Name))
+ {
+ dependent.Name
+ };
+
+ _discriminators.Add(dependent, discriminator);
+ }
+ }
+
+ private IEnumerable OrderInterfaces(List interfaces)
+ {
+ var sorted = new List();
+ while (interfaces.Any())
+ {
+ var allParentInterfaces = interfaces.SelectMany(t => t.GetInterfaces()).ToList();
+
+ foreach (var interfaceType in interfaces)
+ {
+ var newInterfaces = new List();
+
+ if (allParentInterfaces.Contains(interfaceType))
+ {
+ newInterfaces.Add(interfaceType);
+ }
+ else
+ {
+ sorted.Add(interfaceType);
+ }
+
+ interfaces = newInterfaces;
+ }
+ }
+
+ sorted.Reverse();
+
+ return sorted;
+ }
+ }
+}
diff --git a/src/MongoDB.Bson/Serialization/Serializers/BsonClassMapSerializer.cs b/src/MongoDB.Bson/Serialization/Serializers/BsonClassMapSerializer.cs
index e2c7d6dfe6b..57af65f29b5 100644
--- a/src/MongoDB.Bson/Serialization/Serializers/BsonClassMapSerializer.cs
+++ b/src/MongoDB.Bson/Serialization/Serializers/BsonClassMapSerializer.cs
@@ -18,6 +18,7 @@
using System.ComponentModel;
using System.Reflection;
using MongoDB.Bson.IO;
+using MongoDB.Bson.Serialization.Conventions;
using MongoDB.Bson.Serialization.Serializers;
namespace MongoDB.Bson.Serialization
@@ -567,7 +568,7 @@ private void SerializeClass(BsonSerializationContext context, BsonSerializationA
if (ShouldSerializeDiscriminator(args.NominalType))
{
- SerializeDiscriminator(context, args.NominalType, document);
+ SerializeDiscriminator(context, args.NominalType, document, args.DiscriminatorConvention);
}
foreach (var memberMap in _classMap.AllMemberMaps)
@@ -611,9 +612,10 @@ private void SerializeExtraElements(BsonSerializationContext context, object obj
}
}
- private void SerializeDiscriminator(BsonSerializationContext context, Type nominalType, object obj)
+ private void SerializeDiscriminator(BsonSerializationContext context, Type nominalType, object obj, IDiscriminatorConvention discriminatorConvention)
{
- var discriminatorConvention = _classMap.GetDiscriminatorConvention();
+ discriminatorConvention ??= _classMap.GetDiscriminatorConvention();
+
if (discriminatorConvention != null)
{
var actualType = obj.GetType();
diff --git a/src/MongoDB.Bson/Serialization/Serializers/DiscriminatedInterfaceSerializer.cs b/src/MongoDB.Bson/Serialization/Serializers/DiscriminatedInterfaceSerializer.cs
index 91ca46ef2d0..ff26505a4ce 100644
--- a/src/MongoDB.Bson/Serialization/Serializers/DiscriminatedInterfaceSerializer.cs
+++ b/src/MongoDB.Bson/Serialization/Serializers/DiscriminatedInterfaceSerializer.cs
@@ -56,7 +56,6 @@ private static IBsonSerializer CreateInterfaceSerializer()
private readonly Type _interfaceType;
private readonly IDiscriminatorConvention _discriminatorConvention;
private readonly IBsonSerializer _interfaceSerializer;
- private readonly IBsonSerializer