【问题标题】:Unable to cast string to type with implicit conversion无法使用隐式转换将字符串转换为类型
【发布时间】:2021-03-08 21:47:00
【问题描述】:

我正在尝试使用 JSON.NET 和自定义转换器将 JSON 序列化为具有来自 OneOf 库的类型的数据结构。

我遇到以下异常:

System.InvalidCastException: '无法将'System.String'类型的对象转换为'System.Nullable`1[OneOf.OneOf`2[OneOf.OneOf`2[PandocFilters.TagContent ,System.Int64][],System.String]]'.'

这对我来说没有意义,因为以下 C# 代码编译并运行:

OneOf<OneOf<TagContent, long>[], string>? c = "abcd";

因为OneOf&lt;...&gt; 类型定义了从每个子类型到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;

【问题讨论】:

  • @John 您不需要 OneOf 库中的代码;添加package reference。但无论如何我都会使用库中的代码进行更新。
  • 看了源码后,我可以看到这个库是如何工作的,eeek。什么可怕的 json 问题迫使你使用这个?
  • @TheGeneral 我正在尝试用 C# 编写 Pandoc filter。 Pandoc 通过名为 aeson 的 JSON 生成 Haskell 包传入数据。这只是我创建要在过滤器中使用的实际 .NET 类型之前的中间步骤;跳过它并使用完全动态的对象可能更明智。

标签: c# json.net discriminated-union


【解决方案1】:

事实证明我必须实际调用转换:

return conversion.Invoke(null, new [] {token.ToObject(valueType, serializer)});

JSON.NET 不会自行执行转换。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-09-09
    • 2014-05-15
    • 2014-02-04
    • 2011-05-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多