使用 JsonConvert.DeserializeObject 动态选择 class

Using JsonConvert.DeserializeObject to dynamically choose class

所以我正在进行 api 调用,我需要使用 JsonConvert.DeserializeObject 将其转换为 class。 Json结构返回如下

{
    "fcResponse": {
        "responseData": {
            "fcRequest": {
                "mail": "Emails",
                "outlookMail": "Outlook Emails",
                 (etc.)
            }
        }
    }
}

问题是“fcRequest”中返回的值因我发送的参数而异。

目前class结构如下

    public class GetSubModulesResponse : BaseResponse
    {
        [JsonProperty("fcResponse")]
        public SubModuleResponse Response { get; set; }
    }

    public class SubModuleResponse
    {
        [JsonProperty("responseData")]
        public SubModuleData Data { get; set; }
    }

    public class SubModuleData
    {
        [JsonProperty("fcRequest")]
        public SubModuleFIMRequest RequestFIM { get; set; }

        [JsonProperty("fcRequest")]
        public SubModuleFSRequest RequestFS { get; set; }
    }

这是基本的调用结构

GetSubModulesResponse subModuleResponse = new GetSubModulesResponse();
var response = SubmitAPICall();
subModuleResponse = JsonConvert.DeserializeObject<GetSubModulesResponse>(response);

现在我知道我显然不能在 RequestFIM 和 RequestFS 上使用相同的 Json属性,但是我想做的是以某种方式找到一种方法来切换其中一个我应该根据变量使用这两个属性。

如果您可以控制要返回的 Json,我强烈建议将每个对象返回到它自己的 属性 下,并将不需要的设置为 null。

如果这不是一个选项,另一件可行的事情是将“fcRequest”反序列化为动态,然后尝试转换为它可能的类型或基于另一个 属性 名称进行转换。那不是很干净。

不同 Json 库 (Jil) 中的另一种有趣方法是联合。 "Jil has limited support for "unions" (fields on JSON objects that may contain one of several types), provided that they can be distiguished by their first character."

一种选择是使用元素的自定义(反)序列化程序。这样,您仍然可以在大多数地方从自动反序列化中获益最少,并在需要的地方获得灵活性。我假设您使用的是 Newtonsoft JSON / JSON.NET.

让我们先为 fcRequest 元素介绍一个基础 class。

public enum ResponseType
{
    FIM, FS
}

public abstract class ResponseBase
{
    [JsonIgnore]
    public abstract ResponseType ResponseType { get; }
}

在这里加一个ResponseType可以简化消费代码;如果你可以使用基于类型的模式匹配,你甚至可能不需要它。

我显然不知道您的域实体是什么,但是为了论证,SubModuleFIMRequest 现在将包含邮件地址。此外,它还派生于所说的ResponseBase:

public class SubModuleFIMRequest : ResponseBase
{
    public override ResponseType ResponseType => ResponseType.FIM;

    [JsonProperty("mail")]
    public string Mail { get; set; }

    [JsonProperty("outlookMail")]
    public string OutlookMail { get; set; }
}

接下来,您将实施 JsonConverter<ResponseBase>;为了方便起见,它可以先将 responseData 内容反序列化为 JObject。这样做时,您将能够反省元素的属性,这反过来(希望)允许您提出一种启发式方法来确定元素的实际类型。

一旦知道类型,就可以将 JObject 转换为具体实例。这是一个例子:

public class ResponseDataConverter : JsonConverter<ResponseBase>
{
    /// <inheritdoc />
    public override bool CanWrite => false;

    /// <inheritdoc />
    public override ResponseBase ReadJson(JsonReader reader, Type objectType, ResponseBase existingValue, bool hasExistingValue, JsonSerializer serializer)
    {
        var jObject = serializer.Deserialize<JObject>(reader);

        // Now, decide tye type by matching patterns.
        if (jObject.TryGetValue("mail", out var mailToken))
        {
            return jObject.ToObject<SubModuleFIMRequest>();
        }

        // TODO: Add more types as needed

        // If nothing matches, you may choose to throw an exception,
        // return a catchall type (e.g. wrapping the JObject), or just
        // return a default value as a last resort.
        throw new JsonSerializationException();
    }

    /// <inheritdoc />
    public override void WriteJson(JsonWriter writer, ResponseBase value, JsonSerializer serializer) => throw new NotImplementedException();
}

请注意,序列化程序不需要写入,所以我们只是投入 WriteJson

剩下的就是用指向转换器类型的 JsonConverter 属性注释 SubModuleDatafcProperty 属性:

public class SubModuleData
{
    [JsonProperty("fcRequest")]
    [JsonConverter(typeof(ResponseDataConverter))]
    public ResponseBase FcRequest { get; set; }
}

我希望这能让你开始。正如其他评论和答案中提到的:如果您可以影响 API 首先返回 JSON,请尝试更改它。