Skip to content

Commit 1369f83

Browse files
rstamDmitryLukyanov
authored andcommitted
CSHARP-4511: Allow separate allowed types for deserialization and serialization.
1 parent bf94685 commit 1369f83

File tree

4 files changed

+338
-41
lines changed

4 files changed

+338
-41
lines changed

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

Lines changed: 59 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,8 @@ public class ObjectSerializer : ClassSerializerBase<object>
5656
#endregion
5757

5858
// private fields
59-
private readonly Func<Type, bool> _allowedTypes;
59+
private readonly Func<Type, bool> _allowedDeserializationTypes;
60+
private readonly Func<Type, bool> _allowedSerializationTypes;
6061
private readonly IDiscriminatorConvention _discriminatorConvention;
6162
private readonly GuidRepresentation _guidRepresentation;
6263
private readonly GuidSerializer _guidSerializer;
@@ -116,22 +117,51 @@ public ObjectSerializer(IDiscriminatorConvention discriminatorConvention, Func<T
116117
/// <param name="guidRepresentation">The Guid representation.</param>
117118
/// <param name="allowedTypes">A delegate that determines what types are allowed.</param>
118119
public ObjectSerializer(IDiscriminatorConvention discriminatorConvention, GuidRepresentation guidRepresentation, Func<Type, bool> allowedTypes)
120+
: this(discriminatorConvention, guidRepresentation, allowedTypes ?? throw new ArgumentNullException(nameof(allowedTypes)), allowedTypes)
119121
{
120-
if (discriminatorConvention == null)
121-
{
122-
throw new ArgumentNullException("discriminatorConvention");
123-
}
124-
if (allowedTypes == null)
125-
{
126-
throw new ArgumentNullException(nameof(allowedTypes));
127-
}
122+
}
128123

129-
_discriminatorConvention = discriminatorConvention;
124+
/// <summary>
125+
/// Initializes a new instance of the <see cref="ObjectSerializer"/> class.
126+
/// </summary>
127+
/// <param name="discriminatorConvention">The discriminator convention.</param>
128+
/// <param name="guidRepresentation">The Guid representation.</param>
129+
/// <param name="allowedDeserializationTypes">A delegate that determines what types are allowed to be deserialized.</param>
130+
/// <param name="allowedSerializationTypes">A delegate that determines what types are allowed to be serialized.</param>
131+
public ObjectSerializer(
132+
IDiscriminatorConvention discriminatorConvention,
133+
GuidRepresentation guidRepresentation,
134+
Func<Type, bool> allowedDeserializationTypes,
135+
Func<Type, bool> allowedSerializationTypes)
136+
{
137+
_discriminatorConvention = discriminatorConvention ?? throw new ArgumentNullException(nameof(discriminatorConvention));
130138
_guidRepresentation = guidRepresentation;
131139
_guidSerializer = new GuidSerializer(_guidRepresentation);
132-
_allowedTypes = allowedTypes;
140+
_allowedDeserializationTypes = allowedDeserializationTypes ?? throw new ArgumentNullException(nameof(allowedDeserializationTypes));
141+
_allowedSerializationTypes = allowedSerializationTypes ?? throw new ArgumentNullException(nameof(allowedSerializationTypes));
133142
}
134143

144+
// public properties
145+
/// <summary>
146+
/// Gets the AllowedDeserializationTypes filter;
147+
/// </summary>
148+
public Func<Type, bool> AllowedDeserializationTypes => _allowedDeserializationTypes;
149+
150+
/// <summary>
151+
/// Gets the AllowedSerializationTypes filter;
152+
/// </summary>
153+
public Func<Type, bool> AllowedSerializationTypes => _allowedSerializationTypes;
154+
155+
/// <summary>
156+
/// Gets the discriminator convention.
157+
/// </summary>
158+
public IDiscriminatorConvention DiscriminatorConvention => _discriminatorConvention;
159+
160+
/// <summary>
161+
/// Gets the GuidRepresentation.
162+
/// </summary>
163+
public GuidRepresentation GuidRepresentation => _guidRepresentation;
164+
135165
// public methods
136166
/// <summary>
137167
/// Deserializes a value.
@@ -221,7 +251,8 @@ public override object Deserialize(BsonDeserializationContext context, BsonDeser
221251
public override bool Equals(object obj) =>
222252
obj is ObjectSerializer other &&
223253
GetType() == other.GetType() &&
224-
_allowedTypes.Equals(other._allowedTypes) &&
254+
_allowedDeserializationTypes.Equals(other._allowedDeserializationTypes) &&
255+
_allowedSerializationTypes.Equals(other._allowedSerializationTypes) &&
225256
_discriminatorConvention.Equals(other._discriminatorConvention) &&
226257
_guidRepresentation == other._guidRepresentation;
227258

@@ -342,7 +373,7 @@ public override void Serialize(BsonSerializationContext context, BsonSerializati
342373
/// <returns>An ObjectSerializer with the specified discriminator convention.</returns>
343374
public ObjectSerializer WithDiscriminatorConvention(IDiscriminatorConvention discriminatorConvention)
344375
{
345-
return new ObjectSerializer(discriminatorConvention, _guidRepresentation, _allowedTypes);
376+
return new ObjectSerializer(discriminatorConvention, _guidRepresentation, _allowedDeserializationTypes, _allowedSerializationTypes);
346377
}
347378

348379
// private methods
@@ -351,9 +382,9 @@ private object DeserializeDiscriminatedValue(BsonDeserializationContext context,
351382
var bsonReader = context.Reader;
352383

353384
var actualType = _discriminatorConvention.GetActualType(bsonReader, typeof(object));
354-
if (!_allowedTypes(actualType))
385+
if (!_allowedDeserializationTypes(actualType))
355386
{
356-
throw new BsonSerializationException($"Type {actualType.FullName} is not configured as an allowed type for this instance of ObjectSerializer.");
387+
throw new BsonSerializationException($"Type {actualType.FullName} is not configured as a type that is allowed to be deserialized for this instance of ObjectSerializer.");
357388
}
358389

359390
if (actualType == typeof(object))
@@ -419,9 +450,9 @@ private object DeserializeDiscriminatedValue(BsonDeserializationContext context,
419450

420451
private void SerializeDiscriminatedValue(BsonSerializationContext context, BsonSerializationArgs args, object value, Type actualType)
421452
{
422-
if (!_allowedTypes(actualType))
453+
if (!_allowedSerializationTypes(actualType))
423454
{
424-
throw new BsonSerializationException($"Type {actualType.FullName} is not configured as an allowed type for this instance of ObjectSerializer.");
455+
throw new BsonSerializationException($"Type {actualType.FullName} is not configured as a type that is allowed to be serialized for this instance of ObjectSerializer.");
425456
}
426457

427458
var serializer = BsonSerializer.LookupSerializer(actualType);
@@ -456,7 +487,9 @@ private void SerializeDiscriminatedValue(BsonSerializationContext context, BsonS
456487
// nested types
457488
private static class DefaultFrameworkAllowedTypes
458489
{
459-
private readonly static HashSet<Type> __allowedTypes = new HashSet<Type>
490+
private readonly static Func<Type, bool> __allowedTypes = AllowedTypesImplementation;
491+
492+
private readonly static HashSet<Type> __allowedNonGenericTypesSet = new HashSet<Type>
460493
{
461494
typeof(System.Boolean),
462495
typeof(System.Byte),
@@ -496,7 +529,7 @@ private static class DefaultFrameworkAllowedTypes
496529
typeof(System.Version)
497530
};
498531

499-
private readonly static HashSet<Type> __allowedGenericTypes = new HashSet<Type>
532+
private readonly static HashSet<Type> __allowedGenericTypesSet = new HashSet<Type>
500533
{
501534
typeof(System.Collections.Generic.Dictionary<,>),
502535
typeof(System.Collections.Generic.HashSet<>),
@@ -534,18 +567,20 @@ private static class DefaultFrameworkAllowedTypes
534567
typeof(System.ValueTuple<,,,,,,,>)
535568
};
536569

537-
public static bool AllowedTypes(Type type)
570+
public static Func<Type, bool> AllowedTypes => __allowedTypes;
571+
572+
private static bool AllowedTypesImplementation(Type type)
538573
{
539574
return type.IsConstructedGenericType ? IsAllowedGenericType(type) : IsAllowedType(type);
540575

541576
static bool IsAllowedType(Type type) =>
542-
__allowedTypes.Contains(type) ||
543-
type.IsArray && AllowedTypes(type.GetElementType()) ||
577+
__allowedNonGenericTypesSet.Contains(type) ||
578+
type.IsArray && AllowedTypesImplementation(type.GetElementType()) ||
544579
type.IsEnum;
545580

546581
static bool IsAllowedGenericType(Type type) =>
547-
__allowedGenericTypes.Contains(type.GetGenericTypeDefinition()) &&
548-
type.GetGenericArguments().All(AllowedTypes);
582+
__allowedGenericTypesSet.Contains(type.GetGenericTypeDefinition()) &&
583+
type.GetGenericArguments().All(__allowedTypes);
549584
}
550585
}
551586
}

tests/MongoDB.Bson.Tests/Jira/CSharp4475Tests.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ public void Deserialize_should_throw_when_deserializing_a_not_allowed_type()
5353
var exception = Record.Exception(() => BsonSerializer.Deserialize<C>(json));
5454

5555
exception.Should().BeOfType<FormatException>();
56-
exception.Message.Should().Be("An error occurred while deserializing the Object property of class MongoDB.Bson.Tests.Jira.CSharp4475Tests+C: Type MongoDB.Bson.Tests.Jira.CSharp4475Tests+NotAllowed is not configured as an allowed type for this instance of ObjectSerializer.");
56+
exception.Message.Should().Be("An error occurred while deserializing the Object property of class MongoDB.Bson.Tests.Jira.CSharp4475Tests+C: Type MongoDB.Bson.Tests.Jira.CSharp4475Tests+NotAllowed is not configured as a type that is allowed to be deserialized for this instance of ObjectSerializer.");
5757
}
5858

5959
[Fact]
@@ -74,7 +74,7 @@ public void Serialize_should_throw_when_serializing_a_not_allowed_type()
7474
var exception = Record.Exception(() => c.ToJson());
7575

7676
exception.Should().BeOfType<BsonSerializationException>();
77-
exception.Message.Should().Be("An error occurred while serializing the Object property of class MongoDB.Bson.Tests.Jira.CSharp4475Tests+C: Type MongoDB.Bson.Tests.Jira.CSharp4475Tests+NotAllowed is not configured as an allowed type for this instance of ObjectSerializer.");
77+
exception.Message.Should().Be("An error occurred while serializing the Object property of class MongoDB.Bson.Tests.Jira.CSharp4475Tests+C: Type MongoDB.Bson.Tests.Jira.CSharp4475Tests+NotAllowed is not configured as a type that is allowed to be serialized for this instance of ObjectSerializer.");
7878
}
7979

8080
public class C
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
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 FluentAssertions;
18+
using MongoDB.Bson.Serialization;
19+
using MongoDB.Bson.Serialization.Serializers;
20+
using Xunit;
21+
22+
namespace MongoDB.Bson.Tests.Jira
23+
{
24+
public class CSharp4511Tests
25+
{
26+
static CSharp4511Tests()
27+
{
28+
var objectSerializerWithAllowedTypes = new ObjectSerializer(
29+
BsonSerializer.LookupDiscriminatorConvention(typeof(object)),
30+
GuidRepresentation.Standard,
31+
t => t == typeof(AllowedToBeDeserialized),
32+
t => t == typeof(AllowedToBeSerialized));
33+
34+
BsonClassMap.RegisterClassMap<C>(classMap =>
35+
{
36+
classMap.AutoMap();
37+
classMap.MapProperty(x => x.Object).SetSerializer(objectSerializerWithAllowedTypes);
38+
});
39+
}
40+
41+
[Fact]
42+
public void Deserialize_should_return_expected_result_when_deserializing_an_allowed_type()
43+
{
44+
var json = "{ Object : { _t : 'MongoDB.Bson.Tests.Jira.CSharp4511Tests+AllowedToBeDeserialized, MongoDB.Bson.Tests', X : 1 } }";
45+
46+
var result = BsonSerializer.Deserialize<C>(json);
47+
48+
var allowed = result.Object.Should().BeOfType<AllowedToBeDeserialized>().Subject;
49+
allowed.X.Should().Be(1);
50+
}
51+
52+
[Fact]
53+
public void Deserialize_should_throw_when_deserializing_a_not_allowed_type()
54+
{
55+
var json = "{ Object : { _t : 'MongoDB.Bson.Tests.Jira.CSharp4511Tests+NotAllowed, MongoDB.Bson.Tests', X : 1 } }";
56+
57+
var exception = Record.Exception(() => BsonSerializer.Deserialize<C>(json));
58+
59+
exception.Should().BeOfType<FormatException>();
60+
exception.Message.Should().Be("An error occurred while deserializing the Object property of class MongoDB.Bson.Tests.Jira.CSharp4511Tests+C: Type MongoDB.Bson.Tests.Jira.CSharp4511Tests+NotAllowed is not configured as a type that is allowed to be deserialized for this instance of ObjectSerializer.");
61+
}
62+
63+
[Fact]
64+
public void Serialize_should_have_expected_result_when_serializing_an_allowed_type()
65+
{
66+
var c = new C { Object = new AllowedToBeSerialized { X = 1 } };
67+
68+
var result = c.ToJson();
69+
70+
result.Should().Be("{ \"Object\" : { \"_t\" : \"AllowedToBeSerialized\", \"X\" : 1 } }");
71+
}
72+
73+
[Fact]
74+
public void Serialize_should_throw_when_serializing_a_not_allowed_type()
75+
{
76+
var c = new C { Object = new NotAllowed { X = 1 } };
77+
78+
var exception = Record.Exception(() => c.ToJson());
79+
80+
exception.Should().BeOfType<BsonSerializationException>();
81+
exception.Message.Should().Be("An error occurred while serializing the Object property of class MongoDB.Bson.Tests.Jira.CSharp4511Tests+C: Type MongoDB.Bson.Tests.Jira.CSharp4511Tests+NotAllowed is not configured as a type that is allowed to be serialized for this instance of ObjectSerializer.");
82+
}
83+
84+
public class C
85+
{
86+
public object Object { get; set; }
87+
}
88+
89+
public class AllowedToBeDeserialized
90+
{
91+
public int X { get; set; }
92+
}
93+
94+
public class AllowedToBeSerialized
95+
{
96+
public int X { get; set; }
97+
}
98+
99+
public class NotAllowed
100+
{
101+
public int X { get; set; }
102+
}
103+
}
104+
}

0 commit comments

Comments
 (0)