Browse Source

Fixed CSHARP-265. DictionarySerializer and DictionaryGenericSerializer now switch to the alternate array representation when any key value is not a valid element name. The client code can also force use of Array or Document representation using serialization options (perhaps for efficiency reasons to skip scanning the keys for validity as element names).

pull/63/head
rstam 14 years ago
parent
commit
68f8ef8fb5
  1. 54
      Bson/Serialization/Serializers/DictionaryGenericSerializer.cs
  2. 49
      Bson/Serialization/Serializers/DictionarySerializer.cs
  3. 1
      DriverOnlineTests/DriverOnlineTests.csproj
  4. 269
      DriverOnlineTests/Jira/CSharp265Tests.cs

54
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 {
/// <summary>
@ -108,25 +109,48 @@ namespace MongoDB.Bson.Serialization.Serializers {
bsonWriter.WriteNull();
} else {
var dictionary = (IDictionary<TKey, TValue>) value;
if (
typeof(TKey) == typeof(string) ||
(typeof(TKey) == typeof(object) && dictionary.Keys.All(o => o.GetType() == typeof(string)))
) {
bsonWriter.WriteStartDocument();
foreach (KeyValuePair<TKey, TValue> 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<TKey, TValue> entry in dictionary) {
representation = representationOptions.Representation;
}
switch (representation) {
case BsonType.Document:
bsonWriter.WriteStartDocument();
foreach (KeyValuePair<TKey, TValue> 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<TKey, TValue> 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);
}
}
}

49
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 {
/// <summary>
@ -121,23 +122,45 @@ namespace MongoDB.Bson.Serialization.Serializers {
bsonWriter.WriteNull();
} else {
var dictionary = (IDictionary) value;
if (dictionary.Keys.Cast<object>().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

1
DriverOnlineTests/DriverOnlineTests.csproj

@ -105,6 +105,7 @@
<Compile Include="Jira\CSharp247Tests.cs" />
<Compile Include="Jira\CSharp253Tests.cs" />
<Compile Include="Jira\CSharp258Tests.cs" />
<Compile Include="Jira\CSharp265Tests.cs" />
<Compile Include="Jira\CSharp77Tests.cs" />
<Compile Include="Jira\CSharp92Tests.cs" />
<Compile Include="Jira\CSharp93Tests.cs" />

269
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<string, int> Data;
}
public class GDD {
public int Id;
[BsonRepresentation(BsonType.Document)]
public Dictionary<string, int> Data;
}
public class GDX {
public int Id;
public Dictionary<string, int> 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<GDA> collection;
[TestFixtureSetUp]
public void TestFixtureSetup() {
server = MongoServer.Create("mongodb://localhost/?safe=true");
database = server["onlinetests"];
collection = database.GetCollection<GDA>("testcollection");
collection.Drop();
}
[Test]
public void TestGenericDictionaryArrayRepresentationWithDollar() {
var d = new GDA { Id = 1, Data = new Dictionary<string, int> { { "$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<string, int> { { "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<string, int> { { "$a", 1 } } };
var expected = "{ '_id' : 1, 'Data' : { '$a' : 1 } }".Replace("'", "\"");
var json = d.ToJson();
Assert.AreEqual(expected, json);
Assert.Throws<BsonSerializationException>(() => { collection.Insert(d); });
}
[Test]
public void TestGenericDictionaryDocumentRepresentationWithDot() {
var d = new GDD { Id = 1, Data = new Dictionary<string, int> { { "a.b", 1 } } };
var expected = "{ '_id' : 1, 'Data' : { 'a.b' : 1 } }".Replace("'", "\"");
var json = d.ToJson();
Assert.AreEqual(expected, json);
Assert.Throws<BsonSerializationException>(() => { collection.Insert(d); });
}
[Test]
public void TestGenericDictionaryDynamicRepresentationNormal() {
var d = new GDX { Id = 1, Data = new Dictionary<string, int> { { "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<string, int> { { "$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<string, int> { { "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<BsonSerializationException>(() => { 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<BsonSerializationException>(() => { 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"]);
}
}
}
Loading…
Cancel
Save