Skip to content

Commit

Permalink
ValueObjectMessageFormatterResolver prioritizes MessagePackFormatterA…
Browse files Browse the repository at this point in the history
…ttribute over its own formatter
  • Loading branch information
PawelGerr committed Apr 18, 2024
1 parent c9f9a10 commit bea16c8
Show file tree
Hide file tree
Showing 3 changed files with 106 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -91,15 +91,21 @@ public override bool CanConvert(Type typeToConvert)
if (valueObjectType is null)
return false;

return !_skipValueObjectsWithJsonConverterAttribute || !valueObjectType.GetCustomAttributes<JsonConverterAttribute>().Any();
return !_skipValueObjectsWithJsonConverterAttribute || valueObjectType.GetCustomAttribute<JsonConverterAttribute>() is null;
}

private static Type? GetValueObjectType(Type typeToConvert)
{
// typeToConvert could be derived type (like nested Smart Enum)
var metadata = KeyedValueObjectMetadataLookup.Find(typeToConvert);

if (metadata is not null)
return metadata.Type;

if (typeToConvert.GetCustomAttributes<ValueObjectFactoryAttribute>().Any(a => a.UseForSerialization.HasFlag(SerializationFrameworks.SystemTextJson)))
return typeToConvert;

return KeyedValueObjectMetadataLookup.Find(typeToConvert)?.Type;
return null;
}

/// <inheritdoc />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,33 @@ public class ValueObjectMessageFormatterResolver : IFormatterResolver
/// </summary>
public static readonly IFormatterResolver Instance = new ValueObjectMessageFormatterResolver();

private readonly bool _skipValueObjectsWithMessagePackFormatterAttribute;

/// <summary>
/// Initializes new instance of <see cref="ValueObjectMessageFormatterResolver"/>.
/// </summary>
public ValueObjectMessageFormatterResolver()
: this(true)
{
}

/// <summary>
/// Initializes new instance of <see cref="ValueObjectMessageFormatterResolver"/>.
/// </summary>
/// <param name="skipValueObjectsWithMessagePackFormatterAttribute">
/// Indication whether to skip value objects with <see cref="MessagePackFormatterAttribute"/>.
/// </param>
public ValueObjectMessageFormatterResolver(bool skipValueObjectsWithMessagePackFormatterAttribute)
{
_skipValueObjectsWithMessagePackFormatterAttribute = skipValueObjectsWithMessagePackFormatterAttribute;
}

/// <inheritdoc />
public IMessagePackFormatter<T>? GetFormatter<T>()
{
if (_skipValueObjectsWithMessagePackFormatterAttribute && Cache<T>.HasMessagePackFormatterAttribute)
return null;

var formatter = Cache<T>.Formatter;

if (formatter != null)
Expand All @@ -34,8 +58,10 @@ private static class Cache<T>
{
public static readonly IMessagePackFormatter<T>? Formatter;

// ReSharper disable once StaticMemberInGenericType
// ReSharper disable StaticMemberInGenericType
public static readonly bool HasMessagePackFormatterAttribute;
public static readonly string? InitError;
// ReSharper restore StaticMemberInGenericType

static Cache()
{
Expand Down Expand Up @@ -81,6 +107,7 @@ static Cache()
}

Formatter = (IMessagePackFormatter<T>)formatter;
HasMessagePackFormatterAttribute = type.GetCustomAttribute<MessagePackFormatterAttribute>() is not null;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -72,12 +72,12 @@ public void Should_roundtrip_serialize_int_based_enum_having_formatter()
}

public static IEnumerable<object[]> DataForValueObject => new[]
{
new object[] { new ClassWithIntBasedEnum(IntegerEnum.Item1) },
new object[] { new ClassWithStringBasedEnum(TestEnum.Item1) },
new object[] { TestEnum.Item1 },
new object[] { IntegerEnum.Item1 }
};
{
new object[] { new ClassWithIntBasedEnum(IntegerEnum.Item1) },
new object[] { new ClassWithStringBasedEnum(TestEnum.Item1) },
new object[] { TestEnum.Item1 },
new object[] { IntegerEnum.Item1 }
};

[Theory]
[MemberData(nameof(DataForValueObject))]
Expand All @@ -95,15 +95,15 @@ private void RoundTripSerializeWithCustomOptions<T>(T value)
}

public static IEnumerable<object[]> DataForValueObjectWithMultipleProperties => new[]
{
new object[] { null },
new object[] { ValueObjectWithMultipleProperties.Create(0, null, null!) },
new object[] { ValueObjectWithMultipleProperties.Create(0, null, null!) },
new object[] { ValueObjectWithMultipleProperties.Create(0, 0, String.Empty) },
new object[] { ValueObjectWithMultipleProperties.Create(1, 42, "Value") },
new object[] { ValueObjectWithMultipleProperties.Create(1, 42, "Value") },
new object[] { ValueObjectWithMultipleProperties.Create(1, 42, "Value") }
};
{
new object[] { null },
new object[] { ValueObjectWithMultipleProperties.Create(0, null, null!) },
new object[] { ValueObjectWithMultipleProperties.Create(0, null, null!) },
new object[] { ValueObjectWithMultipleProperties.Create(0, 0, String.Empty) },
new object[] { ValueObjectWithMultipleProperties.Create(1, 42, "Value") },
new object[] { ValueObjectWithMultipleProperties.Create(1, 42, "Value") },
new object[] { ValueObjectWithMultipleProperties.Create(1, 42, "Value") }
};

[Theory]
[MemberData(nameof(DataForValueObjectWithMultipleProperties))]
Expand Down Expand Up @@ -182,4 +182,59 @@ public void Should_deserialize_complex_value_object_having_custom_factory()

value.Should().BeEquivalentTo(BoundaryWithCustomFactoryNames.Get(1, 2));
}

public static IEnumerable<object[]> ObjectWithStructTestData =
[
[new TestClass<IntBasedStructValueObject>(IntBasedStructValueObject.Create(42))],
[new TestClass<IntBasedStructValueObject?>(IntBasedStructValueObject.Create(42))],
[new TestClass<IntBasedReferenceValueObject>(IntBasedReferenceValueObject.Create(42))],
[new TestStruct<IntBasedStructValueObject>(IntBasedStructValueObject.Create(42))],
[new TestStruct<IntBasedStructValueObject?>(IntBasedStructValueObject.Create(42))],
[new TestStruct<IntBasedReferenceValueObject>(IntBasedReferenceValueObject.Create(42))],
];

[Theory]
[MemberData(nameof(ObjectWithStructTestData))]
public void Should_roundtrip_serialize_types_with_struct_properties_using_resolver(object obj)
{
Roundtrip_serialize_types_with_struct_properties_using_resolver(true, obj);
Roundtrip_serialize_types_with_struct_properties_using_resolver(false, obj);
}

private static void Roundtrip_serialize_types_with_struct_properties_using_resolver(
bool skipValueObjectsWithMessagePackFormatter,
object obj)
{
var resolver = CompositeResolver.Create(new ValueObjectMessageFormatterResolver(skipValueObjectsWithMessagePackFormatter), StandardResolver.Instance);
var options = MessagePackSerializerOptions.Standard.WithResolver(resolver);

var bytes = MessagePackSerializer.Serialize(obj, options, CancellationToken.None);
var value = MessagePackSerializer.Deserialize(obj.GetType(), bytes, options, CancellationToken.None);

value.Should().BeEquivalentTo(obj);
}

[MessagePackObject]
public struct TestClass<T>
{
[Key(0)]
public T Prop { get; set; }

public TestClass(T prop)
{
Prop = prop;
}
}

[MessagePackObject]
public struct TestStruct<T>
{
[Key(0)]
public T Prop { get; set; }

public TestStruct(T prop)
{
Prop = prop;
}
}
}

0 comments on commit bea16c8

Please sign in to comment.