Skip to content

Commit c9f9a10

Browse files
committed
Non-generic ValueObjectJsonConverterFactory prioritizes JsonConverterAttribute over its own converter
1 parent 3143a53 commit c9f9a10

File tree

4 files changed

+112
-5
lines changed

4 files changed

+112
-5
lines changed

Directory.Build.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
<PropertyGroup>
44
<Copyright>(c) $([System.DateTime]::Now.Year), Pawel Gerr. All rights reserved.</Copyright>
5-
<VersionPrefix>7.2.2</VersionPrefix>
5+
<VersionPrefix>7.3.0</VersionPrefix>
66
<Authors>Pawel Gerr</Authors>
77
<GenerateDocumentationFile>true</GenerateDocumentationFile>
88
<PackageProjectUrl>https://github.com/PawelGerr/Thinktecture.Runtime.Extensions</PackageProjectUrl>

src/Thinktecture.Runtime.Extensions.Json/Text/Json/Serialization/ValueObjectJsonConverterFactory.cs

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,15 +57,49 @@ public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializer
5757
/// </summary>
5858
public sealed class ValueObjectJsonConverterFactory : JsonConverterFactory
5959
{
60+
private readonly bool _skipValueObjectsWithJsonConverterAttribute;
61+
62+
/// <summary>
63+
/// Initializes new instance of <see cref="ValueObjectJsonConverterFactory"/>.
64+
/// </summary>
65+
public ValueObjectJsonConverterFactory()
66+
: this(true)
67+
{
68+
}
69+
70+
/// <summary>
71+
/// Initializes new instance of <see cref="ValueObjectJsonConverterFactory"/>.
72+
/// </summary>
73+
/// <param name="skipValueObjectsWithJsonConverterAttribute">
74+
/// Indication whether to skip value objects with <see cref="JsonConverterAttribute"/>.
75+
/// </param>
76+
public ValueObjectJsonConverterFactory(
77+
bool skipValueObjectsWithJsonConverterAttribute)
78+
{
79+
_skipValueObjectsWithJsonConverterAttribute = skipValueObjectsWithJsonConverterAttribute;
80+
}
81+
6082
/// <inheritdoc />
6183
public override bool CanConvert(Type typeToConvert)
6284
{
6385
// Handling of Nullable<T> should be done by System.Text.Json
6486
if (typeToConvert.IsValueType && Nullable.GetUnderlyingType(typeToConvert) is not null)
6587
return false;
6688

67-
return KeyedValueObjectMetadataLookup.Find(typeToConvert) is not null
68-
|| typeToConvert.GetCustomAttributes<ValueObjectFactoryAttribute>().Any(a => a.UseForSerialization.HasFlag(SerializationFrameworks.SystemTextJson));
89+
var valueObjectType = GetValueObjectType(typeToConvert);
90+
91+
if (valueObjectType is null)
92+
return false;
93+
94+
return !_skipValueObjectsWithJsonConverterAttribute || !valueObjectType.GetCustomAttributes<JsonConverterAttribute>().Any();
95+
}
96+
97+
private static Type? GetValueObjectType(Type typeToConvert)
98+
{
99+
if (typeToConvert.GetCustomAttributes<ValueObjectFactoryAttribute>().Any(a => a.UseForSerialization.HasFlag(SerializationFrameworks.SystemTextJson)))
100+
return typeToConvert;
101+
102+
return KeyedValueObjectMetadataLookup.Find(typeToConvert)?.Type;
69103
}
70104

71105
/// <inheritdoc />

test/Thinktecture.Runtime.Extensions.Json.Tests/Text/Json/Serialization/ValueObjectJsonConverterFactoryTests/RoundTrip.cs

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -225,9 +225,20 @@ [new TestStruct<IntBasedReferenceValueObject>(IntBasedReferenceValueObject.Creat
225225

226226
[Theory]
227227
[MemberData(nameof(ObjectWithStructTestData))]
228-
public void Should_roundtrip_serialize_types_with_struct_properties_using_non_generic_factory(object obj, string expectedJson)
228+
public void Should_roundtrip_serialize_types_with_struct_properties_using_non_generic_factory(
229+
object obj,
230+
string expectedJson)
229231
{
230-
var options = new JsonSerializerOptions { Converters = { new ValueObjectJsonConverterFactory() } };
232+
Roundtrip_serialize_types_with_struct_properties_using_non_generic_factory(true, obj, expectedJson);
233+
Roundtrip_serialize_types_with_struct_properties_using_non_generic_factory(false, obj, expectedJson);
234+
}
235+
236+
private static void Roundtrip_serialize_types_with_struct_properties_using_non_generic_factory(
237+
bool skipValueObjectsWithJsonConverterAttribute,
238+
object obj,
239+
string expectedJson)
240+
{
241+
var options = new JsonSerializerOptions { Converters = { new ValueObjectJsonConverterFactory(skipValueObjectsWithJsonConverterAttribute) } };
231242

232243
var json = JsonSerializer.Serialize(obj, options);
233244
json.Should().Be(expectedJson);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
using System.Collections.Generic;
2+
using Newtonsoft.Json;
3+
using Thinktecture.Json;
4+
using Thinktecture.Runtime.Tests.TestEnums;
5+
using Thinktecture.Runtime.Tests.TestValueObjects;
6+
7+
namespace Thinktecture.Runtime.Tests.Json.ValueObjectNewtonsoftJsonConverterTests;
8+
9+
public class RoundTrip : JsonTestsBase
10+
{
11+
[Fact]
12+
public void Should_roundtrip_serialize_dictionary_with_string_based_enum_key()
13+
{
14+
var dictionary = new Dictionary<TestSmartEnum_Class_StringBased, int>
15+
{
16+
{ TestSmartEnum_Class_StringBased.Value1, 1 },
17+
{ TestSmartEnum_Class_StringBased.Value2, 2 }
18+
};
19+
20+
var options = new JsonSerializerSettings { Converters = { new ValueObjectNewtonsoftJsonConverter() } };
21+
22+
var json = JsonConvert.SerializeObject(dictionary, options);
23+
var deserializedDictionary = JsonConvert.DeserializeObject<Dictionary<TestSmartEnum_Class_StringBased, int>>(json, options);
24+
25+
dictionary.Should().BeEquivalentTo(deserializedDictionary);
26+
}
27+
28+
public static IEnumerable<object[]> ObjectWithStructTestData =
29+
[
30+
[new { Prop = IntBasedStructValueObject.Create(42) }, """{"Prop":42}"""],
31+
[new { Prop = (IntBasedStructValueObject?)IntBasedStructValueObject.Create(42) }, """{"Prop":42}"""],
32+
[new { Prop = IntBasedReferenceValueObject.Create(42) }, """{"Prop":42}"""],
33+
[new TestStruct<IntBasedStructValueObject>(IntBasedStructValueObject.Create(42)), """{"Prop":42}"""],
34+
[new TestStruct<IntBasedStructValueObject?>(IntBasedStructValueObject.Create(42)), """{"Prop":42}"""],
35+
[new TestStruct<IntBasedReferenceValueObject>(IntBasedReferenceValueObject.Create(42)), """{"Prop":42}"""],
36+
];
37+
38+
[Theory]
39+
[MemberData(nameof(ObjectWithStructTestData))]
40+
public void Should_roundtrip_serialize_types_with_struct_properties_using_non_generic_factory(
41+
object obj,
42+
string expectedJson)
43+
{
44+
var options = new JsonSerializerSettings { Converters = { new ValueObjectNewtonsoftJsonConverter() } };
45+
46+
var json = JsonConvert.SerializeObject(obj, options);
47+
json.Should().Be(expectedJson);
48+
49+
var deserializedObj = JsonConvert.DeserializeObject(json, obj.GetType(), options);
50+
obj.Should().BeEquivalentTo(deserializedObj);
51+
}
52+
53+
private struct TestStruct<T>
54+
{
55+
public T Prop { get; set; }
56+
57+
public TestStruct(T prop)
58+
{
59+
Prop = prop;
60+
}
61+
}
62+
}

0 commit comments

Comments
 (0)