首页 > 解决方案 > 具有多态性的自定义 JSON 反序列化

问题描述

我想要一个通用的提交数据合约,我可以使用它向 API 端点发送大量不同的请求,使用多态性来定义每个特定请求的合约。

我有一个如下所示的对象层次结构

    ProductOrderSubmission
         -> IOrder Order
         -> string OrderId
         -> string User

    IOrder      
         -> IProduct[] Items

    EmailOrderSubmission : IOrder    
         -> IProduct[] Items    
         -> string ClientName    
         -> string FromEmail    
         -> IProduct ReferenceItem

    PreOrderSubmission : IOrder    
         -> IProduct[] Items    
         -> int AccountNumber    
         -> DateTime FulfillmentDate

    IProduct
         -> decimal Price
         -> int Quantity

    ListedProduct : IProduct
         -> decimal Price
         -> int Quantity
         -> int CatalogId
         -> int IdType

    PromoProduct : IProduct
         -> decimal Price
         -> int Quantity
         -> DateTime StartDate
         -> DateTime EndDate

使用具有两种方法的 API,EmailOrder并且PreOrder

它们的结构如下所示(请注意,我在模型活页夹中使用“路线”来确定这是哪种顶级订单类型,例如,它是电子邮件订单还是预购订单,您可以在模型活页夹代码中看到这一点)

[HttpPost, Route("email"), System.Web.Http.Description.ResponseType(typeof(OrderSubmissionResult))]
public async Task<IHttpActionResult> EmailOrderAsync([ModelBinder(typeof(ProductOrderSubmissionModelBinder))] ProductOrderSubmission orderSubmission)
{
    \\ action code here
}

[HttpPost, Route("preorder"), System.Web.Http.Description.ResponseType(typeof(OrderSubmissionResult))]
public async Task<IHttpActionResult> PreOrderAsync([ModelBinder(typeof(ProductOrderSubmissionModelBinder))] ProductOrderSubmission orderSubmission)
{
    \\ action code here
}

所以想法是有一个通用的 ProductOrderSubmission 对象,其中包含所有不同类型请求的有效负载。根据运行时的实际类型和数据,将执行不同的代码路径。

我遇到的问题是能够反序列化具有多态性的有效负载。我希望能够在 ProductOrderSubmission 上调用反序列化并对其进行配置,以便反序列化器知道如何确定 IOrder 是 EmailOrderSubmission 还是 PreOrderSubmission 并反序列化为正确的类型,同样对于每个项目,我希望它能够找出反序列化为 ListedProduct 或 PromoProduct。

我知道我需要一个自定义的反序列化器,但我无法弄清楚如何准确地构造它。我将在下面粘贴代码

模型粘合剂

public class ProductOrderSubmissionModelBinder : IModelBinder
    {
        public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
        {
            if (bindingContext.ModelType != typeof(ProductOrderSubmission))
                return false;

            var content = actionContext.Request.Content.ReadAsStringAsync().Result;
            var action = actionContext.Request.RequestUri.Segments.Last();

            try
            {
                var submission = JsonConvert.DeserializeObject<ProductOrderSubmission>
                (
                    content, 
                    new EmailOrderSubmissionConverter(action)
                );

                bindingContext.Model = submission;

                return true;
            }
            catch (ValidationException ex)
            {
                bindingContext.ModelState.AddModelError(bindingContext.ModelName, ex.Message);

                return false;
            }
        }
    }

EmailOrderSubmissionConverter

 public class EmailOrderSubmissionConverter : JsonConverter
    {
        private readonly string _action;
        public EmailOrderSubmissionConverter(string action)
        {
            _action = action;
        }

        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            throw new NotImplementedException();
        }

        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            var emailSubmission =
                JsonConvert.DeserializeObject<EmailOrderSubmission>(
                    reader.ReadAsString(),
                    new ListedProductConverter(),
                    new PromoProductConverter()
                );

            return emailSubmission;
        }

        public override bool CanConvert(Type objectType)
        {
            return _action == "email";
        }
    }

我还没有实现 ListedProductConverter 和 PromoProductConverter,因为我开始觉得我的方法不正确,因为我不确定如何继续

标签: c#jsonserialization

解决方案


推荐阅读