Skip to content

Commit af2d7ad

Browse files
authored
Add TypeConverterJsonAdapter. (#19)
Authored-by: Konstantin Sharon <[email protected]>
1 parent 2641376 commit af2d7ad

File tree

3 files changed

+146
-0
lines changed

3 files changed

+146
-0
lines changed
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
using System.ComponentModel;
2+
using System.Linq;
3+
using System.Reflection;
4+
5+
namespace System.Text.Json.Serialization
6+
{
7+
/// <summary>
8+
/// Adapter between <see cref="TypeConverter"/> and <see cref="JsonConverter"/>.
9+
/// </summary>
10+
/// <typeparam name="T">The type being converted.</typeparam>
11+
public class TypeConverterJsonAdapter<T> : JsonConverter<T>
12+
{
13+
/// <inheritdoc/>
14+
public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
15+
{
16+
TypeConverter converter = TypeDescriptor.GetConverter(typeToConvert);
17+
string text = reader.GetString();
18+
return (T)converter.ConvertFromString(text);
19+
}
20+
21+
/// <inheritdoc/>
22+
public override void Write(Utf8JsonWriter writer, T objectToWrite, JsonSerializerOptions options)
23+
{
24+
if (writer is null)
25+
throw new ArgumentNullException(nameof(writer));
26+
27+
TypeConverter converter = TypeDescriptor.GetConverter(objectToWrite);
28+
string text = converter.ConvertToString(objectToWrite);
29+
writer.WriteStringValue(text);
30+
}
31+
32+
/// <inheritdoc/>
33+
public override bool CanConvert(Type typeToConvert)
34+
{
35+
bool hasConverter = typeToConvert.GetCustomAttributes<TypeConverterAttribute>(inherit: true).Any();
36+
return hasConverter;
37+
}
38+
}
39+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
using System.ComponentModel;
2+
using System.Linq;
3+
using System.Reflection;
4+
5+
namespace System.Text.Json.Serialization
6+
{
7+
/// <summary>
8+
/// A factory used to create various <see cref="TypeConverterJsonAdapter{T}"/> instances.
9+
/// </summary>
10+
public class TypeConverterJsonAdapterFactory : JsonConverterFactory
11+
{
12+
/// <inheritdoc />
13+
public override bool CanConvert(Type typeToConvert)
14+
{
15+
bool hasConverter = typeToConvert.GetCustomAttributes<TypeConverterAttribute>(inherit: true).Any();
16+
return hasConverter;
17+
}
18+
19+
/// <inheritdoc />
20+
public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options)
21+
{
22+
Type converterType = typeof(TypeConverterJsonAdapter<>).MakeGenericType(typeToConvert);
23+
return (JsonConverter)Activator.CreateInstance(converterType);
24+
}
25+
}
26+
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.ComponentModel;
4+
using System.Globalization;
5+
using System.Text.Json;
6+
using System.Text.Json.Serialization;
7+
using System.Text.RegularExpressions;
8+
9+
using Microsoft.VisualStudio.TestTools.UnitTesting;
10+
11+
namespace Macross.Json.Extensions.Tests
12+
{
13+
[TestClass]
14+
public class TypeConverterJsonAdapterFactoryTests
15+
{
16+
[TestMethod]
17+
public void TypeConverterTest()
18+
{
19+
JsonSerializerOptions options = new JsonSerializerOptions
20+
{
21+
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
22+
};
23+
options.Converters.Add(new JsonStringEnumConverter());
24+
options.Converters.Add(new TypeConverterJsonAdapterFactory());
25+
options.IgnoreNullValues = true;
26+
TestClass testObj = new TestClass()
27+
{
28+
One = new Coordinates { X = 10, Y = "str" },
29+
Many = new List<Coordinates>
30+
{
31+
new Coordinates { X = 20, Y = "abc" }, new Coordinates { X = 30, Y = "zyx" },
32+
}
33+
};
34+
35+
string jsonExpected = "{\"one\":\"10,str\",\"many\":[\"20,abc\",\"30,zyx\"]}";
36+
string json = JsonSerializer.Serialize(testObj, options);
37+
Assert.AreEqual(jsonExpected, json);
38+
TestClass? output = JsonSerializer.Deserialize<TestClass>(json, options);
39+
Assert.IsNotNull(output);
40+
41+
Assert.AreEqual(testObj.One, output.One);
42+
CollectionAssert.AreEqual(testObj.Many, output.Many);
43+
}
44+
45+
private class TestClass
46+
{
47+
public Coordinates? One { get; set; }
48+
49+
public List<Coordinates>? Many { get; set; }
50+
}
51+
52+
[TypeConverter(typeof(CoordinatesTypeConverter))]
53+
private record Coordinates
54+
{
55+
public int X { get; set; }
56+
57+
public string? Y { get; set; }
58+
59+
public override string ToString() => $"{X},{Y}";
60+
}
61+
62+
#pragma warning disable CA1812 // Remove class never instantiated
63+
private class CoordinatesTypeConverter : TypeConverter
64+
#pragma warning restore CA1812 // Remove class never instantiated
65+
{
66+
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) =>
67+
sourceType == typeof(string) || base.CanConvertFrom(context, sourceType);
68+
69+
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
70+
{
71+
if (value is string str)
72+
{
73+
Match match = Regex.Match(str, @"(\d+),(\w+)");
74+
return new Coordinates { X = int.Parse(match.Groups[1].Value, CultureInfo.InvariantCulture), Y = match.Groups[2].Value };
75+
}
76+
77+
return base.ConvertFrom(context, culture, value);
78+
}
79+
}
80+
}
81+
}

0 commit comments

Comments
 (0)