-
Notifications
You must be signed in to change notification settings - Fork 5.3k
Description
Thanks for all your efforts.
I believe that there is a strong case for an additional converter that will act as an Adapter between System.ComponentModel.TypeConverter marked on existing types with an attribute and the new serializer via the System.Text.Json.Serialization.JsonConverter . This could be either in the box (optionally) or in the docs as an example.
As I am migrating a number of aspnetcore projects to use the built in Json serializer I have noticed that there are numerous times I rely on serialization to do the right thing regarding models that are already decorated with a TypeConverterAttribute that is there to take care of automatic convesions to and from strings. With newtonsoft JsonConvert and aspnetcore it used to discover and use them under the hood by default whenever needed to convert to and from json. Lets say for example I have a GeoPoint class that is decorated with a TypeConverterAttribute (ComponentModel) instead of writing a new JsonConverter for that it would be nice to have something like the following.
The adapter
/// <summary>
/// Adapter between <see cref="System.ComponentModel.TypeConverter"/>
/// and <see cref="JsonConverter"/>
/// </summary>
public class TypeConverterJsonAdapter : JsonConverter<object>
{
public override object Read(
ref Utf8JsonReader reader,
Type typeToConvert,
JsonSerializerOptions options) {
var converter = TypeDescriptor.GetConverter(typeToConvert);
var text = reader.GetString();
return converter.ConvertFromString(text);
}
public override void Write(
Utf8JsonWriter writer,
object objectToWrite,
JsonSerializerOptions options) {
var converter = TypeDescriptor.GetConverter(objectToWrite);
var text = converter.ConvertToString(objectToWrite);
writer.WriteStringValue(text);
}
public override bool CanConvert(Type typeToConvert) {
var hasConverter = typeToConvert.GetCustomAttributes<TypeConverterAttribute>(inherit: true).Any();
return hasConverter;
}
}The model
/// <summary>
/// Geographic location
/// </summary>
[TypeConverter(typeof(GeoPointTypeConverter))]
public class GeoPoint
{
/// <summary>
/// The latitude
/// </summary>
public double Latitude { get; set; }
/// <summary>
/// The logitude
/// </summary>
public double Longitude { get; set; }
}
/// <summary>
/// Type converter for converting between <see cref="GeoPoint"/> and <seealso cref="string"/>
/// </summary>
public class GeoPointTypeConverter : TypeConverter
{
// ..... excluded for brevity ...
}the test
[Fact]
public void RoundtripTypeConverterAdapter() {
var options = new JsonSerializerOptions();
options.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
options.Converters.Add(new JsonStringEnumConverter());
options.Converters.Add(new TypeConverterJsonAdapter());
options.IgnoreNullValues = true;
var model = new TestModel { Point = GeoPoint.Parse("37.9888529,23.7037796") };
var jsonExpected = "{\"point\":\"37.9888529,23.7037796\"}";
var json = JsonSerializer.Serialize(model, options);
Assert.Equal(jsonExpected, json);
var output = JsonSerializer.Deserialize<TestModel>(json, options);
Assert.Equal(model.Point.Latitude, output.Point.Latitude);
}
class TestModel
{
public GeoPoint Point { get; set; }
}