DataContractJsonSerializer:集合类型在分配给接口时无法序列化

DataContractJsonSerializer: collection type cannot be serialized when assigned to an interface

我正在编写一个 WCF 服务,它可以为多个客户端生成各种 XML 和 JSON 格式。下面的代码会生成一个 SerializationException:'TPH_PriceListJsonItems' 是一个集合类型,当分配给一个没有实现 IEnumerable ('TPH_IPriceListItems') 的接口类型时无法序列化。 XML 部分工作正常,但不是 JSON。我不明白这个错误,我的接口正在实现 IEnumerable 来表示一个 class 包装一个简单的 List<> 所以我可以使用 CollectionDataContract.

public class ReproduceDataContractIssue
{
    public static void Main(String[] args)
    {
        // Create test object - vacation products lowest prices grid
        TPH_IPriceList priceList = new TPH_PriceListJson();
        priceList.ListItems.Add(new TPH_PriceListJsonItem() { DestCityName = "Cancun", StayDuration =  7, LowestPrice = 1111 });
        priceList.ListItems.Add(new TPH_PriceListJsonItem() { DestCityName = "Jamaica", StayDuration = 14, LowestPrice = 2222 });

        // Serialize into XML string
        DataContractSerializer serializer = new DataContractSerializer(priceList.GetType());
        MemoryStream memStream = new MemoryStream();
        serializer.WriteObject(memStream, priceList);
        memStream.Seek(0, SeekOrigin.Begin);
        string xmlOutput;
        using (var streamReader = new StreamReader(memStream))
            xmlOutput = streamReader.ReadToEnd();

        // Serialize into JSON string
        DataContractJsonSerializer jsonSerializer = new DataContractJsonSerializer(priceList.GetType());
        jsonSerializer.WriteObject(memStream = new MemoryStream(), priceList);
        memStream.Seek(0, SeekOrigin.Begin);
        string jsonOutput;
        using (var streamReader = new StreamReader(memStream))
            jsonOutput = streamReader.ReadToEnd();
    }
}

public interface TPH_IPriceList
{
    TPH_IPriceListItems ListItems { get; set; }
}
public interface TPH_IPriceListItems : IEnumerable<TPH_IPriceListItem>, IEnumerable, IList<TPH_IPriceListItem>
{
}
public interface TPH_IPriceListItem
{
    string DestCityName { get; set; }
    int    StayDuration { get; set; }
    int    LowestPrice  { get; set; }
}

[DataContract(Name = "PriceList")]
[KnownType(typeof(TPH_PriceListJsonItems))]
public class TPH_PriceListJson : TPH_IPriceList
{
    [DataMember]
    public TPH_IPriceListItems ListItems { get; set; }

    public TPH_PriceListJson()
    {
        ListItems = new TPH_PriceListJsonItems();
    }
}
[DataContract]
public class TPH_PriceListJsonItem : TPH_IPriceListItem
{
    [DataMember(Order = 1)]
    public string DestCityName { get; set; }
    [DataMember(Order = 2)]
    public int StayDuration { get; set; }
    [DataMember(Order = 3)]
    public int LowestPrice { get; set; }

    public TPH_PriceListJsonItem()
    {
    }
}
[CollectionDataContract(Name = "ListItems", ItemName = "ListItem")]
[KnownType(typeof(TPH_PriceListJsonItem))]
public class TPH_PriceListJsonItems : List<TPH_IPriceListItem>, TPH_IPriceListItems, IEnumerable<TPH_IPriceListItem>, IEnumerable
{
    public TPH_PriceListJsonItems(int capacity)
        : base(capacity)
    {
    }
    public TPH_PriceListJsonItems()
        : base()
    {
    }
}

}

看起来这是一个与此 one 类似的问题。 DataContractJsonSerializer 中支持的接口列表是硬编码的。所以不能添加自己的ListWrapper接口。

为什么不像下面的代码那样直接删除 TPH_IPriceListItems?它更简单,也应该做你想做的事:

public interface TPH_IPriceList
{
    IList<TPH_IPriceListItem> ListItems { get; set; }
}

public interface TPH_IPriceListItem
{
    string DestCityName { get; set; }
    int StayDuration { get; set; }
    int LowestPrice { get; set; }
}

[DataContract(Name = "PriceList")]
[KnownType(typeof(TPH_PriceListJsonItems))]
public class TPH_PriceListJson : TPH_IPriceList
{
    [DataMember]
    public IList<TPH_IPriceListItem> ListItems { get; set; }

    public TPH_PriceListJson()
    {
        ListItems = new TPH_PriceListJsonItems();
    }
}
[DataContract]
public class TPH_PriceListJsonItem : TPH_IPriceListItem
{
    [DataMember(Order = 1)]
    public string DestCityName { get; set; }
    [DataMember(Order = 2)]
    public int StayDuration { get; set; }
    [DataMember(Order = 3)]
    public int LowestPrice { get; set; }
}

[CollectionDataContract(Name = "ListItems", ItemName = "ListItem")]
[KnownType(typeof(TPH_PriceListJsonItem))]
public class TPH_PriceListJsonItems : List<TPH_IPriceListItem>
{
}

不一致的原因是 JSON 和 XML 表示集合的方式不同。对于 XML,数据协定序列化程序将集合转换为一组嵌套的元素——一个外部集合包装器,以及集合中每个项目的一个内部元素。对于 JSON,序列化程序将集合转换为包含对象的数组。这似乎是合理的,但两者之间存在差异:XML 外部元素可以有自己的 XML attributes, but JSON arrays 不能有自己的属性 —— 根本就没有地方为他们在标准中。

这成为处理 type hints. Type hints are properties added to the serialized data to indicate, in the event of serializing an interface or base class of a class hierarchy, what concrete class was actually serialized. They are required to enable deserialization of the object without data loss. In XML, they appear as an i:type 属性的问题:

<PriceList xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/Question32569055.V1">
    <ListItems i:type="ListItems">  <!-- Notice the type hint here. --> 
        <ListItem i:type="TPH_PriceListJsonItem">  <!-- Notice the type hint here also. --> 
            <DestCityName>Cancun</DestCityName>
            <StayDuration>7</StayDuration>
            <LowestPrice>1111</LowestPrice>
        </ListItem>
    </ListItems>
</PriceList>

正如您从自己的示例中看到的,可以为集合和非集合添加类型提示classes

在 JSON 个对象中,它们显示为添加的 属性,名为 "__type":

{
  "__type": "TPH_PriceListJsonItem:#Question32569055.V3",
  "DestCityName": "Cancun",
  "StayDuration": 7,
  "LowestPrice": 1111
}

但是,如前所述,JSON 数组不能有属性。那么,DataContractJsonSerializer 对多态集合类型有什么作用呢?好吧,除了一些标准集合接口,正如 Fabian 指出的那样,它们使用硬编码逻辑映射到集合 classes,它会抛出一个神秘的异常来指示后续的反序列化是不可能的。 (为了比较,Json.NET 引入了一个额外的容器对象来保存集合类型信息。参见 TypeNameHandling setting。)

这种不一致的解决方案是将集合显式序列化为具体集合(TPH_PriceListJsonItems 在您的情况下)而不是作为接口:

[DataContract(Name = "PriceList")]
[KnownType(typeof(TPH_PriceListJsonItems))]
public class TPH_PriceListJson : TPH_IPriceList
{
    [IgnoreDataMember]
    public TPH_IPriceListItems ListItems
    {
        get
        {
            return ListItemList;
        }
        set
        {
            var list = value as TPH_PriceListJsonItems;
            if (list == null)
            {
                list = new TPH_PriceListJsonItems();
                if (value != null)
                    list.AddRange(value);
            }
            ListItemList = list;
        }
    }

    [DataMember(Name = "ListItems")]
    TPH_PriceListJsonItems ListItemList { get; set; }

    public TPH_PriceListJson()
    {
        ListItemList = new TPH_PriceListJsonItems();
    }
}

这消除了对集合元素的类型提示的需要,同时为集合成员保留它。它生成以下 XML:

<PriceList xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/Question32569055.V3">
    <ListItems>  <!-- No type hint here any more. -->
        <ListItem i:type="TPH_PriceListJsonItem">  <!-- But the type hint is still here. -->
            <DestCityName>Cancun</DestCityName>
            <StayDuration>7</StayDuration>
            <LowestPrice>1111</LowestPrice>
        </ListItem>
    </ListItems>
</PriceList>

并产生以下 JSON:

{
  "ListItems": [
    {
      "__type": "TPH_PriceListJsonItem:#Question32569055.V3",
      "DestCityName": "Cancun",
      "StayDuration": 7,
      "LowestPrice": 1111
    },
  ]
}

此设计允许 class TPH_IPriceListItems 准确控制内部使用的集合类型,而不是将其留给 class 的用户使用,因此看起来更可取整体设计。