Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add MongoDb serialization support #60

Closed
dgg opened this issue Dec 29, 2023 · 0 comments
Closed

Add MongoDb serialization support #60

dgg opened this issue Dec 29, 2023 · 0 comments
Assignees
Milestone

Comments

@dgg
Copy link
Owner

dgg commented Dec 29, 2023

Since version 7 approaches serialization in a very different way, we want to still provide snippet support for serializing Money instances.

Old code in src/NMoneys.Serialization
	/// <summary>
	/// Converts a <see cref="Money"/> (or <see cref="Nullable{T}"/>) instance to and from JSON in the canonical way.
	/// </summary>
	/// <remarks>The canonical way is the one implemented in NMoneys itself, with an <c>Amount</c>
	/// numeric property and a <c>Currency</c> object with a three-letter code <c>IsoCode"</c> property.
	/// <para>
	/// Property casing must be configured apart from this serializer using, for instance, another set of
	/// <see cref="IConvention"/>
	/// </para>
	/// </remarks>
	/// <example>
	/// <code>{"Amount" : 123.4, "Currency" : {"IsoCode" : "XXX"}}</code>
	/// <code>{"amount" : 123.4, "currency" : {"isoCode" : "XXX"}}</code>
	/// </example>
	public class CanonicalMoneySerializer : MoneySerializer
	{
		public CanonicalMoneySerializer() : base(
			m => new CanonicalMoneyReader(m),
			m => new CanonicalMoneyWriter(m)) { }
	}

	/// <summary>
	/// Converts a <see cref="Money"/> (or <see cref="Nullable{Money}"/>) instance to and from JSON in a default (standard) way.
	/// </summary>
	/// <remarks>The default (standard) way is the one that a normal serializer would do for a money instance,
	/// with an <c>Amount</c> numeric property and a <c>Currency</c> code. The <c>Currency</c> property can be
	/// serialized either as a number (the default or using a <see cref="EnumRepresentationConvention"/> with <see cref="BsonType.Int32"/>) or as
	/// a string (using a <see cref="EnumRepresentationConvention"/> with <see cref="BsonType.String"/>).
	/// <para>
	/// Property casing must be configured apart from this serializer using, for instance, another set of
	/// <see cref="IConvention"/>
	/// </para>
	/// </remarks>
	/// <example>
	/// <code>{"Amount" : 123.4, "Currency" : "XXX"}</code>
	/// <code>{"amount" : 123.4, "currency" : 999}</code>
	/// </example>
	public class DefaultMoneySerializer : MoneySerializer
	{
		public DefaultMoneySerializer()
			: base(
				m => new DefaultMoneyReader(m),
				m => new DefaultMoneyWriter(m)) { }
	}

	/// <summary>
	/// Base class for custom <see cref="Money"/> serializers
	/// </summary>
	public abstract class MoneySerializer : IBsonSerializer
	{
		private readonly Lazy<IMoneyReader> _reader;
		private readonly Lazy<IMoneyWriter> _writer;

		internal MoneySerializer(Func<BsonClassMap<Money>, IMoneyReader> reader,
			Func<BsonClassMap<Money>, IMoneyWriter> writer)
		{
			_reader = new Lazy<IMoneyReader>(() =>
			{
				var map = (BsonClassMap<Money>)BsonClassMap.LookupClassMap(typeof(Money));
				return reader(map);
			});

			_writer = new Lazy<IMoneyWriter>(() =>
			{
				var map = (BsonClassMap<Money>)BsonClassMap.LookupClassMap(typeof(Money));
				return writer(map);
			});
		}


		/// <summary>
		/// Deserializes an object from a BsonReader.
		/// </summary>
		/// <param name="bsonReader">The BsonReader.</param>
		/// <param name="nominalType">The nominal type of the object.</param>
		/// <param name="options">The serialization options.</param>
		/// <returns>An object.</returns>
		public object Deserialize(BsonReader bsonReader, Type nominalType, IBsonSerializationOptions options)
		{
			Money money = _reader.Value.ReadFrom(bsonReader);
			return money;
		}

		/// <summary>
		/// Deserializes an object from a BsonReader.
		/// </summary>
		/// <param name="bsonReader">The BsonReader.</param>
		/// <param name="nominalType">The nominal type of the object.</param>
		/// <param name="actualType">The actual type of the object.</param>
		/// <param name="options">The serialization options.</param>
		/// <returns>An object.</returns>
		public object Deserialize(BsonReader bsonReader, Type nominalType, Type actualType, IBsonSerializationOptions options)
		{
			return Deserialize(bsonReader, nominalType, options);
		}

		/// <summary>
		/// Gets the default serialization options for this serializer.
		/// </summary>
		/// <returns>
		/// The default serialization options for this serializer.
		/// </returns>
		public IBsonSerializationOptions GetDefaultSerializationOptions()
		{
			return new DocumentSerializationOptions();
		}

		/// <summary>
		/// Serializes an object to a BsonWriter.
		/// </summary>
		/// <param name="bsonWriter">The BsonWriter.</param>
		/// <param name="nominalType">The nominal type.</param>
		/// <param name="value">The object.</param>
		/// <param name="options">The serialization options.</param>
		public void Serialize(BsonWriter bsonWriter, Type nominalType, object value, IBsonSerializationOptions options)
		{
			var money = (Money)value;
			_writer.Value.WriteTo(money, bsonWriter);
		}
	}

	internal static class PropertyNameCapitalizer
	{
		public static string CapitalizeAs(this string pascalCasedPropertyName, BsonMemberMap sampleMap)
		{
			var sb = new StringBuilder(pascalCasedPropertyName, pascalCasedPropertyName.Length);
			if (char.IsLower(sampleMap.ElementName[0]))
			{
				sb[0] = char.ToLowerInvariant(sb[0]);
			}
			return sb.ToString();
		}
	}

	internal interface IMoneyWriter
	{
		void WriteTo(Money instance, BsonWriter writer);
	}

	internal abstract class MoneyWriter : IMoneyWriter
	{
		public void WriteTo(Money instance, BsonWriter writer)
		{
			writer.WriteStartDocument();
			writeAmount(instance, writer);
			writeCurrency(instance, writer);
			writer.WriteEndDocument();
		}

		protected abstract void writeAmount(Money instance, BsonWriter writer);
		protected abstract void writeCurrency(Money instance, BsonWriter writer);
	}

	internal class CanonicalMoneyWriter : MoneyWriter
	{
		private readonly BsonClassMap<Money> _map;

		public CanonicalMoneyWriter(BsonClassMap<Money> map)
		{
			_map = map;
		}

		protected override void writeAmount(Money instance, BsonWriter writer)
		{
			BsonMemberMap amount = _map.GetMemberMap(m => m.Amount);
			writer.WriteDouble(amount.ElementName, Convert.ToDouble(instance.Amount));
		}

		protected override void writeCurrency(Money instance, BsonWriter writer)
		{
			BsonMemberMap currency = _map.GetMemberMap(m => m.CurrencyCode);
			writer.WriteName("Currency".CapitalizeAs(currency));
			writer.WriteStartDocument();
			writer.WriteString("IsoCode".CapitalizeAs(currency), instance.CurrencyCode.AlphabeticCode());
			writer.WriteEndDocument();
		}
	}

	internal class DefaultMoneyWriter : MoneyWriter
	{
		private readonly BsonClassMap<Money> _map;

		public DefaultMoneyWriter(BsonClassMap<Money> map)
		{
			_map = map;
		}

		protected override void writeAmount(Money instance, BsonWriter writer)
		{
			BsonMemberMap amount = _map.GetMemberMap(m => m.Amount);
			writer.WriteDouble(amount.ElementName, Convert.ToDouble(instance.Amount));
		}

		protected override void writeCurrency(Money instance, BsonWriter writer)
		{
			BsonMemberMap currency = _map.GetMemberMap(m => m.CurrencyCode);
			writer.WriteName("Currency".CapitalizeAs(currency));
			currency.GetSerializer(typeof(CurrencyIsoCode)).Serialize(writer, typeof(CurrencyIsoCode), instance.CurrencyCode, currency.SerializationOptions);
		}
	}
Old code in src/NMoneys.Serialization.Tests
public class ProxySerializer<T> : IBsonSerializer
	{
		private static readonly IBsonSerializer _default = new BsonClassMapSerializer(
			BsonClassMap.LookupClassMap(typeof (T)));

		public ProxySerializer()
		{
			Serializer = _default;
		}

		public IBsonSerializer Serializer { get; set; }

		public IBsonSerializer Default { get { return _default; } }
		
		public object Deserialize(BsonReader bsonReader, Type nominalType, IBsonSerializationOptions options)
		{
			return Serializer.Deserialize(bsonReader, nominalType, options);
		}

		public object Deserialize(BsonReader bsonReader, Type nominalType, Type actualType, IBsonSerializationOptions options)
		{
			return Serializer.Deserialize(bsonReader, nominalType, actualType, options);
		}

		public IBsonSerializationOptions GetDefaultSerializationOptions()
		{
			return Serializer.GetDefaultSerializationOptions();
		}

		public void Serialize(BsonWriter bsonWriter, Type nominalType, object value, IBsonSerializationOptions options)
		{
			Serializer.Serialize(bsonWriter, nominalType, value, options);
		}

		public ProxySerializer<T> Register()
		{
			BsonSerializer.RegisterSerializer(typeof (T), this);
			return this;
		}
	}
public class TestingConventions
	{
		private static readonly string _name = "testing";
		private readonly ConventionPack _pack;

		public TestingConventions(params IConvention[] conventions)
		{
			_pack = new ConventionPack();
			_pack.AddRange(conventions);
		}

		public void Register()
		{
			ConventionRegistry.Register(_name, _pack, _ => true);
		}
	}
[TestFixture, Explicit("we cannot change conventions once applied :,-(")]
	public class CustomConventionsTester
	{
		private ProxySerializer<Money> _proxy;

		[OneTimeSetUp]
		public void Setup()
		{
			new TestingConventions(
					new CamelCaseElementNameConvention(),
					new EnumRepresentationConvention(BsonType.String))
				.Register();

			_proxy = new ProxySerializer<Money>();
			BsonSerializer.RegisterSerializer(typeof(Money), _proxy);
		}

		#region serialization

		#region canonical serializer

		[Test]
		public void Serialization_Canonical_UsesCamelCasedPropertyNamesAndAlphabeticCode()
		{
			var toSerialize = new Money(14.3m, CurrencyIsoCode.XTS);

			_proxy.Serializer = new CanonicalMoneySerializer();

			string actual = toSerialize.ToJson();

			Assert.That(actual, Is.EqualTo("{ 'amount' : 14.3, 'currency' : { 'isoCode' : 'XTS' } }".Jsonify()));
		}

		[Test]
		public void Serialization_Canonical_LikeCanonicalJsonSerialization()
		{
			using (var serializer = new DataContractJsonRoundtripSerializer<Money>(dataContractSurrogate: new DataContractSurrogate()))
			{
				var toSerialize = new Money(14.3m, CurrencyIsoCode.XTS);
				string canonical = serializer.Serialize(toSerialize);

				_proxy.Serializer = new CanonicalMoneySerializer();

				string actual = toSerialize.ToJson().Replace(" ", string.Empty);
				Assert.That(actual, Is.EqualTo(canonical));
			}
		}

		[Test]
		public void Serialization_CanonicalNotNull_UsesCamelCasedPropertyNamesAndAlphabeticCode()
		{
			Money? notNull = new Money(14.3m, CurrencyIsoCode.XTS);

			_proxy.Serializer = new CanonicalMoneySerializer();

			string actual = notNull.ToJson();

			Assert.That(actual, Is.EqualTo("{ 'amount' : 14.3, 'currency' : { 'isoCode' : 'XTS' } }".Jsonify()));
		}

		[Test]
		public void Serialization_CanonicalNull_Null()
		{
			Money? @null = default(Money?);

			_proxy.Serializer = new CanonicalMoneySerializer();

			string actual = @null.ToJson();

			Assert.That(actual, Is.EqualTo("null"));
		}

		[Test]
		public void Serialization_CanonicalNotNullContainer_UsesCamelCasedPropertyNamesAndAlphabeticCode()
		{
			var notNull = new NullableMoneyContainer { PropName = new Money(14.3m, CurrencyIsoCode.XTS) };

			_proxy.Serializer = new CanonicalMoneySerializer();

			string actual = notNull.ToJson();
			Assert.That(actual, Is.EqualTo("{ 'propName' : { 'amount' : 14.3, 'currency' : { 'isoCode' : 'XTS' } } }".Jsonify()));
		}

		[Test]
		public void Serialization_CanonicalNullContainer_NullProperty()
		{
			var notNull = new NullableMoneyContainer { PropName = default(Money?) };

			_proxy.Serializer = new CanonicalMoneySerializer();

			string actual = notNull.ToJson();

			Assert.That(actual, Is.EqualTo("{ 'propName' : null }".Jsonify()));
		}

		#endregion

		#region default serializer

		[Test]
		public void Serialization_Default_UsesCamelCasedPropertyNamesAndAlphabeticCode()
		{
			var toSerialize = new Money(14.3m, CurrencyIsoCode.XTS);

			_proxy.Serializer = new DefaultMoneySerializer();

			string actual = toSerialize.ToJson();
			Assert.That(actual, Is.EqualTo("{ 'amount' : 14.3, 'currency' : 'XTS' }".Jsonify()));
		}

		[Test]
		public void Serialization_DefaultNotNull_UsesCamelCasedPropertyNamesAndAlphabeticCode()
		{
			Money? notNull = new Money(14.3m, CurrencyIsoCode.XTS);

			_proxy.Serializer = new DefaultMoneySerializer();

			string actual = notNull.ToJson();
			Assert.That(actual, Is.EqualTo("{ 'amount' : 14.3, 'currency' : 'XTS' }".Jsonify()));
		}

		[Test]
		public void Serialization_DefaultNull_Null()
		{
			Money? @null = default(Money?);

			_proxy.Serializer = new DefaultMoneySerializer();

			string actual = @null.ToJson();
			Assert.That(actual, Is.EqualTo("null"));
		}

		#endregion

		#endregion

		#region deserialization

		#region canonical serializer

		[Test]
		public void Deserialization_Canonical_ReadsCamelCasedProperties()
		{
			var expected = new Money(14.3m, CurrencyIsoCode.XTS);

			string json = "{'amount':14.3,'currency':{'isoCode':'XTS'}}";

			_proxy.Serializer = new CanonicalMoneySerializer();

			var actual = BsonSerializer.Deserialize<Money>(json);

			Assert.That(actual, Is.EqualTo(expected));
		}

		[Test]
		public void Deserialization_Canonical_LikeCanonicalJsonSerialization()
		{
			using (var serializer = new DataContractJsonRoundtripSerializer<Money>(dataContractSurrogate: new DataContractSurrogate()))
			{
				var toSerialize = new Money(14.3m, CurrencyIsoCode.XTS);
				string canonical = serializer.Serialize(toSerialize);

				_proxy.Serializer = new CanonicalMoneySerializer();

				Assert.That(BsonSerializer.Deserialize<Money>(canonical),
					Is.EqualTo(toSerialize));
			}
		}

		[Test]
		public void Deserialization_CanonicalContainer_CamelCasedProperties()
		{
			var expected = new Money(14.3m, CurrencyIsoCode.XTS);

			string json = "{'name': 'something', 'propName': {'amount':14.3,'currency':{'isoCode':'XTS'}}}";

			_proxy.Serializer = new CanonicalMoneySerializer();

			var actual = BsonSerializer.Deserialize<MoneyContainer>(json);

			Assert.That(actual.PropName, Is.EqualTo(expected));
		}

		[Test]
		public void Deserialization_CanonicalNotNull_ReadsCamelCasedProperties()
		{
			var expected = new Money(14.3m, CurrencyIsoCode.XTS);

			string json = "{'amount':14.3,'currency':{'isoCode':'XTS'}}";

			_proxy.Serializer = new CanonicalMoneySerializer();

			var actual = BsonSerializer.Deserialize<Money?>(json);

			Assert.That(actual, Is.EqualTo(expected));
		}

		[Test]
		public void Deserialization_CanonicalNull_Null()
		{

			string json = "null";

			_proxy.Serializer = new CanonicalMoneySerializer();

			var actual = BsonSerializer.Deserialize<Money?>(json);

			Assert.That(actual, Is.Null);
		}

		[Test]
		public void Deserialization_CanonicalNotNullContainer_ReadsCamelCasedProperties()
		{
			var expected = new Money(14.3m, CurrencyIsoCode.XTS);

			string json = "{'propName':{'amount':14.3,'currency':{'isoCode':'XTS'}}}";

			_proxy.Serializer = new CanonicalMoneySerializer();

			var actual = BsonSerializer.Deserialize<NullableMoneyContainer>(json);

			Assert.That(actual.PropName, Is.EqualTo(expected));
		}

		[Test]
		public void Deserialization_CanonicalNullContainer_Null()
		{
			string json = "{'propName':null}";

			_proxy.Serializer = new CanonicalMoneySerializer();

			var actual = BsonSerializer.Deserialize<NullableMoneyContainer>(json);

			Assert.That(actual.PropName, Is.Null);
		}

		#endregion

		#region default serializer

		[Test]
		public void Deserialization_Default_CamelCasedProperties()
		{
			var expected = new Money(14.3m, CurrencyIsoCode.XTS);

			string json = "{'amount':14.3,'currency':'XTS'}".Jsonify();

			_proxy.Serializer = new DefaultMoneySerializer();

			var actual = BsonSerializer.Deserialize<Money>(json);

			Assert.That(actual, Is.EqualTo(expected));
		}

		[Test]
		public void Deserialization_DefaultContainer_CamelCasedProperties()
		{
			var expected = new Money(14.3m, CurrencyIsoCode.XTS);

			string json = "{'name': 'something', 'propName': {'amount':14.3,'currency':'XTS'}}}";

			_proxy.Serializer = new DefaultMoneySerializer();

			var actual = BsonSerializer.Deserialize<MoneyContainer>(json);

			Assert.That(actual.PropName, Is.EqualTo(expected));
		}

		[Test]
		public void Deserialization_DefaultNotNull_ReadsCamelCasedProperties()
		{
			var expected = new Money(14.3m, CurrencyIsoCode.XTS);

			string json = "{'amount':14.3,'currency':'XTS'}";

			_proxy.Serializer = new DefaultMoneySerializer();

			var actual = BsonSerializer.Deserialize<Money?>(json);

			Assert.That(actual, Is.EqualTo(expected));
		}

		[Test]
		public void Deserialization_DefaultNull_Null()
		{
			string json = "null";

			_proxy.Serializer = new DefaultMoneySerializer();

			var actual = BsonSerializer.Deserialize<Money?>(json);

			Assert.That(actual, Is.Null);
		}

		#endregion

		#endregion
	}
[TestFixture]
	public class DefaultConventionsTester
	{
		private ProxySerializer<Money> _proxy;
		
		[OneTimeSetUp]
		public void Setup()
		{
			_proxy = new ProxySerializer<Money>()
				.Register();
		}

		#region serialization

		[Test, Category("exploratory")]
		public void Serialization_OutOfTheBox_UsesPascalizedMemberNamesAndStringifiedDecimal()
		{
			_proxy.Serializer = _proxy.Default;

			var toSerialize = new Money(14.3m, CurrencyIsoCode.XTS);
			var doc = new BsonDocument();
			BsonWriter writer = new BsonDocumentWriter(doc, new BsonDocumentWriterSettings());
			BsonSerializer.Serialize(writer, toSerialize);
			string @default = doc.ToJson();

			
			string expected = "{ 'CurrencyCode' : 963, 'Amount' : '14.3' }".Jsonify();
			Assert.That(@default, Is.EqualTo(expected));
		}

		[Test, Category("exploratory")]
		public void Serialization_OutOfTheBox_NotLikeCanonicalJsonSerialization()
		{
			_proxy.Serializer = _proxy.Default;

			using (var serializer = new DataContractJsonRoundtripSerializer<Money>(dataContractSurrogate: new DataContractSurrogate()))
			{
				var toSerialize = new Money(14.3m, CurrencyIsoCode.XTS);

				string @default = toSerialize.ToJson();

				string canonical = serializer.Serialize(toSerialize);
				Assert.That(@default, Is.Not.EqualTo(canonical));
			}
		}

		#region canonical serializer

		[Test]
		public void Serialization_Canonical_UsesPascalCasedPropertyNamesAndAlphabeticCode()
		{
			var toSerialize = new Money(14.3m, CurrencyIsoCode.XTS);

			_proxy.Serializer = new CanonicalMoneySerializer();

			string actual = toSerialize.ToJson();
			
			Assert.That(actual, Is.EqualTo("{ 'Amount' : 14.3, 'Currency' : { 'IsoCode' : 'XTS' } }".Jsonify()));
		}

		[Test]
		public void Serialization_CanonicalNotNullInstance_UsesPascalCasedPropertyNamesAndAlphabeticCode()
		{
			Money? notNull = new Money(14.3m, CurrencyIsoCode.XTS);

			_proxy.Serializer = new CanonicalMoneySerializer();

			string actual = notNull.ToJson();

			Assert.That(actual, Is.EqualTo("{ 'Amount' : 14.3, 'Currency' : { 'IsoCode' : 'XTS' } }".Jsonify()));
		}

		[Test]
		public void Serialization_CanonicalNullInstance_Null()
		{
			Money? @null = default(Money?);

			_proxy.Serializer = new CanonicalMoneySerializer();

			string actual = @null.ToJson();

			Assert.That(actual, Is.EqualTo("null"));
		}

		#endregion

		#region default serializer

		[Test]
		public void Serialization_Default_UsesPascalCasedPropertyNamesAndNumericCode()
		{
			var toSerialize = new Money(14.3m, CurrencyIsoCode.XTS);

			_proxy.Serializer = new DefaultMoneySerializer();

			string actual = toSerialize.ToJson();

			Assert.That(actual, Is.EqualTo("{ 'Amount' : 14.3, 'Currency' : 963 }".Jsonify()));
		}

		[Test]
		public void Serialization_DefaultNotNullInstance_UsesPascalCasedPropertyNamesAndNumericCode()
		{
			Money? notNull = new Money(14.3m, CurrencyIsoCode.XTS);

			_proxy.Serializer = new DefaultMoneySerializer();

			string actual = notNull.ToJson();

			Assert.That(actual, Is.EqualTo("{ 'Amount' : 14.3, 'Currency' : 963 }".Jsonify()));
		}

		[Test]
		public void Serialization_DefaultNullInstance_Null()
		{
			Money? @null = default(Money?);

			_proxy.Serializer = new DefaultMoneySerializer();

			string actual = @null.ToJson();

			Assert.That(actual, Is.EqualTo("null"));
		}

		[Test]
		public void Serialization_DefaultNotNullContainer_NotNullProperty()
		{
			var notNull = new NullableMoneyContainer { PropName = new Money(14.3m, CurrencyIsoCode.XTS) };

			_proxy.Serializer = new DefaultMoneySerializer();

			string actual = notNull.ToJson();

			Assert.That(actual, Is.EqualTo("{ 'PropName' : { 'Amount' : 14.3, 'Currency' : 963 } }".Jsonify()));
		}

		[Test]
		public void Serialization_DefaultNullContainer_NullProperty()
		{
			var @null = new NullableMoneyContainer { PropName = default(Money?) };

			_proxy.Serializer = new DefaultMoneySerializer();

			string actual = @null.ToJson();

			Assert.That(actual, Is.EqualTo("{ 'PropName' : null }".Jsonify()));
		}

		#endregion

		#endregion

		#region deserialization

		#region canonical serializer

		[Test]
		public void Deserialization_Canonical_ReadsPascalCasedProperties()
		{
			var expected = new Money(14.3m, CurrencyIsoCode.XTS);

			string json = "{'Amount':14.3,'Currency':{'IsoCode':'XTS'}}";

			_proxy.Serializer = new CanonicalMoneySerializer();

			var actual = BsonSerializer.Deserialize<Money>(json);

			Assert.That(actual, Is.EqualTo(expected));
		}

		[Test]
		public void Deserialization_CanonicalContainer_ReadsPascalCasedProperties()
		{
			var expected = new Money(14.3m, CurrencyIsoCode.XTS);

			string json = "{'Name': 'something', 'PropName': {'Amount':14.3,'Currency':{'IsoCode':'XTS'}}}";

			_proxy.Serializer = new CanonicalMoneySerializer();

			var actual = BsonSerializer.Deserialize<MoneyContainer>(json);

			Assert.That(actual.PropName, Is.EqualTo(expected));
		}

		[Test]
		public void Deserialization_CanonicalNotNull_ReadsPascalCasedProperties()
		{
			var expected = new Money(14.3m, CurrencyIsoCode.XTS);

			string json = "{'Amount':14.3,'Currency':{'IsoCode':'XTS'}}";

			_proxy.Serializer = new CanonicalMoneySerializer();

			var actual = BsonSerializer.Deserialize<Money?>(json);

			Assert.That(actual, Is.EqualTo(expected));
		}

		[Test]
		public void Deserialization_CanonicalNull_Null()
		{
			string json = "null";

			_proxy.Serializer = new CanonicalMoneySerializer();

			var actual = BsonSerializer.Deserialize<Money?>(json);

			Assert.That(actual, Is.Null);
		}

		#endregion

		#region default serializer

		[Test]
		public void Deserialization_Default_ReadsPascalCasedProperties()
		{
			var expected = new Money(14.3m, CurrencyIsoCode.XTS);

			string json = "{'Amount':14.3,'Currency': 963}";

			_proxy.Serializer = new DefaultMoneySerializer();

			var actual = BsonSerializer.Deserialize<Money>(json);

			Assert.That(actual, Is.EqualTo(expected));
		}

		[Test]
		public void Deserialization_DefaultContainer_ReadsPascalCasedProperties()
		{
			var expected = new Money(14.3m, CurrencyIsoCode.XTS);

			string json = "{'Name': 'something', 'PropName': {'Amount':14.3,'Currency': 963}}";

			_proxy.Serializer = new DefaultMoneySerializer();

			var actual = BsonSerializer.Deserialize<MoneyContainer>(json);

			Assert.That(actual.PropName, Is.EqualTo(expected));
		}

		[Test]
		public void Deserialization_DefaultNotNull_ReadsPascalCasedProperties()
		{
			var expected = new Money(14.3m, CurrencyIsoCode.XTS);

			string json = "{'Amount':14.3,'Currency':'XTS'}";

			_proxy.Serializer = new DefaultMoneySerializer();

			var actual = BsonSerializer.Deserialize<Money?>(json);

			Assert.That(actual, Is.EqualTo(expected));
		}

		[Test]
		public void Deserialization_DefaultNull_Null()
		{
			string json = "null";
			_proxy.Serializer = new DefaultMoneySerializer();

			var actual = BsonSerializer.Deserialize<Money?>(json);

			Assert.That(actual, Is.Null);
		}

		#endregion

		#endregion
	}
Old code in src/NMoneys.Serialization.Mongo_DB
#region serializers

	/// <summary>
	/// Converts a <see cref="Money"/> (or <see cref="Nullable{Money}"/>) instance to and from JSON in a default (standard) way.
	/// </summary>
	/// <remarks>The default (standard) way is the one that a normal serializer would do for a money instance,
	/// with an <c>Amount</c> numeric property and a <c>Currency</c> code. The <c>Currency</c> property can be
	/// serialized either as a number (the default or using a <see cref="EnumRepresentationConvention"/> with <see cref="BsonType.Int32"/>) or as
	/// a string (using a <see cref="EnumRepresentationConvention"/> with <see cref="BsonType.String"/>).
	/// <para>
	/// Property casing must be configured apart from this serializer using, for instance, another set of
	/// <see cref="IConvention"/>
	/// </para>
	/// </remarks>
	/// <example>
	/// <code>{"Amount" : 123.4, "Currency" : "XXX"}</code>
	/// <code>{"amount" : 123.4, "currency" : 999}</code>
	/// </example>
	public class DefaultMoneySerializer : MoneySerializer
	{
		public DefaultMoneySerializer() : base(
			m => new DefaultMoneyReader(m),
			m => new DefaultMoneyWriter(m))
		{ }
	}

	/// <summary>
	/// Converts a <see cref="Money"/> (or <see cref="Nullable{T}"/>) instance to and from JSON in the canonical way.
	/// </summary>
	/// <remarks>The canonical way is the one implemented in NMoneys itself, with an <c>Amount</c>
	/// numeric property and a <c>Currency</c> object with a three-letter code <c>IsoCode"</c> property.
	/// <para>
	/// Property casing must be configured apart from this serializer using, for instance, another set of
	/// <see cref="IConvention"/>
	/// </para>
	/// </remarks>
	/// <example>
	/// <code>{"Amount" : 123.4, "Currency" : {"IsoCode" : "XXX"}}</code>
	/// <code>{"amount" : 123.4, "currency" : {"isoCode" : "XXX"}}</code>
	/// </example>
	public class CanonicalMoneySerializer : MoneySerializer
	{
		public CanonicalMoneySerializer() : base(
			m => new CanonicalMoneyReader(m),
			m => new CanonicalMoneyWriter(m))
		{ }
	}

	public abstract class MoneySerializer : SerializerBase<Money>
	{
		private readonly Lazy<IMoneyReader> _reader;
		private readonly Lazy<IMoneyWriter> _writer;

		internal MoneySerializer(Func<BsonClassMap<Money>, IMoneyReader> reader,
			Func<BsonClassMap<Money>, IMoneyWriter> writer)
		{
			_reader = new Lazy<IMoneyReader>(() =>
			{
				var map = (BsonClassMap<Money>)BsonClassMap.LookupClassMap(typeof(Money));
				return reader(map);
			});

			_writer = new Lazy<IMoneyWriter>(() =>
			{
				var map = (BsonClassMap<Money>)BsonClassMap.LookupClassMap(typeof(Money));
				return writer(map);
			});
		}

		/// <summary>
		/// Deserializes a value.
		/// </summary>
		/// <param name="context">The deserialization context.</param>
		/// <param name="args">The deserialization args.</param>
		/// <returns>
		/// A deserialized value.
		/// </returns>
		public override Money Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
		{
			return _reader.Value.ReadFrom(context, args);
		}

		/// <summary>
		/// Serializes a value.
		/// </summary>
		/// <param name="context">The serialization context.</param>
		/// <param name="args">The serialization args.</param>
		/// <param name="value">The value.</param>
		public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, Money value)
		{
			_writer.Value.Write(value, context, args);
		}
	}

	#endregion

	internal static class PropertyNameCapitalizer
	{
		public static string CapitalizeAs(this string pascalCasedPropertyName, BsonMemberMap sampleMap)
		{
			var sb = new StringBuilder(pascalCasedPropertyName, pascalCasedPropertyName.Length);
			if (char.IsLower(sampleMap.ElementName[0]))
			{
				sb[0] = char.ToLowerInvariant(sb[0]);
			}
			return sb.ToString();
		}
	}

	#region support for reading

	internal interface IMoneyReader
	{
		Money ReadFrom(BsonDeserializationContext context, BsonDeserializationArgs args);
	}

	internal abstract class MoneyReader : IMoneyReader
	{
		protected readonly BsonClassMap<Money> _map;

		protected MoneyReader(BsonClassMap<Money> map)
		{
			_map = map;
		}
		public Money ReadFrom(BsonDeserializationContext context, BsonDeserializationArgs args)
		{
			context.Reader.ReadStartDocument();
			decimal amount = readAmount(context.Reader);
			CurrencyIsoCode currencyCode = readCurrency(context, args);
			context.Reader.ReadEndDocument();
			return new Money(amount, currencyCode);
		}

		protected virtual decimal readAmount(IBsonReader reader)
		{
			BsonMemberMap amountMap = _map.GetMemberMap(m => m.Amount);
			reader.ReadName(amountMap.ElementName);
			decimal amount;
			// support old numeric representations
			BsonType type = reader.GetCurrentBsonType();
			switch (type)
			{
				case BsonType.Double:
				{
					double amountRead = reader.ReadDouble();
					amount = Convert.ToDecimal(amountRead);
					break;
				}
				case BsonType.Decimal128:
				{
					Decimal128 amountRead = reader.ReadDecimal128();
					amount = Decimal128.ToDecimal(amountRead);
					break;
				}
				case BsonType.String:
				{
					string amountRead = reader.ReadString();
					amount = decimal.Parse(amountRead, CultureInfo.InvariantCulture);
					break;
				}
				default:
					var message = $"Cannot convert a {type} to a Decimal.";
					throw new NotSupportedException(message);
			}
			
			return amount;
		}

		protected abstract CurrencyIsoCode readCurrency(BsonDeserializationContext context, BsonDeserializationArgs args);
	}

	internal class DefaultMoneyReader : MoneyReader
	{
		public DefaultMoneyReader(BsonClassMap<Money> map) : base(map) { }

		protected override CurrencyIsoCode readCurrency(BsonDeserializationContext context, BsonDeserializationArgs args)
		{
			var currencyMap = _map.GetMemberMap(m => m.CurrencyCode);
			context.Reader.ReadName("Currency".CapitalizeAs(currencyMap));
			var currencyCode = (CurrencyIsoCode)currencyMap.GetSerializer()
				.Deserialize(context, args);
			return currencyCode;
		}
	}

	internal class CanonicalMoneyReader : MoneyReader
	{
		public CanonicalMoneyReader(BsonClassMap<Money> map) : base(map) { }

		protected override CurrencyIsoCode readCurrency(BsonDeserializationContext context, BsonDeserializationArgs args)
		{
			context.Reader.ReadStartDocument();

			BsonMemberMap currencyMap = _map.GetMemberMap(m => m.CurrencyCode);
			string currency = context.Reader.ReadString("IsoCode".CapitalizeAs(currencyMap));

			context.Reader.ReadEndDocument();
			CurrencyIsoCode currencyCode = Currency.Code.Parse(currency);
			return currencyCode;
		}
	}

	#endregion

	#region support for writing

	internal interface IMoneyWriter
	{
		void Write(Money value, BsonSerializationContext context, BsonSerializationArgs args);
	}

	internal abstract class MoneyWriter : IMoneyWriter
	{
		protected readonly BsonClassMap<Money> _map;

		protected MoneyWriter(BsonClassMap<Money> map)
		{
			_map = map;
		}

		public void Write(Money value, BsonSerializationContext context, BsonSerializationArgs args)
		{
			context.Writer.WriteStartDocument();
			writeAmount(value, context.Writer);
			writeCurrency(value, context, args);
			context.Writer.WriteEndDocument();
		}

		protected virtual void writeAmount(Money value, IBsonWriter writer)
		{
			BsonMemberMap amountMap = _map.GetMemberMap(m => m.Amount);
			writer.WriteDecimal128(amountMap.ElementName, new Decimal128(value.Amount));
		}
		protected abstract void writeCurrency(Money value, BsonSerializationContext context, BsonSerializationArgs args);
	}

	internal class CanonicalMoneyWriter : MoneyWriter
	{
		public CanonicalMoneyWriter(BsonClassMap<Money> map) : base(map) { }

		protected override void writeCurrency(Money value, BsonSerializationContext context, BsonSerializationArgs args)
		{
			BsonMemberMap currencyMap = _map.GetMemberMap(m => m.CurrencyCode);
			context.Writer.WriteName("Currency".CapitalizeAs(currencyMap));
			context.Writer.WriteStartDocument();
			context.Writer.WriteString("IsoCode".CapitalizeAs(currencyMap), value.CurrencyCode.AlphabeticCode());
			context.Writer.WriteEndDocument();
		}
	}

	internal class DefaultMoneyWriter : MoneyWriter
	{
		public DefaultMoneyWriter(BsonClassMap<Money> map) : base(map) { }
		protected override void writeCurrency(Money value, BsonSerializationContext context, BsonSerializationArgs args)
		{
			BsonMemberMap currencyMap = _map.GetMemberMap(m => m.CurrencyCode);
			context.Writer.WriteName("Currency".CapitalizeAs(currencyMap));
			currencyMap.GetSerializer().Serialize(context, args, value.CurrencyCode);
		}
	}

	#endregion
Old code in src/NMoneys.Serialization.Mongo_DB.Tests
public class MoneyContainer
	{
		public string Name { get; set; }
		public Money PropName { get; set; }
	}

	public class NullableMoneyContainer
	{
		public Money? PropName { get; set; }
	}
public class ProxySerializer<T> : SerializerBase<T>
	{
		private static readonly IBsonSerializer<T> _default = new BsonClassMapSerializer<T>(
			BsonClassMap.RegisterClassMap<T>());

		public ProxySerializer()
		{
			Serializer = _default;
		}

		public IBsonSerializer<T> Serializer { get; set; }

		public IBsonSerializer<T> Default { get { return _default; } }

		public override T Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
		{
			return Serializer.Deserialize(context, args);
		}

		public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, T value)
		{
			Serializer.Serialize(context, args, value);
		}

		public ProxySerializer<T> Register()
		{
			BsonSerializer.RegisterSerializer(typeof (T), this);
			return this;
		}
	}
public class TestingConventions
	{
		private static readonly string _name = "testing";
		private readonly ConventionPack _pack;

		public TestingConventions(params IConvention[] conventions)
		{
			_pack = new ConventionPack();
			_pack.AddRange(conventions);
		}

		public void Register()
		{
			ConventionRegistry.Register(_name, _pack, _ => true);
		}
	}
[TestFixture, Explicit("we cannot change conventions once applied :,-(")]
	public class CustomConventionsTester
	{
		private ProxySerializer<Money> _proxy;

		[OneTimeSetUp]
		public void Setup()
		{
			new TestingConventions(
					new CamelCaseElementNameConvention(),
					new EnumRepresentationConvention(BsonType.String))
				.Register();

			_proxy = new ProxySerializer<Money>();
			BsonSerializer.RegisterSerializer(typeof(Money), _proxy);
		}

		#region serialization

		#region default serializer

		[Test]
		public void Serialization_Default_UsesCamelCasedPropertyNamesAndAlphabeticCode()
		{
			var toSerialize = new Money(14.3m, CurrencyIsoCode.XTS);

			_proxy.Serializer = new DefaultMoneySerializer();

			string actual = toSerialize.ToJson();
			Assert.That(actual, Is.EqualTo("{ 'amount' : NumberDecimal('14.3'), 'currency' : 'XTS' }").AsJson());
		}

		[Test]
		public void Serialization_DefaultNotNull_UsesCamelCasedPropertyNamesAndAlphabeticCode()
		{
			Money? notNull = new Money(14.3m, CurrencyIsoCode.XTS);

			_proxy.Serializer = new DefaultMoneySerializer();

			string actual = notNull.ToJson();
			Assert.That(actual, Is.EqualTo("{ 'amount' : NumberDecimal('14.3'), 'currency' : 'XTS' }").AsJson());
		}

		[Test]
		public void Serialization_DefaultNull_Null()
		{
			Money? @null = default(Money?);

			_proxy.Serializer = new DefaultMoneySerializer();

			string actual = @null.ToJson();
			Assert.That(actual, Is.EqualTo("null"));
		}

		#endregion

		#region canonical serializer

		[Test]
		public void Serialization_Canonical_UsesCamelCasedPropertyNamesAndAlphabeticCode()
		{
			var toSerialize = new Money(14.3m, CurrencyIsoCode.XTS);

			_proxy.Serializer = new CanonicalMoneySerializer();

			string actual = toSerialize.ToJson();

			Assert.That(actual, Is.EqualTo("{ 'amount' : NumberDecimal('14.3'), 'currency' : { 'isoCode' : 'XTS' } }").AsJson());
		}

		[Test]
		public void Serialization_Canonical_SortOfLikeCanonicalJsonSerialization()
		{
			using (var serializer = new DataContractJsonRoundtripSerializer<Money>( dataContractSurrogate: new DataContractSurrogate()))
			{
				var toSerialize = new Money(14.3m, CurrencyIsoCode.XTS);
				string canonical = serializer.Serialize(toSerialize);

				_proxy.Serializer = new CanonicalMoneySerializer();

				string actual = toSerialize.ToJson()
					// spacing
					.Replace(" ", string.Empty)
					// non-numerical figure representation
					.Replace("NumberDecimal(\"", string.Empty)
					.Replace("\")", string.Empty);

				Assert.That(actual, Is.EqualTo(canonical));
			}
		}

		[Test]
		public void Serialization_CanonicalNotNull_UsesCamelCasedPropertyNamesAndAlphabeticCode()
		{
			Money? notNull = new Money(14.3m, CurrencyIsoCode.XTS);

			_proxy.Serializer = new CanonicalMoneySerializer();

			string actual = notNull.ToJson();

			Assert.That(actual, Is.EqualTo("{ 'amount' : NumberDecimal('14.3'), 'currency' : { 'isoCode' : 'XTS' } }").AsJson());
		}

		[Test]
		public void Serialization_CanonicalNull_Null()
		{
			Money? @null = default(Money?);

			_proxy.Serializer = new CanonicalMoneySerializer();

			string actual = @null.ToJson();

			Assert.That(actual, Is.EqualTo("null"));
		}

		[Test]
		public void Serialization_CanonicalNotNullContainer_UsesCamelCasedPropertyNamesAndAlphabeticCode()
		{
			var notNull = new NullableMoneyContainer { PropName = new Money(14.3m, CurrencyIsoCode.XTS) };

			_proxy.Serializer = new CanonicalMoneySerializer();

			string actual = notNull.ToJson();
			Assert.That(actual, Is.EqualTo("{ 'propName' : { 'amount' : NumberDecimal('14.3'), 'currency' : { 'isoCode' : 'XTS' } } }").AsJson());
		}

		[Test]
		public void Serialization_CanonicalNullContainer_NullProperty()
		{
			var notNull = new NullableMoneyContainer { PropName = default(Money?) };

			_proxy.Serializer = new CanonicalMoneySerializer();

			string actual = notNull.ToJson();

			Assert.That(actual, Is.EqualTo("{ 'propName' : null }").AsJson());
		}

		#endregion

		#endregion

		#region deserialization

		#region canonical serializer

		[Test]
		public void Deserialization_Canonical_ReadsCamelCasedProperties()
		{
			var expected = new Money(14.3m, CurrencyIsoCode.XTS);

			string json = "{'amount':NumberDecimal('14.3'),'currency':{'isoCode':'XTS'}}";

			_proxy.Serializer = new CanonicalMoneySerializer();

			var actual = BsonSerializer.Deserialize<Money>(json);

			Assert.That(actual, Is.EqualTo(expected));
		}

		[Test]
		public void Deserialization_Canonical_LikeCanonicalJsonSerialization()
		{
			using (var serializer = new DataContractJsonRoundtripSerializer<Money>(dataContractSurrogate: new DataContractSurrogate()))
			{
				var toSerialize = new Money(14.3m, CurrencyIsoCode.XTS);
				string canonical = serializer.Serialize(toSerialize);

				_proxy.Serializer = new CanonicalMoneySerializer();

				Assert.That(BsonSerializer.Deserialize<Money>(canonical),
					Is.EqualTo(toSerialize));
			}
		}

		[Test]
		public void Deserialization_CanonicalContainer_CamelCasedProperties()
		{
			var expected = new Money(14.3m, CurrencyIsoCode.XTS);

			string json = "{'name': 'something', 'propName': {'amount':NumberDecimal('14.3'),'currency':{'isoCode':'XTS'}}}";

			_proxy.Serializer = new CanonicalMoneySerializer();

			var actual = BsonSerializer.Deserialize<MoneyContainer>(json);

			Assert.That(actual.PropName, Is.EqualTo(expected));
		}

		[Test]
		public void Deserialization_CanonicalNotNull_ReadsCamelCasedProperties()
		{
			var expected = new Money(14.3m, CurrencyIsoCode.XTS);

			string json = "{'amount':NumberDecimal('14.3'),'currency':{'isoCode':'XTS'}}";

			_proxy.Serializer = new CanonicalMoneySerializer();

			var actual = BsonSerializer.Deserialize<Money?>(json);

			Assert.That(actual, Is.EqualTo(expected));
		}

		[Test]
		public void Deserialization_CanonicalNull_Null()
		{

			string json = "null";

			_proxy.Serializer = new CanonicalMoneySerializer();

			var actual = BsonSerializer.Deserialize<Money?>(json);

			Assert.That(actual, Is.Null);
		}

		[Test]
		public void Deserialization_CanonicalNotNullContainer_ReadsCamelCasedProperties()
		{
			var expected = new Money(14.3m, CurrencyIsoCode.XTS);

			string json = "{'propName':{'amount':NumberDecimal('14.3'),'currency':{'isoCode':'XTS'}}}";

			_proxy.Serializer = new CanonicalMoneySerializer();

			var actual = BsonSerializer.Deserialize<NullableMoneyContainer>(json);

			Assert.That(actual.PropName, Is.EqualTo(expected));
		}

		[Test]
		public void Deserialization_CanonicalNullContainer_Null()
		{
			string json = "{'propName':null}";

			_proxy.Serializer = new CanonicalMoneySerializer();

			var actual = BsonSerializer.Deserialize<NullableMoneyContainer>(json);

			Assert.That(actual.PropName, Is.Null);
		}

		#endregion

		#region default serializer

		[Test]
		public void Deserialization_Default_CamelCasedProperties()
		{
			var expected = new Money(14.3m, CurrencyIsoCode.XTS);

			string json = "{'amount':NumberDecimal('14.3'),'currency':'XTS'}";

			_proxy.Serializer = new DefaultMoneySerializer();

			var actual = BsonSerializer.Deserialize<Money>(json);

			Assert.That(actual, Is.EqualTo(expected));
		}

		[Test]
		public void Deserialization_DefaultContainer_CamelCasedProperties()
		{
			var expected = new Money(14.3m, CurrencyIsoCode.XTS);

			string json = "{'name': 'something', 'propName': {'amount':NumberDecimal('14.3'),'currency':'XTS'}}}";

			_proxy.Serializer = new DefaultMoneySerializer();

			var actual = BsonSerializer.Deserialize<MoneyContainer>(json);

			Assert.That(actual.PropName, Is.EqualTo(expected));
		}

		[Test]
		public void Deserialization_DefaultNotNull_ReadsCamelCasedProperties()
		{
			var expected = new Money(14.3m, CurrencyIsoCode.XTS);

			string json = "{'amount':NumberDecimal('14.3'),'currency':'XTS'}";

			_proxy.Serializer = new DefaultMoneySerializer();

			var actual = BsonSerializer.Deserialize<Money?>(json);

			Assert.That(actual, Is.EqualTo(expected));
		}

		[Test]
		public void Deserialization_DefaultNull_Null()
		{
			string json = "null";

			_proxy.Serializer = new DefaultMoneySerializer();

			var actual = BsonSerializer.Deserialize<Money?>(json);

			Assert.That(actual, Is.Null);
		}

		#endregion

		#endregion
	}
[TestFixture]
	public class DefaultConventionsTester
	{
		private ProxySerializer<Money> _proxy;
		
		[OneTimeSetUp]
		public void Setup()
		{
			_proxy = new ProxySerializer<Money>()
				.Register();
		}

		#region serialization

		[Test, Category("exploratory")]
		public void Serialization_OutOfTheBox_UsesPascalizedMemberNamesAndStringifiedDecimalAndType()
		{
			_proxy.Serializer = _proxy.Default;

			var toSerialize = new Money(14.3m, CurrencyIsoCode.XTS);
			var doc = new BsonDocument();
			BsonWriter writer = new BsonDocumentWriter(doc, new BsonDocumentWriterSettings());
			BsonSerializer.Serialize(writer, toSerialize);
			string @default = doc.ToJson();


			string expected = "{ '_t' : 'Money', 'CurrencyCode' : 963, 'Amount' : '14.3' }";
			Assert.That(@default, Is.EqualTo(expected).AsJson());
		}

		[Test, Category("exploratory")]
		public void Serialization_OutOfTheBox_NotLikeCanonicalJsonSerialization()
		{
			_proxy.Serializer = _proxy.Default;

			using (var serializer = new DataContractRoundtripSerializer<Money>())
			{
				var toSerialize = new Money(14.3m, CurrencyIsoCode.XTS);

				string @default = toSerialize.ToJson();

				string canonical = serializer.Serialize(toSerialize);
				Assert.That(@default, Is.Not.EqualTo(canonical));
			}
		}

		#region canonical serializer

		[Test]
		public void Serialization_Canonical_UsesPascalCasedPropertyNamesAndAlphabeticCode()
		{
			var toSerialize = new Money(14.3m, CurrencyIsoCode.XTS);

			_proxy.Serializer = new CanonicalMoneySerializer();

			string actual = toSerialize.ToJson();

			Assert.That(actual, Is.EqualTo("{ 'Amount' : NumberDecimal('14.3'), 'Currency' : { 'IsoCode' : 'XTS' } }").AsJson());
		}

		[Test]
		public void Serialization_CanonicalNotNullInstance_UsesPascalCasedPropertyNamesAndAlphabeticCode()
		{
			Money? notNull = new Money(14.3m, CurrencyIsoCode.XTS);

			_proxy.Serializer = new CanonicalMoneySerializer();

			string actual = notNull.ToJson();

			Assert.That(actual, Is.EqualTo("{ 'Amount' : NumberDecimal('14.3'), 'Currency' : { 'IsoCode' : 'XTS' } }").AsJson());
		}

		[Test]
		public void Serialization_CanonicalNullInstance_Null()
		{
			Money? @null = default(Money?);

			_proxy.Serializer = new CanonicalMoneySerializer();

			string actual = @null.ToJson();

			Assert.That(actual, Is.EqualTo("null"));
		}

		#endregion

		#region default serializer

		[Test]
		public void Serialization_Default_UsesPascalCasedPropertyNamesAndNumericCode()
		{
			var toSerialize = new Money(14.3m, CurrencyIsoCode.XTS);

			_proxy.Serializer = new DefaultMoneySerializer();

			string actual = toSerialize.ToJson();

			Assert.That(actual, Is.EqualTo("{ 'Amount' : NumberDecimal('14.3'), 'Currency' : 963 }").AsJson());
		}

		[Test]
		public void Serialization_DefaultNotNullInstance_UsesPascalCasedPropertyNamesAndNumericCode()
		{
			Money? notNull = new Money(14.3m, CurrencyIsoCode.XTS);

			_proxy.Serializer = new DefaultMoneySerializer();

			string actual = notNull.ToJson();

			Assert.That(actual, Is.EqualTo("{ 'Amount' : NumberDecimal('14.3'), 'Currency' : 963 }").AsJson());
		}

		[Test]
		public void Serialization_DefaultNullInstance_Null()
		{
			Money? @null = default(Money?);

			_proxy.Serializer = new DefaultMoneySerializer();

			string actual = @null.ToJson();

			Assert.That(actual, Is.EqualTo("null"));
		}

		[Test]
		public void Serialization_DefaultNotNullContainer_NotNullProperty()
		{
			var notNull = new NullableMoneyContainer { PropName = new Money(14.3m, CurrencyIsoCode.XTS) };

			_proxy.Serializer = new DefaultMoneySerializer();

			string actual = notNull.ToJson();

			Assert.That(actual, Is.EqualTo("{ 'PropName' : { 'Amount' : NumberDecimal('14.3'), 'Currency' : 963 } }").AsJson());
		}

		[Test]
		public void Serialization_DefaultNullContainer_NullProperty()
		{
			var @null = new NullableMoneyContainer { PropName = default(Money?) };

			_proxy.Serializer = new DefaultMoneySerializer();

			string actual = @null.ToJson();

			Assert.That(actual, Is.EqualTo("{ 'PropName' : null }").AsJson());
		}

		#endregion

		#endregion

		#region deserialization

		#region canonical serializer

		[Test]
		public void Deserialization_Canonical_ReadsPascalCasedProperties()
		{
			var expected = new Money(14.3m, CurrencyIsoCode.XTS);

			string json = "{'Amount':14.3,'Currency':{'IsoCode':'XTS'}}";

			_proxy.Serializer = new CanonicalMoneySerializer();

			var actual = BsonSerializer.Deserialize<Money>(json);

			Assert.That(actual, Is.EqualTo(expected));
		}

		[Test]
		public void Deserialization_CanonicalContainer_ReadsPascalCasedProperties()
		{
			var expected = new Money(14.3m, CurrencyIsoCode.XTS);

			string json = "{'Name': 'something', 'PropName': {'Amount':14.3,'Currency':{'IsoCode':'XTS'}}}";

			_proxy.Serializer = new CanonicalMoneySerializer();

			var actual = BsonSerializer.Deserialize<MoneyContainer>(json);

			Assert.That(actual.PropName, Is.EqualTo(expected));
		}

		[Test]
		public void Deserialization_CanonicalNotNull_ReadsPascalCasedProperties()
		{
			var expected = new Money(14.3m, CurrencyIsoCode.XTS);

			string json = "{'Amount':14.3,'Currency':{'IsoCode':'XTS'}}";

			_proxy.Serializer = new CanonicalMoneySerializer();

			var actual = BsonSerializer.Deserialize<Money?>(json);

			Assert.That(actual, Is.EqualTo(expected));
		}

		[Test]
		public void Deserialization_CanonicalNull_Null()
		{
			string json = "null";

			_proxy.Serializer = new CanonicalMoneySerializer();

			var actual = BsonSerializer.Deserialize<Money?>(json);

			Assert.That(actual, Is.Null);
		}

		#endregion

		#region default serializer

		[Test]
		public void Deserialization_Default_ReadsPascalCasedProperties()
		{
			var expected = new Money(14.3m, CurrencyIsoCode.XTS);

			string json = "{'Amount':14.3,'Currency': 963}";

			_proxy.Serializer = new DefaultMoneySerializer();

			var actual = BsonSerializer.Deserialize<Money>(json);

			Assert.That(actual, Is.EqualTo(expected));
		}

		[Test]
		public void Deserialization_DefaultContainer_ReadsPascalCasedProperties()
		{
			var expected = new Money(14.3m, CurrencyIsoCode.XTS);

			string json = "{'Name': 'something', 'PropName': {'Amount':14.3,'Currency': 963}}";

			_proxy.Serializer = new DefaultMoneySerializer();

			var actual = BsonSerializer.Deserialize<MoneyContainer>(json);

			Assert.That(actual.PropName, Is.EqualTo(expected));
		}

		[Test]
		public void Deserialization_DefaultNotNull_ReadsPascalCasedProperties()
		{
			var expected = new Money(14.3m, CurrencyIsoCode.XTS);

			string json = "{'Amount':14.3,'Currency':'XTS'}";

			_proxy.Serializer = new DefaultMoneySerializer();

			var actual = BsonSerializer.Deserialize<Money?>(json);

			Assert.That(actual, Is.EqualTo(expected));
		}

		[Test]
		public void Deserialization_DefaultNull_Null()
		{
			string json = "null";
			_proxy.Serializer = new DefaultMoneySerializer();

			var actual = BsonSerializer.Deserialize<Money?>(json);

			Assert.That(actual, Is.Null);
		}

		#endregion

		#endregion
	}
@dgg dgg added this to the Serialization milestone Dec 29, 2023
@dgg dgg self-assigned this Dec 29, 2023
@dgg dgg mentioned this issue Jan 27, 2024
@dgg dgg closed this as completed Jan 28, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant