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()
{
}
}
}
答案 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>
{
}