如何使用JsonSerializer反序列化异构JSON数组?

How to use JsonSerializer to deserialize a heterogenous JSON Array?

考虑以下 class:

class MethodCallDescription
{
    public string MethodName { get; set; }
    public object[] MethodParameters { get; set; }
}

我想将以下 JSON 反序列化为 class 的一个实例:

{
    "MethodName": "LaunchRockets",
    "MethodParameters": [ "Long Range", 100, true ]
}

似乎当我这样做时,MethodParameters 变成了一个包含类型 JsonElement 而不是字符串、整数和布尔对象的数组。

如何告诉 JsonSerializer 将 MethodParameters 转换为包含预期类型值的数组?

我在 System.Text.Json 源代码中遇到了 this test,它使用自定义 JsonConverter 来模仿对象反序列化的 Newtonsoft.Json 行为(它确实保持了原始对象类型相反)。

这是可用于实现此目的的自定义转换器的精简版:

class SystemObjectNewtonsoftCompatibleConverter : JsonConverter<object>
{
    public override object Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        return reader.TokenType switch
        {
            JsonTokenType.True => true,
            JsonTokenType.False => false,
            JsonTokenType.Number => reader.GetInt32(),
            JsonTokenType.String => reader.GetString(),
            _ => Fallback(ref reader)
        };

        object Fallback(ref Utf8JsonReader reader)
        {
            // Use JsonElement as fallback.
            // Newtonsoft uses JArray or JObject.
            using JsonDocument document = JsonDocument.ParseValue(ref reader);
            return document.RootElement.Clone();
        }
    }

    public override void Write(Utf8JsonWriter writer, object value, JsonSerializerOptions options) => 
        throw new InvalidOperationException("Should not get here.");
}

这个使用该转换器的示例显示了被保留的对象类型:

const string json = @"{
    ""MethodName"": ""LaunchRockets"",
    ""MethodParameters"": [ ""Long Range"", 100, true ]
}";

var options = new JsonSerializerOptions();
options.Converters.Add(new SystemObjectNewtonsoftCompatibleConverter());
MethodCallDescription instance = JsonSerializer.Deserialize<MethodCallDescription>(json, options);
Console.WriteLine(instance.MethodName);
Console.WriteLine(string.Join(", ", instance.MethodParameters.Select(p => (p, p.GetType().Name))));

提供输出:

LaunchRockets
(Long Range, String), (100, Int32), (True, Boolean)