1

我必须管理对服务的大量 API 调用,并且所有响应消息都有一个共同的结构,除了“数据”字段,它根据调用的端点以及调用是否成功而有所不同。寻找一种巧妙地管理整个情况的方法,我使用泛型和 Mapster 制作了一个令人满意的解决方案。这是一个典型的响应消息:

    {
       "type": "send",
       "datetime": "2022-02-21",
       "correlation_id": "dc659b16-0781-4e32-ae0d-fbe737ff3215",
       "data": {
        "id": 22,
        "description": "blue t-shirt with stripes",
        "category": "t-shirt",
        "size": "XL"
       }
    }

数据字段是完全可变的,有时是单级结构,有时是多级结构,有时是数组,有时是简单字符串,有时是空值。显然,响应消息是已知的,并且取决于被调用的端点,所以我知道当我拨打电话时会发生什么,但在某些情况下结构仍然可以改变。如果调用不成功并且出现错误,仍然会从端点返回 200,但响应如下:

    {
       "type": "error",
       "datetime": "2022-02-21",
       "correlation_id": "dc659b16-0781-4e32-ae0d-fbe737ff3215",
       "data": {
        "id": 1522,
        "description": "product code not found",
       }
    }

寻找一个优雅而简洁的解决方案来用一种方法管理所有案例,我找到了一个解决方案:这些是我的模型:

    public class Response<T> where T : class
    {
        public string type { get; set; }
        public T data { get; set; } = null;
        public Error Error { get; set; } = null;
        public bool IsError => Error != null;
        public string ErrorMessage => IsError ? $"An error occurred. Error code {Error.id} - {Error.description}" : "";
    }
    public class Customer
    {
        public int customerId { get; set; }
        public string name { get; set; }
    }
    public class Supplier
    {
        public int supplierId { get; set; }
        public string company { get; set; }
    }
    public class Error
    {
        public int id { get; set; }
        public string description { get; set; }
    }

这是我管理所有反序列化的函数:

    private static Response<T> GetData<T>(string json) where T : class
    {
        //Deserialize the json using dynamic as T so can receive any kind of data structure
        var resp = JsonConvert.DeserializeObject<Response<dynamic>>(json);
        var ret = resp.Adapt<Response<T>>();
    
        if (resp.type == "error")
        {
            //Adapt the dynamic to Error property
            ret.Error = ((object)resp.data).Adapt<Error>();
            ret.data = null;
        }
        return ret;
    }

所以我以这种方式调用我的函数:

    var customerData = GetData<Customer>("{\"type\":\"send\", \"data\": {\"id\":1, \"name\": \"John Ross\"}}");
    if (customerData.IsError)
        Console.WriteLine($"ERROR! {customerData.ErrorMessage}");
    else
        Console.WriteLine($"The response is OK. Customer name is {customerData.data.name}");

如您所见,所采用的解决方案很优雅并且效果很好。我没有找到解决方案的唯一问题是,如果我尝试将错误的 json 放入 T 类型,Mapster.Adapt 不会失败。因此,如果我在供应商类中反序列化客户的 json,我不会注意到问题。

Mapster 有没有办法知道我要适应的对象是否与目标类型不兼容?所以我可以提出一个例外,我的程序将是完美的。

这是一个带有工作示例的 仓库 https://github.com/mmassari/MapDynamicWIthMapster

谢谢

4

1 回答 1

0

您尝试制定的解决方案可能看起来很优雅,但实际上您可能会引入一种代码异味,这可能会在将来造成难以捕获的错误。

您可能需要重新考虑使用您已经掌握的有关端点的知识将响应反序列化为正确的响应类型。这是一种很好的做法,对于您自己和未来的开发人员来说,它都易于理解和维护。

对于有问题的“未找到产品代码”响应,您可以添加一个验证步骤,将 JSON 响应反序列化为 JObject 类型的对象,您可以安全地查询该对象以检查是否存在已知错误消息。

这是一个简单的例子:

private static void ValidateResponse(string json)
{
    var knownErrors = new List<string>()
    {
        "Product code not found",
        "Customer does not exist",
        "Other known errors here"
    };

    var jobj = JsonConvert.DeserializeObject<JObject>(json);
    var error = jobj.SelectToken("data.description");
    if (error != null)
    {
        var errorMessage = error.Value<string>();
        if (knownErrors.Contains(errorMessage))
        {
            throw new ApplicationException(errorMessage);
        }
    }
}

您可能需要考虑修剪和规范化大小写,以避免因错误消息中的微小变化而被抛弃。

于 2022-02-13T11:18:19.643 回答