String kann nicht implizit in Typ mit impliziter Konvertierung umgewandelt werden

Nov 25 2020

Ich versuche, JSON mithilfe von JSON.NET mithilfe eines benutzerdefinierten Konverters in eine Datenstruktur mit den Typen aus der OneOf- Bibliothek zu serialisieren .

Ich stoße auf folgende Ausnahme:

System.InvalidCastException: 'Objekt vom Typ' System.String 'kann nicht in' System.Nullable`1 [OneOf.OneOf`2 [OneOf.OneOf`2 [PandocFilters.TagContent, System.Int64] [], System umgewandelt werden. String]] '.'

Was für mich keinen Sinn ergibt, da der folgende C # -Code kompiliert und ausgeführt wird:

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

weil die OneOf<...>Typen eine implizite Konvertierung von jedem der Subtypen in den OneOfTyp definieren (Quelle kann hier gesehen werden ).

TagContent ist wie folgt definiert:

internal record TagContent(string T, OneOf<OneOf<TagContent, long>[], string>? C);

Wie kann ich das debuggen?


Der Vollständigkeit halber füge ich hier den vollständigen Konverter hinzu. Es ist relevant, weil ich ohne den Konverter nicht reproduzieren kann.

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();
}

und die relevanten Erweiterungsmethoden hier:

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;

Antworten

ZevSpitz Nov 30 2020 at 01:52

Es stellt sich heraus, dass ich die Konvertierung tatsächlich aufrufen muss:

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

JSON.NET führt die Konvertierung nicht alleine durch.