You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

ASP.NET Core Web API:多供应商API异构JSON结果绑定至统一模型的实现方案问询

ASP.NET Core Web API:多供应商API异构JSON结果绑定至统一模型的实现方案问询

嗨,这个场景太典型了——做聚合类API对接多供应商时,异构JSON字段的统一处理确实是个需要注意的细节,你想统一模型给前端的思路完全是最佳实践,我给你几个实用的方案,结合你的代码来拆解:


方案一:利用.NET 7+的多别名JsonPropertyName特性(最简单)

从.NET 7开始,System.Text.Json的JsonPropertyNameAttribute支持指定多个别名,这样你的统一模型属性可以自动匹配不同供应商返回的不同字段名。

首先定义你的统一Product模型:

public class Product : IProduct
{
    // 匹配image_product、image、file这几个字段名
    [JsonPropertyName("image_product")]
    [JsonPropertyName("image")]
    [JsonPropertyName("file")]
    public string ImageUrl { get; set; } = string.Empty;

    [JsonPropertyName("product_name")]
    [JsonPropertyName("name")]
    public string ProductName { get; set; } = string.Empty;

    [JsonPropertyName("price")]
    [JsonPropertyName("pricing")]
    [JsonPropertyName("cost")]
    public decimal Price { get; set; }
}

然后更新你的接口与供应商实现,直接反序列化为统一模型:

// 先更新接口,返回统一模型而非字符串
public interface IFulfillmentService
{
    Task<IEnumerable<Product>> GetProducts();
}

public class Printful : IFulfillmentService
{
    private readonly string _printfulAPIKey;
    private readonly IHttpClientFactory _httpClientFactory;

    public Printful(IConfiguration configuration, IHttpClientFactory httpClientFactory)
    {
        _printfulAPIKey = configuration.GetValue<string>("PrintfulAPIKey") ?? "";
        _httpClientFactory = httpClientFactory;
    }

    public async Task<IEnumerable<Product>> GetProducts()
    {
        using var httpClient = _httpClientFactory.CreateClient();
        // 补充Printful要求的Basic认证头
        httpClient.DefaultRequestHeaders.Authorization = 
            new AuthenticationHeaderValue("Basic", Convert.ToBase64String(Encoding.ASCII.GetBytes($"{_printfulAPIKey}:")));

        var response = await httpClient.GetAsync("https://api.printful.com/products");
        response.EnsureSuccessStatusCode(); // 处理请求失败的情况

        // Printful的返回包裹在"data"字段中,需要中间模型适配
        var printfulResponse = await response.Content.ReadFromJsonAsync<PrintfulProductsResponse>();
        return printfulResponse?.Data.Select(p => p.Product) ?? Enumerable.Empty<Product>();
    }
}

// Printful返回结构的中间适配模型
public class PrintfulProductsResponse
{
    public List<PrintfulProductWrapper> Data { get; set; } = new();
}

public class PrintfulProductWrapper
{
    [JsonPropertyName("product")]
    public Product Product { get; set; } = new();
}

最后修改控制器,直接返回统一模型:

[HttpGet(Name = "GetProducts")]
[EnableRateLimiting("api")]
public async Task<IActionResult> Get()
{
    var products = await _printful.GetProducts();
    return Ok(products);
}

方案二:自定义JSON转换器(适合复杂映射场景)

如果某些供应商的字段逻辑更复杂(比如嵌套层级不同、需要类型转换),可以编写自定义转换器来灵活处理:

public class ProductJsonConverter : JsonConverter<Product>
{
    public override Product Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        using var doc = JsonDocument.ParseValue(ref reader);
        var root = doc.RootElement;

        var product = new Product();

        // 匹配所有可能的图片字段名
        if (root.TryGetProperty("image_product", out var img1)) product.ImageUrl = img1.GetString() ?? "";
        else if (root.TryGetProperty("image", out var img2)) product.ImageUrl = img2.GetString() ?? "";
        else if (root.TryGetProperty("file", out var img3)) product.ImageUrl = img3.GetString() ?? "";

        // 处理产品名称
        if (root.TryGetProperty("product_name", out var name1)) product.ProductName = name1.GetString() ?? "";
        else if (root.TryGetProperty("name", out var name2)) product.ProductName = name2.GetString() ?? "";

        // 处理价格,兼容不同字段与类型转换
        if (root.TryGetProperty("price", out var price1)) product.Price = price1.GetDecimal();
        else if (root.TryGetProperty("pricing", out var price2)) product.Price = price2.GetDecimal();
        else if (root.TryGetProperty("cost", out var price3)) product.Price = price3.GetDecimal();

        return product;
    }

    public override void Write(Utf8JsonWriter writer, Product value, JsonSerializerOptions options)
    {
        // 前端接收统一模型时一般无需自定义序列化逻辑,直接默认序列化即可
        JsonSerializer.Serialize(writer, value, options);
    }
}

然后在统一模型上标记使用该转换器:

[JsonConverter(typeof(ProductJsonConverter))]
public class Product : IProduct
{
    public string ImageUrl { get; set; } = string.Empty;
    public string ProductName { get; set; } = string.Empty;
    public decimal Price { get; set; }
}

方案三:手动映射(最灵活,适合数据清洗场景)

如果每个供应商的返回结构差异极大,或者你需要对数据做额外处理(比如价格换算、图片URL拼接),可以先反序列化为供应商专属模型,再手动映射到统一模型:

// Printful专属模型
public class PrintfulProduct
{
    [JsonPropertyName("image_product")]
    public string ImageUrl { get; set; } = string.Empty;

    [JsonPropertyName("product_name")]
    public string Name { get; set; } = string.Empty;

    [JsonPropertyName("price")]
    public decimal Cost { get; set; }
}

// 修改Printful类的GetProducts方法
public async Task<IEnumerable<Product>> GetProducts()
{
    // ... 前面的HTTP请求与认证代码 ...
    var printfulResponse = await response.Content.ReadFromJsonAsync<PrintfulProductsResponse>();
    
    // 手动映射到统一模型,可加入自定义业务逻辑
    return printfulResponse?.Data.Select(wrapper => new Product
    {
        ImageUrl = wrapper.PrintfulProduct.ImageUrl,
        ProductName = wrapper.PrintfulProduct.Name,
        Price = wrapper.PrintfulProduct.Cost * 1.2m // 示例:添加20%的利润
    }) ?? Enumerable.Empty<Product>();
}

额外建议

  • 记得处理HTTP请求失败的情况(比如response.EnsureSuccessStatusCode()),避免前端拿到无意义的错误数据
  • 可以把多个供应商的实现通过依赖注入注册,控制器注入IEnumerable<IFulfillmentService>后,批量获取所有供应商的产品并聚合
  • 统一模型抽象为IProduct接口,方便后续扩展更多供应商适配逻辑

备注:内容来源于stack exchange,提问作者Qiuzman

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.23 11:32:47