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

时间:2015-09-14 15:59:40

标签: c# json serialization datacontractserializer datacontractjsonserializer

I am writing a WCF service that generates various XML and JSON formats for multiple clients. The code below generates a SerializationException: 'TPH_PriceListJsonItems' is a collection type and cannot be serialized when assigned to an interface type that does not implement IEnumerable ('TPH_IPriceListItems'). The XML part is working fine, but not JSON. I do not understand the error, my interface is implementing IEnumerable to represent a class wrapping a simple List<> so I can use the 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()
    {
    }
}

}

2 个答案:

答案 0 :(得分:1)

这种不一致源于JSON和XML如何代表集合的差异。对于XML,数据协定序列化程序将集合转换为嵌套的元素集 - 外部集合包装器和集合中每个项目的内部元素。对于JSON,序列化程序将集合转换为包含对象的数组。这似乎是合理的,但两者之间存在差异:XML外部元素可以有自己的XML attributes,但JSON arrays 不能拥有自己的属性 - 那里& #39;标准中没有它们的位置。

这在处理type hints时成为一个问题。类型提示是添加到序列化数据的属性,用于指示在序列化类层次结构的接口或基类的情况下,实际序列化了哪个具体类。它们需要启用对象的反序列化而不会丢失数据。在XML中,它们显示为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>

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

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

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

但是,如前所述,JSON数组不能具有属性。那么,DataContractJsonSerializer对多态集合类型做了什么?好吧,除了一些标准的集合接口,正如Fabian所说,它使用硬编码逻辑映射到集合类,它抛出了一个神秘的异常,表明后续的反序列化是不可能的。 (为了进行比较,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
    },
  ]
}

这种设计允许类TPH_IPriceListItems精确控制内部使用的集合类型,而不是将其留给类的用户,因此看起来总体上是一个更好的设计。

答案 1 :(得分:0)

看起来这是one的类似问题。 DataContractJsonSerializer中支持的接口列表是硬编码的。因此,您无法添加自己的List Wrapper界面。

为什么不直接删除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>
{
}