DataContractJsonSerializer.ReadObject 传递不同类型的对象
DataContractJsonSerializer.ReadObject pass a different type of object
我有个问题,某个httpAPI可以return两种不同类型的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)
{
}
}
这里有一个比 serializer.ReadObject()
return 错误更基本的问题:从 WebResponse.GetResponseStream()
编辑的 Stream
return 无法重新定位和读取从第二次。因此,通常您需要将响应复制到某个本地缓冲区并查询 returned 的内容。至少有两种方法。
首先,您可以将响应复制到本地 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 by Nick 的扩展方法:
public static class StreamExtensions
{
//
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()
and query the returned results keeping in mind the mapping from JSON to XML defined in Mapping Between JSON and XML 编辑的 XmlReader
return 将响应加载到中间 XElement
。然后根据存在的元素将中间 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
.
标记可区分的数据成员
我有个问题,某个httpAPI可以return两种不同类型的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)
{
}
}
这里有一个比 serializer.ReadObject()
return 错误更基本的问题:从 WebResponse.GetResponseStream()
编辑的 Stream
return 无法重新定位和读取从第二次。因此,通常您需要将响应复制到某个本地缓冲区并查询 returned 的内容。至少有两种方法。
首先,您可以将响应复制到本地 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 by Nick 的扩展方法:
public static class StreamExtensions
{
//
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()
and query the returned results keeping in mind the mapping from JSON to XML defined in Mapping Between JSON and XML 编辑的 XmlReader
return 将响应加载到中间 XElement
。然后根据存在的元素将中间 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
.