Skip to content

Commit fe99fcc

Browse files
committed
Prototype using public read-only property in POCO as discriminator
1 parent 2d60432 commit fe99fcc

File tree

4 files changed

+199
-4
lines changed

4 files changed

+199
-4
lines changed
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/* Copyright 2010-present MongoDB Inc.
2+
*
3+
* Licensed under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*/
15+
16+
using System;
17+
18+
namespace MongoDB.Bson.Serialization.Attributes;
19+
20+
/// <summary>
21+
/// Identifies a class member to be used as the discriminator.
22+
/// </summary>
23+
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
24+
public class BsonDiscriminatorMemberAttribute : Attribute, IBsonMemberMapAttribute
25+
{
26+
private readonly string _elementName;
27+
28+
/// <summary>
29+
/// Initializes a new instance of BsonDiscriminatorMemberAttribute.
30+
/// </summary>
31+
public BsonDiscriminatorMemberAttribute()
32+
{
33+
}
34+
35+
/// <summary>
36+
/// Initializes a new instance of BsonDiscriminatorMemberAttribute.
37+
/// </summary>
38+
/// <param name="elementName">The element name.</param>
39+
public BsonDiscriminatorMemberAttribute(string elementName)
40+
{
41+
_elementName = elementName;
42+
}
43+
44+
/// <inheritdoc/>
45+
public void Apply(BsonMemberMap memberMap)
46+
{
47+
var classMap = memberMap.ClassMap;
48+
classMap.SetDiscriminatorMember(memberMap);
49+
50+
if (_elementName != null)
51+
{
52+
memberMap.SetElementName(_elementName);
53+
}
54+
}
55+
}

src/MongoDB.Bson/Serialization/BsonClassMap.cs

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ public class BsonClassMap
5252
private Func<object> _creator;
5353
private string _discriminator;
5454
private bool _discriminatorIsRequired;
55+
private BsonMemberMap _discriminatorMemberMap;
5556
private bool _hasRootClass;
5657
private bool _isRootClass;
5758
private BsonMemberMap _idMemberMap;
@@ -156,6 +157,14 @@ public bool DiscriminatorIsRequired
156157
get { return _discriminatorIsRequired; }
157158
}
158159

160+
/// <summary>
161+
/// Gets the discriminator member map (null if none).
162+
/// </summary>
163+
public BsonMemberMap DiscriminatorMemberMap
164+
{
165+
get { return _discriminatorMemberMap; }
166+
}
167+
159168
/// <summary>
160169
/// Gets the member map of the member used to hold extra elements.
161170
/// </summary>
@@ -637,6 +646,15 @@ public BsonClassMap Freeze()
637646
}
638647
}
639648

649+
if (_discriminatorMemberMap == null)
650+
{
651+
// see if we can inherit the discriminatorMemberMap from our base class
652+
if (_baseClassMap != null)
653+
{
654+
_discriminatorMemberMap = _baseClassMap.DiscriminatorMemberMap;
655+
}
656+
}
657+
640658
if (_extraElementsMemberMap == null)
641659
{
642660
// see if we can inherit the extraElementsMemberMap from our base class
@@ -1118,6 +1136,29 @@ public void SetDiscriminatorIsRequired(bool discriminatorIsRequired)
11181136
_discriminatorIsRequired = discriminatorIsRequired;
11191137
}
11201138

1139+
/// <summary>
1140+
/// Sets the discriminator member.
1141+
/// </summary>
1142+
/// <param name="memberMap">The discriminator member (null if none).</param>
1143+
public void SetDiscriminatorMember(BsonMemberMap memberMap)
1144+
{
1145+
if (memberMap != null)
1146+
{
1147+
EnsureMemberMapIsForThisClass(memberMap);
1148+
1149+
if (memberMap.MemberInfo is not PropertyInfo propertyInfo ||
1150+
propertyInfo.CanWrite ||
1151+
!propertyInfo.GetMethod.IsVirtual)
1152+
{
1153+
throw new ArgumentException("The discriminator member must be a virtual read-only property.", nameof(memberMap));
1154+
}
1155+
}
1156+
1157+
if (_frozen) { ThrowFrozenException(); }
1158+
1159+
_discriminatorMemberMap = memberMap;
1160+
}
1161+
11211162
/// <summary>
11221163
/// Sets the member map of the member used to hold extra elements.
11231164
/// </summary>
@@ -1329,7 +1370,17 @@ internal IDiscriminatorConvention GetDiscriminatorConvention()
13291370

13301371
if (discriminatorConvention != null)
13311372
{
1332-
EnsureNoMemberMapConflicts(discriminatorConvention.ElementName);
1373+
if (_discriminatorMemberMap == null)
1374+
{
1375+
EnsureNoMemberMapConflicts(discriminatorConvention.ElementName);
1376+
}
1377+
else
1378+
{
1379+
if (_discriminatorMemberMap.ElementName != discriminatorConvention.ElementName)
1380+
{
1381+
throw new BsonSerializationException("Discriminator convention and disciminator map element names do not match.");
1382+
}
1383+
}
13331384
}
13341385
}
13351386

src/MongoDB.Bson/Serialization/Serializers/BsonClassMapSerializer.cs

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -581,7 +581,7 @@ private void SerializeClass(BsonSerializationContext context, BsonSerializationA
581581

582582
if (ShouldSerializeDiscriminator(args.NominalType))
583583
{
584-
SerializeDiscriminator(context, args.NominalType, document);
584+
SerializeDiscriminator(context, args.NominalType, document.GetType());
585585
}
586586

587587
foreach (var memberMap in _classMap.AllMemberMaps)
@@ -625,12 +625,11 @@ private void SerializeExtraElements(BsonSerializationContext context, object obj
625625
}
626626
}
627627

628-
private void SerializeDiscriminator(BsonSerializationContext context, Type nominalType, object obj)
628+
private void SerializeDiscriminator(BsonSerializationContext context, Type nominalType, Type actualType)
629629
{
630630
var discriminatorConvention = _classMap.GetDiscriminatorConvention();
631631
if (discriminatorConvention != null)
632632
{
633-
var actualType = obj.GetType();
634633
var discriminator = discriminatorConvention.GetDiscriminator(nominalType, actualType);
635634
if (discriminator != null)
636635
{
@@ -642,6 +641,14 @@ private void SerializeDiscriminator(BsonSerializationContext context, Type nomin
642641

643642
private void SerializeMember(BsonSerializationContext context, object obj, BsonMemberMap memberMap)
644643
{
644+
if (memberMap == _classMap.DiscriminatorMemberMap)
645+
{
646+
// the discriminator has already been serialized
647+
// verify that the value of the read-only property matches the discriminator value returned by the discriminator convention
648+
VerifyDiscriminatorMemberValue(obj, memberMap);
649+
return;
650+
}
651+
645652
try
646653
{
647654
if (memberMap != _classMap.ExtraElementsMemberMap)
@@ -682,6 +689,26 @@ private bool ShouldSerializeDiscriminator(Type nominalType)
682689
return (nominalType != _classMap.ClassType || _classMap.DiscriminatorIsRequired || _classMap.HasRootClass) && !_classMap.IsAnonymous;
683690
}
684691

692+
private void VerifyDiscriminatorMemberValue(object obj, BsonMemberMap discriminatorMemberMap)
693+
{
694+
var discriminatorConvention = _classMap.GetDiscriminatorConvention();
695+
if (discriminatorConvention != null)
696+
{
697+
var discriminatorMemberSerializer = discriminatorMemberMap.GetSerializer();
698+
var discriminatorMemberValue = discriminatorMemberMap.Getter(obj);
699+
var serializedDiscriminatorMemberValue = discriminatorMemberSerializer.ToBsonValue(discriminatorMemberValue);
700+
701+
var nominalType = _classMap.ClassType;
702+
var actualType = obj.GetType();
703+
var conventionDiscriminatorValue = discriminatorConvention.GetDiscriminator(nominalType, actualType);
704+
705+
if (!object.Equals(serializedDiscriminatorMemberValue, conventionDiscriminatorValue))
706+
{
707+
throw new BsonSerializationException("Discriminator member value does not match discriminator convention GetDiscriminator return value.");
708+
}
709+
}
710+
}
711+
685712
// nested classes
686713
// helper class that implements member map bit array helper functions
687714
internal static class FastMemberMapHelper
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/* Copyright 2010-present MongoDB Inc.
2+
*
3+
* Licensed under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*/
15+
16+
using System;
17+
using System.Collections.Generic;
18+
using FluentAssertions;
19+
using MongoDB.Bson.Serialization;
20+
using MongoDB.Bson.Serialization.Attributes;
21+
using Xunit;
22+
23+
namespace MongoDB.Bson.Tests.Jira;
24+
25+
public class PublicDiscriminatorTests
26+
{
27+
[Fact]
28+
public void Deserialize_with_public_discriminator_should_work()
29+
{
30+
var json = "{ _id : 1, _t : ['C', 'D'] }";
31+
var serializer = BsonSerializer.LookupSerializer<C>();
32+
33+
var document = BsonSerializer.Deserialize<C>(json);
34+
35+
document.Should().BeOfType<D>();
36+
document.Id.Should().Be(1);
37+
document.Discriminator.Should().Equal("C", "D");
38+
}
39+
40+
[Fact]
41+
public void Serialize_with_public_discriminator_should_work()
42+
{
43+
var document = new D { Id = 1 };
44+
45+
var serializedDocument = document.ToBsonDocument<C>(args: new BsonSerializationArgs { SerializeIdFirst = true });
46+
47+
serializedDocument.Should().Be("{ _id : 1, _t : ['C', 'D'] }");
48+
}
49+
50+
[BsonDiscriminator(RootClass = true)]
51+
[BsonKnownTypes(typeof(D))]
52+
public abstract class C
53+
{
54+
public int Id { get; set; }
55+
[BsonDiscriminatorMember("_t")] public virtual IReadOnlyList<string> Discriminator => ["C"];
56+
}
57+
58+
public sealed class D : C
59+
{
60+
public override IReadOnlyList<string> Discriminator => ["C", "D"];
61+
}
62+
}

0 commit comments

Comments
 (0)