diff --git a/Bson/Serialization/Serializers/DictionaryGenericSerializer.cs b/Bson/Serialization/Serializers/DictionaryGenericSerializer.cs index 057406fe74..4ceb469c5d 100644 --- a/Bson/Serialization/Serializers/DictionaryGenericSerializer.cs +++ b/Bson/Serialization/Serializers/DictionaryGenericSerializer.cs @@ -21,6 +21,7 @@ using System.IO; using MongoDB.Bson.IO; using MongoDB.Bson.Serialization; +using MongoDB.Bson.Serialization.Options; namespace MongoDB.Bson.Serialization.Serializers { /// @@ -108,25 +109,48 @@ namespace MongoDB.Bson.Serialization.Serializers { bsonWriter.WriteNull(); } else { var dictionary = (IDictionary) value; - if ( - typeof(TKey) == typeof(string) || - (typeof(TKey) == typeof(object) && dictionary.Keys.All(o => o.GetType() == typeof(string))) - ) { - bsonWriter.WriteStartDocument(); - foreach (KeyValuePair entry in dictionary) { - bsonWriter.WriteName((string) (object) entry.Key); - BsonSerializer.Serialize(bsonWriter, typeof(TValue), entry.Value); + + var representationOptions = options as RepresentationSerializationOptions; + BsonType representation; + if (representationOptions == null) { + if (typeof(TKey) == typeof(string) || typeof(TKey) == typeof(object)) { + representation = BsonType.Document; + foreach (object key in dictionary.Keys) { + var name = key as string; // check for null and type string at the same time + if (name == null || name.StartsWith("$") || name.Contains(".")) { + representation = BsonType.Array; + break; + } + } + } else { + representation = BsonType.Array; } - bsonWriter.WriteEndDocument(); } else { - bsonWriter.WriteStartArray(); - foreach (KeyValuePair entry in dictionary) { + representation = representationOptions.Representation; + } + + switch (representation) { + case BsonType.Document: + bsonWriter.WriteStartDocument(); + foreach (KeyValuePair entry in dictionary) { + bsonWriter.WriteName((string) (object) entry.Key); + BsonSerializer.Serialize(bsonWriter, typeof(TValue), entry.Value); + } + bsonWriter.WriteEndDocument(); + break; + case BsonType.Array: bsonWriter.WriteStartArray(); - BsonSerializer.Serialize(bsonWriter, typeof(TKey), entry.Key); - BsonSerializer.Serialize(bsonWriter, typeof(TValue), entry.Value); + foreach (KeyValuePair entry in dictionary) { + bsonWriter.WriteStartArray(); + BsonSerializer.Serialize(bsonWriter, typeof(TKey), entry.Key); + BsonSerializer.Serialize(bsonWriter, typeof(TValue), entry.Value); + bsonWriter.WriteEndArray(); + } bsonWriter.WriteEndArray(); - } - bsonWriter.WriteEndArray(); + break; + default: + var message = string.Format("'{0}' is not a valid representation for type IDictionary<{1}, {2}>.", representation, typeof(TKey).Name, typeof(TValue).Name); + throw new BsonSerializationException(message); } } } diff --git a/Bson/Serialization/Serializers/DictionarySerializer.cs b/Bson/Serialization/Serializers/DictionarySerializer.cs index 60b822e1dd..0519b41328 100644 --- a/Bson/Serialization/Serializers/DictionarySerializer.cs +++ b/Bson/Serialization/Serializers/DictionarySerializer.cs @@ -22,6 +22,7 @@ using System.IO; using MongoDB.Bson.IO; using MongoDB.Bson.Serialization; +using MongoDB.Bson.Serialization.Options; namespace MongoDB.Bson.Serialization.Serializers { /// @@ -121,23 +122,45 @@ namespace MongoDB.Bson.Serialization.Serializers { bsonWriter.WriteNull(); } else { var dictionary = (IDictionary) value; - if (dictionary.Keys.Cast().All(o => o.GetType() == typeof(string))) { - bsonWriter.WriteStartDocument(); - foreach (DictionaryEntry entry in dictionary) { - bsonWriter.WriteName((string) entry.Key); - BsonSerializer.Serialize(bsonWriter, typeof(object), entry.Value); + + var representationOptions = options as RepresentationSerializationOptions; + BsonType representation; + if (representationOptions == null) { + representation = BsonType.Document; + foreach (object key in dictionary.Keys) { + var name = key as string; // check for null and type string at the same time + if (name == null || name.StartsWith("$") || name.Contains(".")) { + representation = BsonType.Array; + break; + } } - bsonWriter.WriteEndDocument(); } else { - bsonWriter.WriteStartArray(); - foreach (DictionaryEntry entry in dictionary) { + representation = representationOptions.Representation; + } + + switch (representation) { + case BsonType.Document: + bsonWriter.WriteStartDocument(); + foreach (DictionaryEntry entry in dictionary) { + bsonWriter.WriteName((string) entry.Key); + BsonSerializer.Serialize(bsonWriter, typeof(object), entry.Value); + } + bsonWriter.WriteEndDocument(); + break; + case BsonType.Array: bsonWriter.WriteStartArray(); - BsonSerializer.Serialize(bsonWriter, typeof(object), entry.Key); - BsonSerializer.Serialize(bsonWriter, typeof(object), entry.Value); + foreach (DictionaryEntry entry in dictionary) { + bsonWriter.WriteStartArray(); + BsonSerializer.Serialize(bsonWriter, typeof(object), entry.Key); + BsonSerializer.Serialize(bsonWriter, typeof(object), entry.Value); + bsonWriter.WriteEndArray(); + } bsonWriter.WriteEndArray(); - } - bsonWriter.WriteEndArray(); - } + break; + default: + var message = string.Format("'{0}' is not a valid representation for type IDictionary.", representation); + throw new BsonSerializationException(message); + } } } #endregion diff --git a/DriverOnlineTests/DriverOnlineTests.csproj b/DriverOnlineTests/DriverOnlineTests.csproj index b4431f6f09..33f87c7569 100644 --- a/DriverOnlineTests/DriverOnlineTests.csproj +++ b/DriverOnlineTests/DriverOnlineTests.csproj @@ -105,6 +105,7 @@ + diff --git a/DriverOnlineTests/Jira/CSharp265Tests.cs b/DriverOnlineTests/Jira/CSharp265Tests.cs new file mode 100644 index 0000000000..5db29c44f0 --- /dev/null +++ b/DriverOnlineTests/Jira/CSharp265Tests.cs @@ -0,0 +1,269 @@ +/* Copyright 2010-2011 10gen 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; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.Serialization; +using System.Text; +using NUnit.Framework; + +using MongoDB.Bson; +using MongoDB.Bson.Serialization.Attributes; +using MongoDB.Driver; +using MongoDB.Driver.Builders; + +namespace MongoDB.DriverOnlineTests.Jira.CSharp265 { + [TestFixture] + public class CSharp265Tests { + public class GDA { + public int Id; + [BsonRepresentation(BsonType.Array)] + public Dictionary Data; + } + + public class GDD { + public int Id; + [BsonRepresentation(BsonType.Document)] + public Dictionary Data; + } + + public class GDX { + public int Id; + public Dictionary Data; + } + + public class HA { + public int Id; + [BsonRepresentation(BsonType.Array)] + public Hashtable Data; + } + + public class HD { + public int Id; + [BsonRepresentation(BsonType.Document)] + public Hashtable Data; + } + + public class HX { + public int Id; + public Hashtable Data; + } + + private MongoServer server; + private MongoDatabase database; + private MongoCollection collection; + + [TestFixtureSetUp] + public void TestFixtureSetup() { + server = MongoServer.Create("mongodb://localhost/?safe=true"); + database = server["onlinetests"]; + collection = database.GetCollection("testcollection"); + collection.Drop(); + } + + [Test] + public void TestGenericDictionaryArrayRepresentationWithDollar() { + var d = new GDA { Id = 1, Data = new Dictionary { { "$a", 1 } } }; + var expected = "{ '_id' : 1, 'Data' : [['$a', 1]] }".Replace("'", "\""); + var json = d.ToJson(); + Assert.AreEqual(expected, json); + + collection.RemoveAll(); + collection.Insert(d); + var r = collection.FindOne(Query.EQ("_id", d.Id)); + Assert.AreEqual(d.Id, r.Id); + Assert.AreEqual(1, r.Data.Count); + Assert.AreEqual(1, r.Data["$a"]); + } + + [Test] + public void TestGenericDictionaryArrayRepresentationWithDot() { + var d = new GDA { Id = 1, Data = new Dictionary { { "a.b", 1 } } }; + var expected = "{ '_id' : 1, 'Data' : [['a.b', 1]] }".Replace("'", "\""); + var json = d.ToJson(); + Assert.AreEqual(expected, json); + + collection.RemoveAll(); + collection.Insert(d); + var r = collection.FindOne(Query.EQ("_id", d.Id)); + Assert.AreEqual(d.Id, r.Id); + Assert.AreEqual(1, r.Data.Count); + Assert.AreEqual(1, r.Data["a.b"]); + } + + [Test] + public void TestGenericDictionaryDocumentRepresentationWithDollar() { + var d = new GDD { Id = 1, Data = new Dictionary { { "$a", 1 } } }; + var expected = "{ '_id' : 1, 'Data' : { '$a' : 1 } }".Replace("'", "\""); + var json = d.ToJson(); + Assert.AreEqual(expected, json); + + Assert.Throws(() => { collection.Insert(d); }); + } + + [Test] + public void TestGenericDictionaryDocumentRepresentationWithDot() { + var d = new GDD { Id = 1, Data = new Dictionary { { "a.b", 1 } } }; + var expected = "{ '_id' : 1, 'Data' : { 'a.b' : 1 } }".Replace("'", "\""); + var json = d.ToJson(); + Assert.AreEqual(expected, json); + + Assert.Throws(() => { collection.Insert(d); }); + } + + [Test] + public void TestGenericDictionaryDynamicRepresentationNormal() { + var d = new GDX { Id = 1, Data = new Dictionary { { "abc", 1 } } }; + var expected = "{ '_id' : 1, 'Data' : { 'abc' : 1 } }".Replace("'", "\""); + var json = d.ToJson(); + Assert.AreEqual(expected, json); + + collection.RemoveAll(); + collection.Insert(d); + var r = collection.FindOne(Query.EQ("_id", d.Id)); + Assert.AreEqual(d.Id, r.Id); + Assert.AreEqual(1, r.Data.Count); + Assert.AreEqual(1, r.Data["abc"]); + } + + [Test] + public void TestGenericDictionaryDynamicRepresentationWithDollar() { + var d = new GDX { Id = 1, Data = new Dictionary { { "$a", 1 } } }; + var expected = "{ '_id' : 1, 'Data' : [['$a', 1]] }".Replace("'", "\""); + var json = d.ToJson(); + Assert.AreEqual(expected, json); + + collection.RemoveAll(); + collection.Insert(d); + var r = collection.FindOne(Query.EQ("_id", d.Id)); + Assert.AreEqual(d.Id, r.Id); + Assert.AreEqual(1, r.Data.Count); + Assert.AreEqual(1, r.Data["$a"]); + } + + [Test] + public void TestGenericDictionaryDynamicRepresentationWithDot() { + var d = new GDX { Id = 1, Data = new Dictionary { { "a.b", 1 } } }; + var expected = "{ '_id' : 1, 'Data' : [['a.b', 1]] }".Replace("'", "\""); + var json = d.ToJson(); + Assert.AreEqual(expected, json); + + collection.RemoveAll(); + collection.Insert(d); + var r = collection.FindOne(Query.EQ("_id", d.Id)); + Assert.AreEqual(d.Id, r.Id); + Assert.AreEqual(1, r.Data.Count); + Assert.AreEqual(1, r.Data["a.b"]); + } + + [Test] + public void TestHashtableArrayRepresentationWithDollar() { + var d = new HA { Id = 1, Data = new Hashtable { { "$a", 1 } } }; + var expected = "{ '_id' : 1, 'Data' : [['$a', 1]] }".Replace("'", "\""); + var json = d.ToJson(); + Assert.AreEqual(expected, json); + + collection.RemoveAll(); + collection.Insert(d); + var r = collection.FindOne(Query.EQ("_id", d.Id)); + Assert.AreEqual(d.Id, r.Id); + Assert.AreEqual(1, r.Data.Count); + Assert.AreEqual(1, r.Data["$a"]); + } + + [Test] + public void TestHashtableArrayRepresentationWithDot() { + var d = new HA { Id = 1, Data = new Hashtable { { "a.b", 1 } } }; + var expected = "{ '_id' : 1, 'Data' : [['a.b', 1]] }".Replace("'", "\""); + var json = d.ToJson(); + Assert.AreEqual(expected, json); + + collection.RemoveAll(); + collection.Insert(d); + var r = collection.FindOne(Query.EQ("_id", d.Id)); + Assert.AreEqual(d.Id, r.Id); + Assert.AreEqual(1, r.Data.Count); + Assert.AreEqual(1, r.Data["a.b"]); + } + + [Test] + public void TestHashtableDocumentRepresentationWithDollar() { + var d = new HD { Id = 1, Data = new Hashtable { { "$a", 1 } } }; + var expected = "{ '_id' : 1, 'Data' : { '$a' : 1 } }".Replace("'", "\""); + var json = d.ToJson(); + Assert.AreEqual(expected, json); + + Assert.Throws(() => { collection.Insert(d); }); + } + + [Test] + public void TestHashtableDocumentRepresentationWithDot() { + var d = new HD { Id = 1, Data = new Hashtable { { "a.b", 1 } } }; + var expected = "{ '_id' : 1, 'Data' : { 'a.b' : 1 } }".Replace("'", "\""); + var json = d.ToJson(); + Assert.AreEqual(expected, json); + + Assert.Throws(() => { collection.Insert(d); }); + } + + [Test] + public void TestHashtableDynamicRepresentationNormal() { + var d = new HX { Id = 1, Data = new Hashtable { { "abc", 1 } } }; + var expected = "{ '_id' : 1, 'Data' : { 'abc' : 1 } }".Replace("'", "\""); + var json = d.ToJson(); + Assert.AreEqual(expected, json); + + collection.RemoveAll(); + collection.Insert(d); + var r = collection.FindOne(Query.EQ("_id", d.Id)); + Assert.AreEqual(d.Id, r.Id); + Assert.AreEqual(1, r.Data.Count); + Assert.AreEqual(1, r.Data["abc"]); + } + + [Test] + public void TestHashtableDynamicRepresentationWithDollar() { + var d = new HX { Id = 1, Data = new Hashtable { { "$a", 1 } } }; + var expected = "{ '_id' : 1, 'Data' : [['$a', 1]] }".Replace("'", "\""); + var json = d.ToJson(); + Assert.AreEqual(expected, json); + + collection.RemoveAll(); + collection.Insert(d); + var r = collection.FindOne(Query.EQ("_id", d.Id)); + Assert.AreEqual(d.Id, r.Id); + Assert.AreEqual(1, r.Data.Count); + Assert.AreEqual(1, r.Data["$a"]); + } + + [Test] + public void TestHashtableDynamicRepresentationWithDot() { + var d = new HX { Id = 1, Data = new Hashtable { { "a.b", 1 } } }; + var expected = "{ '_id' : 1, 'Data' : [['a.b', 1]] }".Replace("'", "\""); + var json = d.ToJson(); + Assert.AreEqual(expected, json); + + collection.RemoveAll(); + collection.Insert(d); + var r = collection.FindOne(Query.EQ("_id", d.Id)); + Assert.AreEqual(d.Id, r.Id); + Assert.AreEqual(1, r.Data.Count); + Assert.AreEqual(1, r.Data["a.b"]); + } + } +}