Skip to content

Commit f92cb41

Browse files
rstamdnickless
authored andcommitted
CSHARP-4412: BsonRepresentationAttribute not respected when string array assigned to IList property.
1 parent 855d471 commit f92cb41

File tree

3 files changed

+349
-8
lines changed

3 files changed

+349
-8
lines changed

src/MongoDB.Bson/Serialization/CollectionsSerializationProvider.cs

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -204,10 +204,9 @@ private IBsonSerializer GetCollectionSerializer(Type type, IBsonSerializerRegist
204204

205205
if (typeInfo.IsInterface)
206206
{
207-
var hashSetDefinition = typeof(HashSet<>);
208-
var hashSetType = hashSetDefinition.MakeGenericType(itemType);
209-
var serializerDefinition = typeof(ImpliedImplementationInterfaceSerializer<,>);
210-
return CreateGenericSerializer(serializerDefinition, new[] { type, hashSetType }, serializerRegistry);
207+
var serializerDefinition = typeof(IEnumerableDeserializingAsCollectionSerializer<,,>);
208+
var collectionType = typeof(HashSet<>).MakeGenericType(itemType);
209+
return CreateGenericSerializer(serializerDefinition, new[] { type, itemType, collectionType }, serializerRegistry);
211210
}
212211
else
213212
{
@@ -232,12 +231,12 @@ private IBsonSerializer GetCollectionSerializer(Type type, IBsonSerializerRegist
232231
}
233232
else if (typeInfo.IsInterface)
234233
{
235-
var listDefinition = typeof(List<>);
236-
var listType = listDefinition.MakeGenericType(itemType);
234+
var listType = typeof(List<>).MakeGenericType(itemType);
237235
if (typeInfo.IsAssignableFrom(listType))
238236
{
239-
var serializerDefinition = typeof(ImpliedImplementationInterfaceSerializer<,>);
240-
return CreateGenericSerializer(serializerDefinition, new[] { type, listType }, serializerRegistry);
237+
var serializerDefinition = typeof(IEnumerableDeserializingAsCollectionSerializer<,,>);
238+
var collectionType = typeof(List<>).MakeGenericType(itemType);
239+
return CreateGenericSerializer(serializerDefinition, new[] { type, itemType, collectionType }, serializerRegistry);
241240
}
242241
}
243242

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
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+
19+
namespace MongoDB.Bson.Serialization.Serializers
20+
{
21+
/// <summary>
22+
/// Represents a serializer for IEnumerable and any other derived interface implemented by TCollection.
23+
/// </summary>
24+
/// <typeparam name="TIEnumerable">The type of an IEnumerable interface.</typeparam>
25+
/// <typeparam name="TItem">The type of the items.</typeparam>
26+
/// <typeparam name="TCollection">The type of the collection used when deserializing.</typeparam>
27+
public class IEnumerableDeserializingAsCollectionSerializer<TIEnumerable, TItem, TCollection> :
28+
SerializerBase<TIEnumerable>,
29+
IBsonArraySerializer,
30+
IChildSerializerConfigurable
31+
where TIEnumerable : class, IEnumerable<TItem> // TIEnumerable must be an interface
32+
where TCollection : class, ICollection<TItem>, new()
33+
{
34+
#region static
35+
private static void EnsureTIEnumerableIsAnInterface()
36+
{
37+
if (!typeof(TIEnumerable).IsInterface)
38+
{
39+
// this constraint cannot be specified at compile time
40+
throw new ArgumentException($"The {nameof(TIEnumerable)} type argument is not an interface: {typeof(TIEnumerable)}.", nameof(TIEnumerable));
41+
}
42+
}
43+
#endregion
44+
45+
// private fields
46+
private readonly Lazy<IBsonSerializer<TItem>> _lazyItemSerializer;
47+
48+
// constructors
49+
/// <summary>
50+
/// Initializes a new instance of the IEnumerableDeserializingAsCollectionSerializer class.
51+
/// </summary>
52+
public IEnumerableDeserializingAsCollectionSerializer()
53+
: this(BsonSerializer.SerializerRegistry)
54+
{
55+
}
56+
57+
/// <summary>
58+
/// Initializes a new instance of the IEnumerableDeserializingAsCollectionSerializer class.
59+
/// </summary>
60+
/// <param name="itemSerializer">The item serializer.</param>
61+
public IEnumerableDeserializingAsCollectionSerializer(IBsonSerializer<TItem> itemSerializer)
62+
{
63+
EnsureTIEnumerableIsAnInterface();
64+
if (itemSerializer == null)
65+
{
66+
throw new ArgumentNullException(nameof(itemSerializer));
67+
}
68+
69+
_lazyItemSerializer = new Lazy<IBsonSerializer<TItem>>(() => itemSerializer);
70+
}
71+
72+
/// <summary>
73+
/// Initializes a new instance of the IEnumerableDeserializingAsCollectionSerializer class.
74+
/// </summary>
75+
/// <param name="serializerRegistry">The serializer registry.</param>
76+
public IEnumerableDeserializingAsCollectionSerializer(IBsonSerializerRegistry serializerRegistry)
77+
{
78+
EnsureTIEnumerableIsAnInterface();
79+
if (serializerRegistry == null)
80+
{
81+
throw new ArgumentNullException(nameof(serializerRegistry));
82+
}
83+
84+
_lazyItemSerializer = new Lazy<IBsonSerializer<TItem>>(serializerRegistry.GetSerializer<TItem>);
85+
}
86+
87+
// public properties
88+
/// <summary>
89+
/// Gets the item serializer.
90+
/// </summary>
91+
/// <value>
92+
/// The item serializer.
93+
/// </value>
94+
public IBsonSerializer<TItem> ItemSerializer => _lazyItemSerializer.Value;
95+
96+
// public methods
97+
/// <inheritdoc/>
98+
public override TIEnumerable Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
99+
{
100+
var reader = context.Reader;
101+
102+
if (reader.GetCurrentBsonType() == BsonType.Null)
103+
{
104+
reader.ReadNull();
105+
return null;
106+
}
107+
else
108+
{
109+
reader.ReadStartArray();
110+
var collection = new TCollection();
111+
var itemSerializer = _lazyItemSerializer.Value;
112+
while (reader.ReadBsonType() != 0)
113+
{
114+
var item = itemSerializer.Deserialize(context);
115+
collection.Add(item);
116+
}
117+
reader.ReadEndArray();
118+
return (TIEnumerable)(object)collection;
119+
}
120+
}
121+
122+
/// <inheritdoc/>
123+
public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, TIEnumerable value)
124+
{
125+
var writer = context.Writer;
126+
127+
if (value == null)
128+
{
129+
writer.WriteNull();
130+
}
131+
else
132+
{
133+
writer.WriteStartArray();
134+
var itemSerializer = _lazyItemSerializer.Value;
135+
foreach (var item in value)
136+
{
137+
itemSerializer.Serialize(context, item);
138+
}
139+
writer.WriteEndArray();
140+
}
141+
}
142+
143+
/// <summary>
144+
/// Tries to get the serialization info for the individual items of the array.
145+
/// </summary>
146+
/// <param name="serializationInfo">The serialization information.</param>
147+
/// <returns>
148+
/// The serialization info for the items.
149+
/// </returns>
150+
public bool TryGetItemSerializationInfo(out BsonSerializationInfo serializationInfo)
151+
{
152+
var itemSerializer = _lazyItemSerializer.Value;
153+
serializationInfo = new BsonSerializationInfo(null, itemSerializer, itemSerializer.ValueType);
154+
return true;
155+
}
156+
157+
/// <summary>
158+
/// Returns a serializer that has been reconfigured with the specified item serializer.
159+
/// </summary>
160+
/// <param name="itemSerializer">The item serializer.</param>
161+
/// <returns>The reconfigured serializer.</returns>
162+
public IEnumerableDeserializingAsCollectionSerializer<TIEnumerable, TItem, TCollection> WithItemSerializer(IBsonSerializer<TItem> itemSerializer)
163+
{
164+
return new IEnumerableDeserializingAsCollectionSerializer<TIEnumerable, TItem, TCollection>(itemSerializer);
165+
}
166+
167+
// explicit interface implementations
168+
IBsonSerializer IChildSerializerConfigurable.ChildSerializer => ItemSerializer;
169+
170+
IBsonSerializer IChildSerializerConfigurable.WithChildSerializer(IBsonSerializer childSerializer)
171+
=> WithItemSerializer((IBsonSerializer<TItem>)childSerializer);
172+
}
173+
}
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
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.Collections.Generic;
17+
using System.Collections.ObjectModel;
18+
using FluentAssertions;
19+
using MongoDB.Bson.Serialization;
20+
using MongoDB.Bson.Serialization.Attributes;
21+
using Xunit;
22+
23+
namespace MongoDB.Bson.Tests.Jira.CSharp783
24+
{
25+
public class CSharp4412Tests
26+
{
27+
[Fact]
28+
public void IEnumerable_Ids_should_deserialize_from_ObjectIds()
29+
{
30+
var json = "{ \"Ids\" : [ObjectId(\"0102030405060708090a0b0c\")] }";
31+
32+
var rehydrated = BsonSerializer.Deserialize<ClassWithIEnumerableIds>(json);
33+
34+
rehydrated.Ids.Should().Equal("0102030405060708090a0b0c");
35+
}
36+
37+
public static readonly object[][] IEnumerable_should_serialize_as_ObjectIds_MemberData =
38+
{
39+
new object[] { new string[] { "0102030405060708090a0b0c" } },
40+
new object[] { new List<string> { "0102030405060708090a0b0c" } },
41+
new object[] { new Collection<string> { "0102030405060708090a0b0c" } },
42+
new object[] { new ReadOnlyCollection<string>(new List<string> { "0102030405060708090a0b0c" }) },
43+
new object[] { new HashSet<string> { "0102030405060708090a0b0c" } },
44+
new object[] { new SortedSet<string> { "0102030405060708090a0b0c" } }
45+
};
46+
47+
[Theory]
48+
[MemberData(nameof(IEnumerable_should_serialize_as_ObjectIds_MemberData))]
49+
public void IEnumerable_should_serialize_as_ObjectIds(IEnumerable<string> value)
50+
{
51+
var document = new ClassWithIEnumerableIds { Ids = value };
52+
53+
var json = document.ToJson();
54+
55+
json.Should().Be("{ \"Ids\" : [ObjectId(\"0102030405060708090a0b0c\")] }");
56+
}
57+
58+
[Fact]
59+
public void ICollection_Ids_should_deserialize_from_ObjectIds()
60+
{
61+
var json = "{ \"Ids\" : [ObjectId(\"0102030405060708090a0b0c\")] }";
62+
63+
var rehydrated = BsonSerializer.Deserialize<ClassWithICollectionIds>(json);
64+
65+
rehydrated.Ids.Should().Equal("0102030405060708090a0b0c");
66+
}
67+
68+
public static readonly object[][] ICollection_should_serialize_as_ObjectIds_MemberData =
69+
{
70+
new object[] { new string[] { "0102030405060708090a0b0c" } },
71+
new object[] { new List<string> { "0102030405060708090a0b0c" } },
72+
new object[] { new Collection<string> { "0102030405060708090a0b0c" } },
73+
new object[] { new ReadOnlyCollection<string>(new List<string> { "0102030405060708090a0b0c" }) },
74+
new object[] { new HashSet<string> { "0102030405060708090a0b0c" } },
75+
new object[] { new SortedSet<string> { "0102030405060708090a0b0c" } }
76+
};
77+
78+
[Theory]
79+
[MemberData(nameof(ICollection_should_serialize_as_ObjectIds_MemberData))]
80+
public void ICollection_should_serialize_as_ObjectIds(ICollection<string> value)
81+
{
82+
var document = new ClassWithICollectionIds { Ids = value };
83+
84+
var json = document.ToJson();
85+
86+
json.Should().Be("{ \"Ids\" : [ObjectId(\"0102030405060708090a0b0c\")] }");
87+
}
88+
89+
[Fact]
90+
public void IList_Ids_should_deserialize_from_ObjectIds()
91+
{
92+
var json = "{ \"Ids\" : [ObjectId(\"0102030405060708090a0b0c\")] }";
93+
94+
var rehydrated = BsonSerializer.Deserialize<ClassWithIListIds>(json);
95+
96+
rehydrated.Ids.Should().Equal("0102030405060708090a0b0c");
97+
}
98+
99+
public static readonly object[][] IList_should_serialize_as_ObjectIds_MemberData =
100+
{
101+
new object[] { new string[] { "0102030405060708090a0b0c" } },
102+
new object[] { new List<string> { "0102030405060708090a0b0c" } },
103+
new object[] { new Collection<string> { "0102030405060708090a0b0c" } },
104+
new object[] { new ReadOnlyCollection<string>(new List<string> { "0102030405060708090a0b0c" }) }
105+
};
106+
107+
[Theory]
108+
[MemberData(nameof(IList_should_serialize_as_ObjectIds_MemberData))]
109+
public void IList_should_serialize_as_ObjectIds(IList<string> value)
110+
{
111+
var document = new ClassWithIListIds { Ids = value };
112+
113+
var json = document.ToJson();
114+
115+
json.Should().Be("{ \"Ids\" : [ObjectId(\"0102030405060708090a0b0c\")] }");
116+
}
117+
118+
[Fact]
119+
public void ISet_Ids_should_deserialize_from_ObjectIds()
120+
{
121+
var json = "{ \"Ids\" : [ObjectId(\"0102030405060708090a0b0c\")] }";
122+
123+
var rehydrated = BsonSerializer.Deserialize<ClassWithISetIds>(json);
124+
125+
rehydrated.Ids.Should().Equal("0102030405060708090a0b0c");
126+
}
127+
128+
public static readonly object[][] ISet_should_serialize_as_ObjectIds_MemberData =
129+
{
130+
new object[] { new HashSet<string> { "0102030405060708090a0b0c" } },
131+
new object[] { new SortedSet<string> { "0102030405060708090a0b0c" } }
132+
};
133+
134+
[Theory]
135+
[MemberData(nameof(ISet_should_serialize_as_ObjectIds_MemberData))]
136+
public void ISet_should_serialize_as_ObjectIds(ISet<string> value)
137+
{
138+
var document = new ClassWithISetIds { Ids = value };
139+
140+
var json = document.ToJson();
141+
142+
json.Should().Be("{ \"Ids\" : [ObjectId(\"0102030405060708090a0b0c\")] }");
143+
}
144+
145+
private class ClassWithIEnumerableIds
146+
{
147+
[BsonRepresentation(BsonType.ObjectId)]
148+
public IEnumerable<string> Ids { get; set; }
149+
}
150+
151+
private class ClassWithICollectionIds
152+
{
153+
[BsonRepresentation(BsonType.ObjectId)]
154+
public ICollection<string> Ids { get; set; }
155+
}
156+
157+
private class ClassWithIListIds
158+
{
159+
[BsonRepresentation(BsonType.ObjectId)]
160+
public IList<string> Ids { get; set; }
161+
}
162+
163+
private class ClassWithISetIds
164+
{
165+
[BsonRepresentation(BsonType.ObjectId)]
166+
public ISet<string> Ids { get; set; }
167+
}
168+
}
169+
}

0 commit comments

Comments
 (0)