-
Notifications
You must be signed in to change notification settings - Fork 154
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
JsonConverter of the OneOf class for serialization #118
Comments
I think the converter could be simplified to only use properties from Lines 3 to 7 in 014d68f
and it could be used for both serialization and deserialization |
I've been recently trying to write a general System.Json.Text converter for
A Write/serialization is trivial. For any public override void Write(Utf8JsonWriter writer, Uri value, JsonSerializerOptions options) {
if (value.IsT0) {
JsonSerializer.Serialize(writer, value.AsT0, options);
} else { // value.IsT1 {
JsonSerializer.Serialize(writer, value.AsT1, options);
}
} Read/deserialization is the real trouble. A This means the converter would have to read the first token, and choose an appropriate subtype based on that token or on the entire value. Some possibilities are obvious:
But what happens for And for I've written something similar for Newtonsoft.Json, which sort of worked for my needs. |
I found a solution for deserialization, see dotnet/runtime#30083 (comment) This converter factory works fine, but is not as efficient as the native one Here is the full code: public class OnOfTwoValueConverter : JsonConverterFactory
{
public override bool CanConvert(Type typeToConvert)
{
return typeToConvert.IsGenericType && typeToConvert.GetGenericTypeDefinition() == typeof(OneOf<,>);
}
public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options)
{
var _typeOfT0 = typeToConvert.GetGenericArguments()[0];
var _typeOfT1 = typeToConvert.GetGenericArguments()[1];
return (JsonConverter)Activator.CreateInstance(
typeof(OneOfJsonConverter<,>).MakeGenericType(new Type[] { _typeOfT0, _typeOfT1 }),
BindingFlags.Instance | BindingFlags.Public,
binder: null,
args: new object[] { options },
culture: null)!;
}
private class OneOfJsonConverter<T0, T1> : JsonConverter<OneOf<T0, T1>>
{
private const string INDEX_KEY = "$index";
private readonly Type _typeOfT0;
private readonly Type _typeOfT1;
public OneOfJsonConverter(JsonSerializerOptions options)
{
_typeOfT0 = typeof(T0);
_typeOfT1 = typeof(T1);
}
public override OneOf<T0, T1> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
using var doc = JsonDocument.ParseValue(ref reader);
if (!doc.RootElement.TryGetProperty(INDEX_KEY, out var indexElement)
|| !indexElement.TryGetInt32(out var index)
|| index < 0
|| index > 1)
{
throw new JsonException("Cannot not find type index or type index is not a valid number.");
}
if (index == 0)
{
return doc.Deserialize<T0>(options);
}
else
{
return doc.Deserialize<T1>(options);
}
}
public override void Write(Utf8JsonWriter writer, OneOf<T0, T1> value, JsonSerializerOptions options)
{
writer.WriteStartObject();
writer.WritePropertyName(INDEX_KEY);
writer.WriteNumberValue(value.Index);
using var doc = value.Match(
t0 => JsonSerializer.SerializeToDocument(t0, _typeOfT0, options),
t1 => JsonSerializer.SerializeToDocument(t1, _typeOfT1, options));
foreach (var prop in doc.RootElement.EnumerateObject())
{
prop.WriteTo(writer);
}
writer.WriteEndObject();
}
}
} |
@ling921 There are a number of issues. Firstly, the But more importantly, your converter works only for |
@zspitz Simply write a source generator like this https://github.com/mcintyre321/OneOf/blob/master/Generator/Program.cs In the In the above example, I use |
Might be interesting to you how the F# people did it: https://github.com/Tarmil/FSharp.SystemTextJson/blob/master/docs/Customizing.md#unwrap-union-cases-with-a-record-field |
Here's a variant for System.Text.Json that (only!) supports using System.Reflection;
using System.Text.Json;
using System.Text.Json.Serialization;
using OneOf;
namespace Infrastructure.Serialization;
public class OneOfConverterFactory : JsonConverterFactory
{
public override bool CanConvert(Type typeToConvert)
=> typeof(IOneOf).IsAssignableFrom(typeToConvert);
public override JsonConverter CreateConverter(Type? typeToConvert, JsonSerializerOptions options)
{
var (oneOfGenericType, converterType) = GetTypes(typeToConvert);
if (oneOfGenericType is null || converterType is null)
{
throw new NotSupportedException($"Cannot convert {typeToConvert}");
}
var jsonConverter = (JsonConverter) Activator.CreateInstance(
converterType.MakeGenericType(oneOfGenericType.GenericTypeArguments),
BindingFlags.Instance | BindingFlags.Public,
null,
new object[] { options },
null)!;
return jsonConverter;
}
static (Type? oneOfGenericType, Type? converterType) GetTypes(Type? type)
{
while (type is not null)
{
if (type.IsGenericType)
{
var genericTypeDefinition = type.GetGenericTypeDefinition();
if (genericTypeDefinition == typeof(OneOfBase<,>) ||
genericTypeDefinition == typeof(OneOf<,>))
{
return (type, typeof(OneOf2JsonConverter<,>));
}
if (genericTypeDefinition == typeof(OneOfBase<,,>) ||
genericTypeDefinition == typeof(OneOf<,,>))
{
return (type, typeof(OneOf3JsonConverter<,,>));
}
// TODO: Not supported (yet).
// if (genericTypeDefinition == typeof(OneOfBase<,,,>) ||
// genericTypeDefinition == typeof(OneOf<,,,>))
// {
// return (type, typeof(OneOfJson<,,,>));
// }
//
// if (genericTypeDefinition == typeof(OneOfBase<,,,,>) ||
// genericTypeDefinition == typeof(OneOf<,,,,>))
// {
// return (type, typeof(OneOfJson<,,,,>));
// }
//
// if (genericTypeDefinition == typeof(OneOfBase<,,,,,>) ||
// genericTypeDefinition == typeof(OneOf<,,,,,>))
// {
// return (type, typeof(OneOfJson<,,,,,>));
// }
//
// if (genericTypeDefinition == typeof(OneOfBase<,,,,,,>) ||
// genericTypeDefinition == typeof(OneOf<,,,,,,>))
// {
// return (type, typeof(OneOfJson<,,,,,,>));
// }
//
// if (genericTypeDefinition == typeof(OneOfBase<,,,,,,,>) ||
// genericTypeDefinition == typeof(OneOf<,,,,,,,>))
// {
// return (type, typeof(OneOfJson<,,,,,,,>));
// }
//
// if (genericTypeDefinition == typeof(OneOfBase<,,,,,,,,>) ||
// genericTypeDefinition == typeof(OneOf<,,,,,,,,>))
// {
// return (type, typeof(OneOfJson<,,,,,,,,>));
// }
}
type = type.BaseType;
}
return (null, null);
}
static IOneOf CreateOneOf(JsonSerializerOptions options,
int index,
JsonDocument doc,
Type oneOfType,
Type[] types)
{
var args = new object[types.Length + 1];
args[0] = index;
args[index + 1] = doc.Deserialize(types[index], options);
var oneOf = Activator.CreateInstance(
oneOfType,
BindingFlags.Instance | BindingFlags.NonPublic,
null,
args,
null
);
return (IOneOf) oneOf;
}
const string IndexKey = "$index";
class OneOf2JsonConverter<T0, T1> : JsonConverter<OneOfBase<T0, T1>>
{
static readonly Type OneOfType = typeof(OneOf<,>).MakeGenericType(typeof(T0), typeof(T1));
static readonly Type[] Types = { typeof(T0), typeof(T1) };
public OneOf2JsonConverter(JsonSerializerOptions _)
{
}
public override OneOfBase<T0, T1> Read(ref Utf8JsonReader reader,
Type typeToConvert,
JsonSerializerOptions options)
{
using var doc = JsonDocument.ParseValue(ref reader);
if (!doc.RootElement.TryGetProperty(IndexKey, out var indexElement) ||
!indexElement.TryGetInt32(out var index) ||
index is < 0 or > 1)
{
throw new JsonException("Cannot not find type index or type index is not a valid number");
}
var oneOf = CreateOneOf(options, index, doc, OneOfType, Types);
return (OneOfBase<T0, T1>) Activator.CreateInstance(typeToConvert, oneOf);
}
public override void Write(Utf8JsonWriter writer,
OneOfBase<T0, T1> value,
JsonSerializerOptions options)
{
writer.WriteStartObject();
writer.WritePropertyName(IndexKey);
writer.WriteNumberValue(value.Index);
using var doc = value.Match(
t0 => JsonSerializer.SerializeToDocument(t0, typeof(T0), options),
t1 => JsonSerializer.SerializeToDocument(t1, typeof(T1), options)
);
foreach (var prop in doc.RootElement.EnumerateObject())
{
prop.WriteTo(writer);
}
writer.WriteEndObject();
}
}
class OneOf3JsonConverter<T0, T1, T2> : JsonConverter<OneOfBase<T0, T1, T2>>
{
static readonly Type OneOfType = typeof(OneOf<,,>).MakeGenericType(typeof(T0), typeof(T1), typeof(T2));
static readonly Type[] Types = { typeof(T0), typeof(T1), typeof(T2) };
public OneOf3JsonConverter(JsonSerializerOptions _)
{
}
public override OneOfBase<T0, T1, T2> Read(ref Utf8JsonReader reader,
Type typeToConvert,
JsonSerializerOptions options)
{
using var doc = JsonDocument.ParseValue(ref reader);
if (!doc.RootElement.TryGetProperty(IndexKey, out var indexElement) ||
!indexElement.TryGetInt32(out var index) ||
index is < 0 or > 2)
{
throw new JsonException("Cannot not find type index or type index is not a valid number");
}
var oneOfBase = CreateOneOf(options, index, doc, OneOfType, Types);
return (OneOfBase<T0, T1, T2>) Activator.CreateInstance(typeToConvert, oneOfBase);
}
public override void Write(Utf8JsonWriter writer,
OneOfBase<T0, T1, T2> value,
JsonSerializerOptions options)
{
writer.WriteStartObject();
writer.WritePropertyName(IndexKey);
writer.WriteNumberValue(value.Index);
using var doc = value.Match(
t0 => JsonSerializer.SerializeToDocument(t0, typeof(T0), options),
t1 => JsonSerializer.SerializeToDocument(t1, typeof(T1), options),
t2 => JsonSerializer.SerializeToDocument(t2, typeof(T2), options)
);
foreach (var prop in doc.RootElement.EnumerateObject())
{
prop.WriteTo(writer);
}
writer.WriteEndObject();
}
}
} |
Example of OneOf<T0, T1>, here is the converter class
Then use it on OneOf class
In the WebApi project, we can use like this
The text was updated successfully, but these errors were encountered: