我遇到了问题,某个http API可以返回两种不同类型的JSON对象。不幸的是,我不得不忍受它。我必须使用.NET 3.5代码处理它,并使用DataContractJsonSerializer
反序列化来自服务的响应。这也是一个约束 - 我不能使用其他任何东西来进行json序列化。当我尝试从类型2 DataContractJsonSerializer
的json对象反序列化类型1的对象时,只需成功 - 只有对象的所有属性都设置为默认值。有没有办法让它失败?
ResponseDto Get<ResponseDto>(string requestUrl)
{
// skip all the HttpWebRequest bullshit
try
{
var response = request.GetResponse();
if (response.StatusCode = HttpStatusCode.Ok)
{
var serializer = new DataContractJsonSerializer(typeof(ResponseDto));
// I would like this line to fail somehow, give me null back, whatever
var responseDto = (ResponseDto)serializer.ReadObject(response.GetResponseStream());
// this never happens now
if (responseDto == null)
{
var otherResponseSerializer = new DataContractJsonSerializer(typeof(SecondResponseDto));
// SecondResponseDto is always fixed type
var otherResponse = (SecondResponseDto)otherResponseSerializer.ReadObject(response.GetResponseStream());
// this typically would throw an exception
HandleOtherResponse(otherResponse);
return default(ResponseDto);
}
}
}
catch(WebException e)
{
}
}
答案 0 :(得分:1)
此处有一个更基本的问题,而不是让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()
返回的XElement
将响应加载到中间XmlReader
并查询返回的结果,同时记住Mapping Between JSON and XML中定义的JSON到XML。然后根据存在的元素将中间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
标记区分数据成员。