无法使用隐式转换将字符串转换为类型
Unable to cast string to type with implicit conversion
我正在尝试将 JSON 序列化为具有 OneOf 库类型的数据结构,使用 JSON.NET,使用自定义转换器。
我遇到以下异常:
System.InvalidCastException: 'Unable to cast object of type 'System.String' to type 'System.Nullable`1[OneOf.OneOf`2[OneOf.OneOf`2[PandocFilters.TagContent,System.Int64][],System.String]]'.'
这对我来说没有意义,因为以下 C# 代码编译并运行:
OneOf<OneOf<TagContent, long>[], string>? c = "abcd";
因为 OneOf<...>
类型定义了从每个子类型到 OneOf
类型的隐式转换(来源可以参见 here)。
TagContent定义如下:
internal record TagContent(string T, OneOf<OneOf<TagContent, long>[], string>? C);
我该如何调试它?
为了完整起见,我在此处包含了完整的转换器。这很重要,因为没有转换器我似乎无法重现。
public class OneOfJsonConverter : JsonConverter {
public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer) {
if (value is IOneOf of) {
value = of.Value;
}
serializer.Serialize(writer, value);
}
public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer) {
var dict = new Dictionary<JTokenType, Type>();
if (objectType.IsNullable()) { dict[JTokenType.Null] = objectType; }
var subtypes = objectType.OneOfTypes();
foreach (var t in subtypes) {
// TODO handle NRT -- if the type is defined as a non-nullable reference type, prefer something else
if (!dict.ContainsKey(JTokenType.Null) && t.IsNullable(true)) {
dict[JTokenType.Null] = t;
}
var u = t.UnderlyingIfNullable();
JTokenType tokenType =
t.IsArray ? JTokenType.Array :
t == typeof(string) ? JTokenType.String :
u == typeof(bool) ? JTokenType.Boolean :
u == typeof(DateTime) ? JTokenType.Date :
u == typeof(TimeSpan) ? JTokenType.TimeSpan :
u.IsIntegral() ? JTokenType.Integer :
u.IsNumeric() ? JTokenType.Float :
t == typeof(Uri) ? JTokenType.Uri :
JTokenType.Object;
if (!dict.ContainsKey(tokenType)) {
dict[tokenType] = t;
}
}
var token = JToken.ReadFrom(reader);
if (token.Type == JTokenType.Null && !dict.ContainsKey(JTokenType.Null)) {
throw new InvalidOperationException($"Unable to find null-accepting subtype in '{objectType}");
}
var valueType = dict[token.Type];
var conversion = objectType.UnderlyingIfNullable().GetMethod("op_Implicit", new[] { dict[token.Type] });
if (conversion is null) {
throw new InvalidOperationException($"Unable to find implicit conversion for token of type `{token.Type}` from '{valueType}' to '{objectType}");
}
return token.ToObject(valueType, serializer);
}
public override bool CanConvert(Type objectType) => objectType.OneOfTypes().Any();
}
和相关的扩展方法在这里:
internal static Type UnderlyingIfNullable(this Type t) => Nullable.GetUnderlyingType(t) ?? t;
private static readonly Type[] OneOfDefinitions = new[] {
typeof(OneOf<>),
typeof(OneOf<,>),
typeof(OneOf<,,>),
typeof(OneOf<,,,>),
typeof(OneOf<,,,,>),
typeof(OneOf<,,,,,>),
typeof(OneOf<,,,,,,>),
typeof(OneOf<,,,,,,,>),
typeof(OneOf<,,,,,,,,>),
typeof(OneOfBase<>),
typeof(OneOfBase<,>),
typeof(OneOfBase<,,>),
typeof(OneOfBase<,,,>),
typeof(OneOfBase<,,,,>),
typeof(OneOfBase<,,,,,>),
typeof(OneOfBase<,,,,,,>),
typeof(OneOfBase<,,,,,,,>),
typeof(OneOfBase<,,,,,,,,>)
};
internal static Type[] OneOfTypes(this Type t) {
t = t.UnderlyingIfNullable();
var current = t;
while (current is { }) {
if (current.IsGenericType) {
var def = current.GetGenericTypeDefinition();
if (def.In(OneOfDefinitions)) {
return current.GetGenericArguments();
}
}
current = current.BaseType;
}
return Array.Empty<Type>();
}
// TODO return false for non-nullable reference type in a nullable-enabled context
internal static bool IsNullable(this Type t, bool orReferenceType = false) {
if (orReferenceType && !t.IsValueType) { return true; }
return t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Nullable<>);
}
private static readonly Dictionary<Type, bool> numericTypes = new Dictionary<Type, bool> {
[typeof(byte)] = true,
[typeof(short)] = true,
[typeof(int)] = true,
[typeof(long)] = true,
[typeof(sbyte)] = true,
[typeof(ushort)] = true,
[typeof(uint)] = true,
[typeof(ulong)] = true,
[typeof(BigInteger)] = true,
[typeof(float)] = false,
[typeof(double)] = false,
[typeof(decimal)] = false
};
internal static bool IsNumeric(this Type type) => numericTypes.ContainsKey(type);
internal static bool IsIntegral(this Type type) => numericTypes.TryGetValue(type, out var isIntegeral) && isIntegeral;
事实证明我必须实际调用转换:
return conversion.Invoke(null, new [] {token.ToObject(valueType, serializer)});
JSON.NET 不会自行执行转换。
我正在尝试将 JSON 序列化为具有 OneOf 库类型的数据结构,使用 JSON.NET,使用自定义转换器。
我遇到以下异常:
System.InvalidCastException: 'Unable to cast object of type 'System.String' to type 'System.Nullable`1[OneOf.OneOf`2[OneOf.OneOf`2[PandocFilters.TagContent,System.Int64][],System.String]]'.'
这对我来说没有意义,因为以下 C# 代码编译并运行:
OneOf<OneOf<TagContent, long>[], string>? c = "abcd";
因为 OneOf<...>
类型定义了从每个子类型到 OneOf
类型的隐式转换(来源可以参见 here)。
TagContent定义如下:
internal record TagContent(string T, OneOf<OneOf<TagContent, long>[], string>? C);
我该如何调试它?
为了完整起见,我在此处包含了完整的转换器。这很重要,因为没有转换器我似乎无法重现。
public class OneOfJsonConverter : JsonConverter {
public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer) {
if (value is IOneOf of) {
value = of.Value;
}
serializer.Serialize(writer, value);
}
public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer) {
var dict = new Dictionary<JTokenType, Type>();
if (objectType.IsNullable()) { dict[JTokenType.Null] = objectType; }
var subtypes = objectType.OneOfTypes();
foreach (var t in subtypes) {
// TODO handle NRT -- if the type is defined as a non-nullable reference type, prefer something else
if (!dict.ContainsKey(JTokenType.Null) && t.IsNullable(true)) {
dict[JTokenType.Null] = t;
}
var u = t.UnderlyingIfNullable();
JTokenType tokenType =
t.IsArray ? JTokenType.Array :
t == typeof(string) ? JTokenType.String :
u == typeof(bool) ? JTokenType.Boolean :
u == typeof(DateTime) ? JTokenType.Date :
u == typeof(TimeSpan) ? JTokenType.TimeSpan :
u.IsIntegral() ? JTokenType.Integer :
u.IsNumeric() ? JTokenType.Float :
t == typeof(Uri) ? JTokenType.Uri :
JTokenType.Object;
if (!dict.ContainsKey(tokenType)) {
dict[tokenType] = t;
}
}
var token = JToken.ReadFrom(reader);
if (token.Type == JTokenType.Null && !dict.ContainsKey(JTokenType.Null)) {
throw new InvalidOperationException($"Unable to find null-accepting subtype in '{objectType}");
}
var valueType = dict[token.Type];
var conversion = objectType.UnderlyingIfNullable().GetMethod("op_Implicit", new[] { dict[token.Type] });
if (conversion is null) {
throw new InvalidOperationException($"Unable to find implicit conversion for token of type `{token.Type}` from '{valueType}' to '{objectType}");
}
return token.ToObject(valueType, serializer);
}
public override bool CanConvert(Type objectType) => objectType.OneOfTypes().Any();
}
和相关的扩展方法在这里:
internal static Type UnderlyingIfNullable(this Type t) => Nullable.GetUnderlyingType(t) ?? t;
private static readonly Type[] OneOfDefinitions = new[] {
typeof(OneOf<>),
typeof(OneOf<,>),
typeof(OneOf<,,>),
typeof(OneOf<,,,>),
typeof(OneOf<,,,,>),
typeof(OneOf<,,,,,>),
typeof(OneOf<,,,,,,>),
typeof(OneOf<,,,,,,,>),
typeof(OneOf<,,,,,,,,>),
typeof(OneOfBase<>),
typeof(OneOfBase<,>),
typeof(OneOfBase<,,>),
typeof(OneOfBase<,,,>),
typeof(OneOfBase<,,,,>),
typeof(OneOfBase<,,,,,>),
typeof(OneOfBase<,,,,,,>),
typeof(OneOfBase<,,,,,,,>),
typeof(OneOfBase<,,,,,,,,>)
};
internal static Type[] OneOfTypes(this Type t) {
t = t.UnderlyingIfNullable();
var current = t;
while (current is { }) {
if (current.IsGenericType) {
var def = current.GetGenericTypeDefinition();
if (def.In(OneOfDefinitions)) {
return current.GetGenericArguments();
}
}
current = current.BaseType;
}
return Array.Empty<Type>();
}
// TODO return false for non-nullable reference type in a nullable-enabled context
internal static bool IsNullable(this Type t, bool orReferenceType = false) {
if (orReferenceType && !t.IsValueType) { return true; }
return t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Nullable<>);
}
private static readonly Dictionary<Type, bool> numericTypes = new Dictionary<Type, bool> {
[typeof(byte)] = true,
[typeof(short)] = true,
[typeof(int)] = true,
[typeof(long)] = true,
[typeof(sbyte)] = true,
[typeof(ushort)] = true,
[typeof(uint)] = true,
[typeof(ulong)] = true,
[typeof(BigInteger)] = true,
[typeof(float)] = false,
[typeof(double)] = false,
[typeof(decimal)] = false
};
internal static bool IsNumeric(this Type type) => numericTypes.ContainsKey(type);
internal static bool IsIntegral(this Type type) => numericTypes.TryGetValue(type, out var isIntegeral) && isIntegeral;
事实证明我必须实际调用转换:
return conversion.Invoke(null, new [] {token.ToObject(valueType, serializer)});
JSON.NET 不会自行执行转换。