这里有一个比让serializer.ReadObject() 返回错误更基本的问题:从WebResponse.GetResponseStream() 返回的Stream 不能重新定位并从第二次读取。因此,通常您需要将响应复制到某个本地缓冲区并查询返回的内容。至少有两种方法。
首先,您可以将响应复制到本地MemoryStream 并尝试反序列化为ResponseDto。如果失败,请尝试SecondResponseDto。要在反序列化时区分这两种类型,可以用[DataMember(IsRequired = true)] 标记区分属性。
比如说ResponseDto 有一个成员data 而SecondResponseDto 有一个成员results。您可以将它们定义如下:
[DataContract]
public class ResponseDto
{
[DataMember(Name = "data", IsRequired = true)]
public Data data { get; set; }
}
[DataContract]
public class SecondResponseDto
{
[DataMember(Name = "results", IsRequired = true)]
public List<Result> Results { get; set; }
}
然后反序列化如下:
ResponseDto response1;
SecondResponseDto response2;
var copyStream = new MemoryStream();
using (var response = (HttpWebResponse)request.GetResponse())
{
if (response.StatusCode == HttpStatusCode.OK)
{
using (var responseStream = response.GetResponseStream())
{
responseStream.CopyTo(copyStream);
}
}
}
try
{
var serializer = new DataContractJsonSerializer(typeof(ResponseDto));
copyStream.Position = 0L;
response1 = (ResponseDto)serializer.ReadObject(copyStream);
}
catch (SerializationException)
{
response1 = null;
}
if (response1 != null)
response2 = null;
else
{
try
{
var otherResponseSerializer = new DataContractJsonSerializer(typeof(SecondResponseDto));
copyStream.Position = 0L;
response2 = (SecondResponseDto)otherResponseSerializer.ReadObject(copyStream);
}
catch (SerializationException)
{
response2 = null;
}
}
其中CopyTo()是this answer由Nick改编的扩展方法:
public static class StreamExtensions
{
// https://stackoverflow.com/questions/230128/how-do-i-copy-the-contents-of-one-stream-to-another
public static void CopyTo(this Stream input, Stream output)
{
byte[] buffer = new byte[32768];
int read;
while ((read = input.Read(buffer, 0, buffer.Length)) > 0)
{
output.Write(buffer, 0, read);
}
}
}
(此扩展方法仅在 .Net 3.5 中需要,因为 .Net 4.0 及更高版本已内置 Stream.CopyTo()。)
在此解决方案中,区分数据成员不需要出现在根数据合约上。只要[DataMember(IsRequired = true)] 存在于对象图中的某处,如果对象存在但标记的数据成员不存在,序列化程序就会抛出异常。
其次,您可以使用JsonReaderWriterFactory.CreateJsonReader() 返回的XmlReader 将响应加载到中间XElement 并查询返回的结果,记住从JSON 到在@ 中定义的XML 的映射987654327@。然后根据存在的元素将中间 XML 反序列化为适当的类型。在上述情况下,您的代码可能如下所示:
ResponseDto response1 = null;
SecondResponseDto response2 = null;
XElement root = null;
using (var response = (HttpWebResponse)request.GetResponse())
{
if (response.StatusCode == HttpStatusCode.OK)
{
using (var responseStream = response.GetResponseStream())
using (var reader = JsonReaderWriterFactory.CreateJsonReader(responseStream, XmlDictionaryReaderQuotas.Max))
{
root = XElement.Load(reader);
}
}
}
// Replace the Where queries below with something appropriate to your actual JSON.
if (root != null && root.Elements().Where(e => e.Name.LocalName == "data").Any())
{
var serializer = new DataContractJsonSerializer(typeof(ResponseDto));
response1 = (ResponseDto)serializer.ReadObject(root.CreateReader());
}
else if (root != null && root.Elements().Where(e => e.Name.LocalName == "results").Any())
{
var serializer = new DataContractJsonSerializer(typeof(SecondResponseDto));
response2 = (SecondResponseDto)serializer.ReadObject(root.CreateReader());
}
此解决方案利用了DataContractJsonSerializer 与DataContractSerializer 共享代码库这一事实,实际上是通过在反序列化期间在内部将 JSON 动态转换为 XML 来工作的。使用此解决方案,不再需要使用IsRequired = true 标记可区分的数据成员。