-
Notifications
You must be signed in to change notification settings - Fork 248
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Support for more deserialization controls via ITypeSerializers
Motivation ---------- The current ITypeSerializer implementation works under the assumption that all serialization/deserialization requests should have the same behavior. Any client library which implements a custom ITypeSerializer overrides this behavior for all requests to Couchbase. However, there are instances where a specific request may require custom options. The particular example addressed here is change tracking in the Linq2Couchbase library. It needs to control the object creation process for some deserialization requests in order to create change tracking proxies. Additionally, we need a method for custom ITypeSerializer implementations to provide member name resolution information to consumers. This will allow Linq2Couchbase to determine the correct attribute names to use when building N1QL queries. Currently, it is forced to assume that the Newtonsoft.Json behavior is in use. Finally, there is currently no method to override the deserialization process for N1QL queries on a per-request basis. Modifications ------------- Created a new interface which extends ITypeSerializer named IExtendedTypeSerializer. Added GetMemberName method to IExtendedTypeSerializer, which provides member name resolution information to consumers. Added DeserializationOptions to IExtendedTypeSerializer, which allows consumers to set the options they'd like. Currently, this object supports only one option, CustomTypeCreator, which allows the consumer to override the type creation process on a type-by-type basis. Also provided a SupportedDeserializationOptions object. This allows the IExtendedTypeSerializer to define which options it does or does not support. Updated the DefaultSerializer to support all of the new interfaces, methods, and options provided. Additionally, created a new interface IQueryRequestWithMapper, inherited form IQueryRequest, which adds a DataMapper to IQueryRequest. Added this interface to the default QueryRequest implentation. This allows the data mapper used for N1QL queries can be customized on a per-request basis. Results ------- For users using the DefaultSerializer based on Newtonsoft.Json, they will immediately have access to the new features on IExtendedTypeSerializer. This includes a method to resolve member names, and the ability to override the type creation process. This will allow Linq2Couchbase to transparently implement change tracking proxies. For projects using a custom ITypeSerializer implementation, consumers such as Linq2Couchbase can detect support for advanced features by testing for the IExtendedTypeSerializer interface. If present, they can then test for specific features via the SupportedDeserializationOptions property. Backwards compatibility is fully maintained by these changes. Additionally, the use of SupportedDeserializationOptions will allow the addition of more deserialization options in the future without creating backwards compatibility issues. Change-Id: I60db3a6a93d787d9e5e48ed1984e7b31566d348e Reviewed-on: http://review.couchbase.org/56960 Reviewed-by: Jeffry Morris <jeffrymorris@gmail.com> Tested-by: Jeffry Morris <jeffrymorris@gmail.com>
- Loading branch information
1 parent
2a3fba5
commit db07eb1
Showing
14 changed files
with
770 additions
and
9 deletions.
There are no files selected for viewing
237 changes: 237 additions & 0 deletions
237
Src/Couchbase.UnitTests/Core/Serialization/DefaultSerializerTests.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,237 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.IO; | ||
using System.Linq; | ||
using System.Text; | ||
using System.Threading.Tasks; | ||
using Couchbase.Core.Serialization; | ||
using Moq; | ||
using Newtonsoft.Json; | ||
using Newtonsoft.Json.Serialization; | ||
using NUnit.Framework; | ||
|
||
namespace Couchbase.UnitTests.Core.Serialization | ||
{ | ||
[TestFixture] | ||
public class DefaultSerializerTests | ||
{ | ||
#region DeserializationOptions | ||
|
||
[Test] | ||
public void DeserializationOptions_Modification_UpdatesEffectiveSettings() | ||
{ | ||
// Arrange | ||
|
||
var options = new DeserializationOptions(); | ||
var effectiveSettings = new JsonSerializerSettings(); | ||
|
||
var serializer = new Mock<DefaultSerializer> | ||
{ | ||
CallBase = true | ||
}; | ||
|
||
serializer.Setup(p => p.GetDeserializationSettings(It.IsAny<JsonSerializerSettings>(), options)) | ||
.Returns(effectiveSettings); | ||
|
||
// Act | ||
|
||
serializer.Object.DeserializationOptions = options; | ||
|
||
// Assert | ||
|
||
Assert.AreEqual(effectiveSettings, serializer.Object.EffectiveDeserializationSettings); | ||
} | ||
|
||
#endregion | ||
|
||
#region Deserialize With ICustomObjectCreator | ||
|
||
[Test] | ||
public void Deserialize_Stream_WithICustomObjectCreator_CreatesCustomObjects() | ||
{ | ||
// Arrange | ||
|
||
var creator = new FakeCustomObjectCreator(); | ||
|
||
var settings = new JsonSerializerSettings() | ||
{ | ||
ContractResolver = new CamelCasePropertyNamesContractResolver() | ||
}; | ||
|
||
var serializer = new DefaultSerializer(settings, settings) | ||
{ | ||
DeserializationOptions = new DeserializationOptions() | ||
{ | ||
CustomObjectCreator = creator | ||
} | ||
}; | ||
|
||
var stream = new MemoryStream(Encoding.UTF8.GetBytes("{\"subNode\":{\"property\":\"value\"}}")); | ||
|
||
// Act | ||
|
||
var result = serializer.Deserialize<JsonDocument>(stream); | ||
|
||
// Assert | ||
|
||
Assert.NotNull(result); | ||
Assert.NotNull(result.SubNode); | ||
Assert.AreEqual(typeof(DocumentSubNodeInherited), result.SubNode.GetType()); | ||
Assert.AreEqual("value", result.SubNode.Property); | ||
} | ||
|
||
[Test] | ||
public void Deserialize_ByteArray_WithICustomObjectCreator_CreatesCustomObjects() | ||
{ | ||
// Arrange | ||
|
||
var creator = new FakeCustomObjectCreator(); | ||
|
||
var settings = new JsonSerializerSettings() | ||
{ | ||
ContractResolver = new CamelCasePropertyNamesContractResolver() | ||
}; | ||
|
||
var serializer = new DefaultSerializer(settings, settings) | ||
{ | ||
DeserializationOptions = new DeserializationOptions() | ||
{ | ||
CustomObjectCreator = creator | ||
} | ||
}; | ||
|
||
var jsonBuffer = Encoding.UTF8.GetBytes("{\"subNode\":{\"property\":\"value\"}}"); | ||
|
||
// Act | ||
|
||
var result = serializer.Deserialize<JsonDocument>(jsonBuffer, 0, jsonBuffer.Length); | ||
|
||
// Assert | ||
|
||
Assert.NotNull(result); | ||
Assert.NotNull(result.SubNode); | ||
Assert.AreEqual(typeof(DocumentSubNodeInherited), result.SubNode.GetType()); | ||
Assert.AreEqual("value", result.SubNode.Property); | ||
} | ||
|
||
#endregion | ||
|
||
#region GetMemberName | ||
|
||
[Test] | ||
public void GetMemberName_Null_ArgumentNullException() | ||
{ | ||
// Arrange | ||
|
||
var serializer = new DefaultSerializer(); | ||
|
||
// Act/Assert | ||
|
||
Assert.Throws<ArgumentNullException>(() => serializer.GetMemberName(null)); | ||
} | ||
|
||
[Test] | ||
public void GetMemberName_BasicProperty_ReturnsPropertyName() | ||
{ | ||
// Arrange | ||
|
||
var settings = new JsonSerializerSettings() | ||
{ | ||
ContractResolver = new CamelCasePropertyNamesContractResolver() | ||
}; | ||
|
||
var serializer = new DefaultSerializer(settings, settings); | ||
|
||
// Act | ||
|
||
var result = serializer.GetMemberName(typeof (JsonDocument).GetProperty("BasicProperty")); | ||
|
||
// Assert | ||
|
||
Assert.AreEqual("basicProperty", result); | ||
} | ||
|
||
[Test] | ||
public void GetMemberName_NamedProperty_ReturnsNameFromAttribute() | ||
{ | ||
// Arrange | ||
|
||
var settings = new JsonSerializerSettings() | ||
{ | ||
ContractResolver = new CamelCasePropertyNamesContractResolver() | ||
}; | ||
|
||
var serializer = new DefaultSerializer(settings, settings); | ||
|
||
// Act | ||
|
||
var result = serializer.GetMemberName(typeof(JsonDocument).GetProperty("NamedProperty")); | ||
|
||
// Assert | ||
|
||
Assert.AreEqual("useThisName", result); | ||
} | ||
|
||
[Test] | ||
public void GetMemberName_IgnoredProperty_ReturnsNull() | ||
{ | ||
// Arrange | ||
|
||
var settings = new JsonSerializerSettings() | ||
{ | ||
ContractResolver = new CamelCasePropertyNamesContractResolver() | ||
}; | ||
|
||
var serializer = new DefaultSerializer(settings, settings); | ||
|
||
// Act | ||
|
||
var result = serializer.GetMemberName(typeof(JsonDocument).GetProperty("IgnoredProperty")); | ||
|
||
// Assert | ||
|
||
Assert.IsNull(result); | ||
} | ||
|
||
#endregion | ||
|
||
#region Helpers | ||
|
||
private class JsonDocument | ||
{ | ||
public string BasicProperty { get; set; } | ||
|
||
[JsonProperty("useThisName")] | ||
public string NamedProperty { get; set; } | ||
|
||
[JsonIgnore] | ||
public string IgnoredProperty { get; set; } | ||
|
||
public DocumentSubNode SubNode { get; set; } | ||
} | ||
|
||
private class DocumentSubNode | ||
{ | ||
public string Property { get; set; } | ||
} | ||
|
||
private class DocumentSubNodeInherited : DocumentSubNode | ||
{ | ||
} | ||
|
||
private class FakeCustomObjectCreator : ICustomObjectCreator | ||
{ | ||
public bool CanCreateObject(Type type) | ||
{ | ||
return type == typeof (DocumentSubNode); | ||
} | ||
|
||
public object CreateObject(Type type) | ||
{ | ||
return new DocumentSubNodeInherited(); | ||
} | ||
} | ||
|
||
#endregion | ||
} | ||
} |
54 changes: 54 additions & 0 deletions
54
Src/Couchbase.UnitTests/Core/Serialization/DeserializationOptionsTests.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Text; | ||
using System.Threading.Tasks; | ||
using Couchbase.Core.Serialization; | ||
using Moq; | ||
using NUnit.Framework; | ||
|
||
namespace Couchbase.UnitTests.Core.Serialization | ||
{ | ||
[TestFixture] | ||
public class DeserializationOptionsTests | ||
{ | ||
#region HasSettings | ||
|
||
[Test] | ||
public void HasSettings_DefaultObject_ReturnsFalse() | ||
{ | ||
// Arrange | ||
|
||
var settings = new DeserializationOptions(); | ||
|
||
// Act | ||
|
||
var result = settings.HasSettings; | ||
|
||
// Assert | ||
|
||
Assert.False(result); | ||
} | ||
|
||
[Test] | ||
public void HasSettings_WithCustomObjectCreator_ReturnsTrue() | ||
{ | ||
// Arrange | ||
|
||
var settings = new DeserializationOptions() | ||
{ | ||
CustomObjectCreator = new Mock<ICustomObjectCreator>().Object | ||
}; | ||
|
||
// Act | ||
|
||
var result = settings.HasSettings; | ||
|
||
// Assert | ||
|
||
Assert.True(result); | ||
} | ||
|
||
#endregion | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
using System; | ||
using System.Collections.Concurrent; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Net.Http; | ||
using System.Text; | ||
using System.Threading.Tasks; | ||
using Couchbase.Configuration.Client; | ||
using Couchbase.N1QL; | ||
using Couchbase.Views; | ||
using Moq; | ||
using NUnit.Framework; | ||
|
||
namespace Couchbase.UnitTests.N1Ql | ||
{ | ||
[TestFixture] | ||
public class QueryClientTests | ||
{ | ||
#region GetDataMapper | ||
|
||
[Test] | ||
public void GetDataMapper_IQueryRequest_ReturnsClientDataMapper() | ||
{ | ||
// Arrange | ||
|
||
var dataMapper = new Mock<IDataMapper>(); | ||
|
||
var queryRequest = new Mock<IQueryRequest>(); | ||
|
||
var queryClient = new QueryClient(new HttpClient(), dataMapper.Object, new ClientConfiguration(), | ||
new ConcurrentDictionary<string, QueryPlan>()); | ||
|
||
// Act | ||
|
||
var result = queryClient.GetDataMapper(queryRequest.Object); | ||
|
||
// Assert | ||
|
||
Assert.AreEqual(dataMapper.Object, result); | ||
} | ||
|
||
[Test] | ||
public void GetDataMapper_IQueryRequestWithDataMapper_NoDataMapper_ReturnsClientDataMapper() | ||
{ | ||
// Arrange | ||
|
||
var clientDataMapper = new Mock<IDataMapper>(); | ||
|
||
var queryRequest = new Mock<IQueryRequestWithDataMapper>(); | ||
queryRequest.SetupProperty(p => p.DataMapper, null); | ||
|
||
var queryClient = new QueryClient(new HttpClient(), clientDataMapper.Object, new ClientConfiguration(), | ||
new ConcurrentDictionary<string, QueryPlan>()); | ||
|
||
// Act | ||
|
||
var result = queryClient.GetDataMapper(queryRequest.Object); | ||
|
||
// Assert | ||
|
||
Assert.AreEqual(clientDataMapper.Object, result); | ||
} | ||
|
||
[Test] | ||
public void GetDataMapper_IQueryRequestWithDataMapper_HasDataMapper_ReturnsRequestDataMapper() | ||
{ | ||
// Arrange | ||
|
||
var clientDataMapper = new Mock<IDataMapper>(); | ||
var requestDataMapper = new Mock<IDataMapper>(); | ||
|
||
var queryRequest = new Mock<IQueryRequestWithDataMapper>(); | ||
queryRequest.SetupProperty(p => p.DataMapper, requestDataMapper.Object); | ||
|
||
var queryClient = new QueryClient(new HttpClient(), clientDataMapper.Object, new ClientConfiguration(), | ||
new ConcurrentDictionary<string, QueryPlan>()); | ||
|
||
// Act | ||
|
||
var result = queryClient.GetDataMapper(queryRequest.Object); | ||
|
||
// Assert | ||
|
||
Assert.AreEqual(requestDataMapper.Object, result); | ||
} | ||
|
||
#endregion | ||
} | ||
} |
Oops, something went wrong.