Open
Show file tree
Hide file tree
Changes from all commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Failed to load files.
Original file line numberDiff line numberDiff line change
Expand Up@@ -391,8 +391,7 @@ public static IDiscriminatorConvention LookupDiscriminatorConvention(Type type)
}
else if (typeInfo.IsInterface)
{
// TODO: should convention for interfaces be inherited from parent interfaces?
convention = LookupDiscriminatorConvention(typeof(object));
convention = CreateInterfaceDiscriminatorConvention(type);
RegisterDiscriminatorConvention(type, convention);
}
else
Expand DownExpand Up@@ -432,6 +431,12 @@ public static IDiscriminatorConvention LookupDiscriminatorConvention(Type type)
}
}

private static IDiscriminatorConvention CreateInterfaceDiscriminatorConvention(Type type)
{
var discriminatorConventionType = typeof(InterfaceDiscriminatorConvention<>).MakeGenericType(type);
return (IDiscriminatorConvention) Activator.CreateInstance(discriminatorConventionType, "_t");
}

/// <summary>
/// Looks up an IdGenerator.
/// </summary>
Expand Down
Original file line numberDiff line numberDiff line change
@@ -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
{
/// <summary>
/// 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.
/// </summary>
public class InterfaceDiscriminatorConvention<TInterface> : StandardDiscriminatorConvention
{
private readonly IDictionary<Type, BsonValue> _discriminators = new Dictionary<Type, BsonValue>();
// constructors
/// <summary>
/// Initializes a new instance of the HierarchicalDiscriminatorConvention class.
/// </summary>
/// <param name="elementName">The element name.</param>
public InterfaceDiscriminatorConvention(string elementName)
: base(elementName)
{
PrecomputeDiscriminators();
}

// public methods
/// <summary>
/// Gets the discriminator value for an actual type.
/// </summary>
/// <param name="nominalType">The nominal type.</param>
/// <param name="actualType">The actual type.</param>
/// <returns>The discriminator value.</returns>
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("<TInterface> must be an interface", nameof(TInterface));
}

var dependents = interfaceType.Assembly.GetTypes().Where(x => interfaceType.IsAssignableFrom(x));

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<Type> OrderInterfaces(List<Type> interfaces)
{
var sorted = new List<Type>();
while (interfaces.Any())
{
var allParentInterfaces = interfaces.SelectMany(t => t.GetInterfaces()).ToList();

foreach (var interfaceType in interfaces)
{
var newInterfaces = new List<Type>();

if (allParentInterfaces.Contains(interfaceType))
{
newInterfaces.Add(interfaceType);
}
else
{
sorted.Add(interfaceType);
}

interfaces = newInterfaces;
}
}

sorted.Reverse();

return sorted;
}
}
}
Original file line numberDiff line numberDiff line change
Expand Up@@ -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
Expand DownExpand Up@@ -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)
Expand DownExpand Up@@ -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();
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -56,7 +56,6 @@ private static IBsonSerializer<TInterface> CreateInterfaceSerializer()
private readonly Type _interfaceType;
private readonly IDiscriminatorConvention _discriminatorConvention;
private readonly IBsonSerializer<TInterface> _interfaceSerializer;
private readonly IBsonSerializer<object> _objectSerializer;

// constructors
/// <summary>
Expand DownExpand Up@@ -96,18 +95,6 @@ public DiscriminatedInterfaceSerializer(IDiscriminatorConvention discriminatorCo

_interfaceType = typeof(TInterface);
_discriminatorConvention = discriminatorConvention ?? BsonSerializer.LookupDiscriminatorConvention(typeof(TInterface));
_objectSerializer = BsonSerializer.LookupSerializer<object>();
if (_objectSerializer is ObjectSerializer standardObjectSerializer)
{
_objectSerializer = standardObjectSerializer.WithDiscriminatorConvention(_discriminatorConvention);
}
else
{
if (discriminatorConvention != null)
{
throw new BsonSerializationException("Can't set discriminator convention on custom object serializer.");
}
}

_interfaceSerializer = interfaceSerializer;
}
Expand DownExpand Up@@ -164,12 +151,12 @@ public override void Serialize(BsonSerializationContext context, BsonSerializati
if (value == null)
{
bsonWriter.WriteNull();
return;
}
else
{
args.NominalType = typeof(object);
_objectSerializer.Serialize(context, args, value);
}

args.DiscriminatorConvention = _discriminatorConvention;
var serializer = BsonSerializer.LookupSerializer(value.GetType());
serializer.Serialize(context, args, value);
}

/// <inheritdoc/>
Expand Down
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
/* 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.Linq;
using MongoDB.Bson;
using MongoDB.Bson.Serialization;
using MongoDB.Bson.Serialization.Conventions;
using MongoDB.Bson.Serialization.Options;
using MongoDB.Bson.Serialization.Serializers;
using Xunit;

namespace MongoDB.Bson.Tests.Serialization
{
public class AnimalInterfaceHierarchyWithoutAttributesTests
{
public interface IAnimal
{
public ObjectId Id { get; set; }
public int Age { get; set; }
public string Name { get; set; }
}

public class Bear : IAnimal
{
public ObjectId Id { get; set; }
public int Age { get; set; }
public string Name { get; set; }
}

public interface ICat : IAnimal
{
}

public class Tiger : ICat
{
public ObjectId Id { get; set; }
public int Age { get; set; }
public string Name { get; set; }
}

public class Lion : ICat
{
public ObjectId Id { get; set; }
public int Age { get; set; }
public string Name { get; set; }
}

static AnimalInterfaceHierarchyWithoutAttributesTests()
{
BsonSerializer.RegisterSerializer(new DiscriminatedInterfaceSerializer<IAnimal>(new InterfaceDiscriminatorConvention<IAnimal>("_t")));
BsonClassMap.RegisterClassMap<Bear>();
BsonClassMap.RegisterClassMap<Tiger>();
BsonClassMap.RegisterClassMap<Lion>();
}

[Fact]
public void TestDeserializeBear()
{
var document = new BsonDocument
{
{ "_id", ObjectId.Empty },
{ "_t", new BsonArray { "IAnimal", "Bear" } },
{ "Age", 123 },
{ "Name", "Panda Bear" }
};

var bson = document.ToBson();
var rehydrated = (Bear)BsonSerializer.Deserialize<IAnimal>(bson);
Assert.IsType<Bear>(rehydrated);

var json = rehydrated.ToJson<IAnimal>(args: new BsonSerializationArgs { SerializeIdFirst = true });
var expected = "{ '_id' : ObjectId('000000000000000000000000'), '_t' : ['IAnimal', 'Bear'], 'Age' : 123, 'Name' : 'Panda Bear' }".Replace("'", "\"");
Assert.Equal(expected, json);
Assert.True(bson.SequenceEqual(rehydrated.ToBson<IAnimal>(args: new BsonSerializationArgs { SerializeIdFirst = true })));
}

[Fact]
public void TestDeserializeTiger()
{
var document = new BsonDocument
{
{ "_id", ObjectId.Empty },
{ "_t", new BsonArray { "IAnimal", "ICat", "Tiger" } },
{ "Age", 234 },
{ "Name", "Striped Tiger" }
};

var bson = document.ToBson();
var rehydrated = (Tiger)BsonSerializer.Deserialize<IAnimal>(bson);
Assert.IsType<Tiger>(rehydrated);

var json = rehydrated.ToJson<IAnimal>(args: new BsonSerializationArgs { SerializeIdFirst = true });
var expected = "{ '_id' : ObjectId('000000000000000000000000'), '_t' : ['IAnimal', 'ICat', 'Tiger'], 'Age' : 234, 'Name' : 'Striped Tiger' }".Replace("'", "\"");
Assert.Equal(expected, json);
Assert.True(bson.SequenceEqual(rehydrated.ToBson<IAnimal>(args: new BsonSerializationArgs { SerializeIdFirst = true })));
}

[Fact]
public void TestDeserializeLion()
{
var document = new BsonDocument
{
{ "_id", ObjectId.Empty },
{ "_t", new BsonArray { "IAnimal", "ICat", "Lion" } },
{ "Age", 234 },
{ "Name", "King Lion" }
};

var bson = document.ToBson();
var rehydrated = (Lion)BsonSerializer.Deserialize<IAnimal>(bson);
Assert.IsType<Lion>(rehydrated);

var json = rehydrated.ToJson<IAnimal>(args: new BsonSerializationArgs { SerializeIdFirst = true });
var expected = "{ '_id' : ObjectId('000000000000000000000000'), '_t' : ['IAnimal', 'ICat', 'Lion'], 'Age' : 234, 'Name' : 'King Lion' }".Replace("'", "\"");
Assert.Equal(expected, json);
Assert.True(bson.SequenceEqual(rehydrated.ToBson<IAnimal>(args: new BsonSerializationArgs { SerializeIdFirst = true })));
}
}
}