1

我正在编写一个为多个客户端生成各种 XML 和 JSON 格式的 WCF 服务。下面的代码生成一个 SerializationException:“TPH_PriceListJsonItems”是一个集合类型,当分配给一个不实现 IEnumerable 的接口类型(“TPH_IPriceListItems”)时不能被序列化。XML 部分工作正常,但不是 JSON。我不明白这个错误,我的接口正在实现 IEnumerable 来表示一个包装简单 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()
    {
    }
}

}

4

2 回答 2

1

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

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

这种不一致的解决方案是将集合显式序列化为具体集合(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准确控制内部使用的集合类型,而不是将其留给类的用户,因此总体上看起来是一个更可取的设计。

于 2015-09-14T18:10:16.737 回答
0

看起来这是一个类似的问题。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>
{
}
于 2015-09-14T16:59:07.917 回答