使用 FromBody 在 WebAPI 中建模的 JSON 对象数组

Posted

技术标签:

【中文标题】使用 FromBody 在 WebAPI 中建模的 JSON 对象数组【英文标题】:JSON Array of Objects to Model in WebAPI using FromBody 【发布时间】:2015-01-19 22:12:12 【问题描述】:

我正在创建一个 Web Api 方法,它应该通过 XML 或 JSON 接受对象列表并将它们添加到数据库中。

这是我目前拥有的一个非常基本的版本:

[HttpPost]
public HttpResponseMessage Put([FromBody]ProductAdd productAdd)

    //do stuff with productadd object
    return Request.CreateResponse(HttpStatusCode.OK);

它接受的对象列表的模型结构如下:

public class ProductAdd

    public List<ProductInformation> Products  get; set; 


public class ProductInformation

    public string ProductName  get; set; 

当我使用 XML - (Content-Type: application/xml)

<?xml version="1.0" encoding="utf-8"?>
<ProductAdd>
    <Products>  
        <ProductInformation>
            <ProductName>Seahorse Necklace</ProductName>
        </ProductInformation>
    </Products>
    <Products>  
        <ProductInformation>
            <ProductName>Ping Pong Necklace</ProductName>
        </ProductInformation>
    </Products>
</ProductAdd>

但是当我尝试使用 JSON(内容类型:application/json)提供相同的东西时,产品列表为空


  "ProductAdd": 
    "Products": [
      
        "ProductInformation":  "ProductName": "Seahorse Necklace" 
      ,
      
        "ProductInformation":  "ProductName": "Ping Pong Necklace" 
      
    ]
  

当另一个对象中有一个对象数组时,JSON 序列化程序是否存在问题?

有什么办法可以解决这个问题吗?

谢谢

编辑: 您为 XML 和 Json 使用了哪些序列化程序? XML:XmlSerializer JSON:牛顿软件

【问题讨论】:

【参考方案1】:

您发送到 Web API 方法的 JSON 与您反序列化到的结构不匹配。与 XML 不同,JSON 中的根对象没有名称。您需要从 JSON 中删除包装器对象才能使其工作:

  
    "Products": [
      
        "ProductInformation":  "ProductName": "Seahorse Necklace" 
      ,
      
        "ProductInformation":  "ProductName": "Ping Pong Necklace" 
      
    ]
  

或者,您可以更改您的类结构以添加一个包装类,但是您还需要更改您的 XML 以匹配它。

public class RootObject

    public ProductAdd ProductAdd  get; set; 

【讨论】:

【参考方案2】:

在反序列化莫名其妙地失败的情况下,我发现序列化测试对象并将实际输出与所需输入进行比较很有帮助。如果输出与期望的输入不同,这可能是错误的原因。在您的情况下,如果我反序列化 XML 并将其重新序列化为 JSON,我会得到:


  "Products": [
    
      "ProductName": "Seahorse Necklace"
    ,
    
      "ProductName": "Ping Pong Necklace"
    
  ]

如您所见,与重新序列化的 XML 相比,您的 JSON 中有两个额外的间接级别:有一个根对象名称,它不应该出现在 JSON 中,还有一个集合元素类型名称,它也不应该出现。 (不过,这两者都是 XML 中的共同特征。)

是否可以更改您的 JSON,使其在间接情况下不具有这些额外级别? (例如,此 JSON 是否通过脚本从 XML 转换为测试目的,因此不能反映实际需求?)如果是这样,那么您的问题就解决了。

如果没有,那么这里有一些反序列化的选项:

    要使用 JSON 读取和写入根对象名称,请参阅解决方案 here 或 here

    或者,滚动您自己的代理包装器:

    public sealed class ProductAddWrapper
    
        public ProductAddWrapper()
        
        
    
        public ProductAddWrapper(ProductAdd product)
        
            this.ProductAdd = product;
        
    
        public ProductAdd ProductAdd  get; set; 
    
        public static implicit operator ProductAdd(ProductAddWrapper wrapper)  return wrapper.ProductAdd; 
    
        public static implicit operator ProductAddWrapper(ProductAdd product)  return new ProductAddWrapper(product); 
    
    

    在列表中注入额外的间接级别有点困难。您需要做的是在读取和写入 JSON 时动态重构 JSON,添加或删除额外的人工嵌套级别。这可以通过JsonConverter 来完成:

    class CollectionWithNamedElementsConverter : JsonConverter
    
        static Type GetEnumerableType(Type type)
        
            foreach (Type intType in type.GetInterfaces())
            
                if (intType.IsGenericType
                    && intType.GetGenericTypeDefinition() == typeof(IEnumerable<>))
                
                    return intType.GetGenericArguments()[0];
                
            
            return null;
        
    
        public override bool CanConvert(Type objectType)
        
            return typeof(IEnumerable).IsAssignableFrom(objectType)
                && !typeof(string).IsAssignableFrom(objectType)
                && GetEnumerableType(objectType) != null;
        
    
        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        
            JArray originalArray = JArray.Load(reader);
            if (originalArray == null)
                return null;
            JArray array = new JArray();
            foreach (var item in originalArray)
            
                var child = item.Children<JProperty>().FirstOrDefault();
                if (child != null)
                
                    var value = child.Value;
                    array.Add(child.Value);
                
            
            return serializer.Deserialize(new StringReader(array.ToString()), objectType);
        
    
        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        
            var objectType = value.GetType();
            var itemType = GetEnumerableType(objectType);
    
            IEnumerable collection = value as IEnumerable;
    
            writer.WriteStartArray();
    
            foreach (object item in collection)
            
                writer.WriteStartObject();
                writer.WritePropertyName(itemType.Name);
                serializer.Serialize(writer, item);
                writer.WriteEndObject();
            
    
            writer.WriteEndArray();
        
    
    

然后通过将[JsonConverter(typeof(CollectionWithNamedElementsConverter))] 属性应用于具有项目类型名称的集合来使用它:

    public class ProductAdd
    
        [JsonConverter(typeof(CollectionWithNamedElementsConverter))]
        public List<ProductInformation> Products  get; set; 
    

【讨论】:

以上是关于使用 FromBody 在 WebAPI 中建模的 JSON 对象数组的主要内容,如果未能解决你的问题,请参考以下文章

WEB API FromBody 提交

使用 HttpClient 和 Web API 方法 [FromBody] 参数发布到 Web API 最终为空

WebAPI 2不反序列化List POST请求中FromBody对象的属性

webApi之FromUri和FromBody区别

ASP.NET Core WebAPI FromBody 属性未验证对象非引用字段

Fromform