【问题标题】:DataContractJsonSerializer.ReadObject pass a different type of objectDataContractJsonSerializer.ReadObject 传递不同类型的对象
【发布时间】:2018-01-19 23:01:25
【问题描述】:

我有一个问题,某个 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)
    {
    }
}

【问题讨论】:

  • 反应有何不同?你能分享一个简化的例子吗?
  • 您可以在ResponseDtoSecondResponseDto的不同成员上设置[DataMember(IsRequired = true)],如果该成员不存在则会抛出异常。但顺便说一句,从响应流中获取和读取 两次 真的有效吗?这让我很吃惊。

标签: c# json datacontractjsonserializer


【解决方案1】:

这里有一个比让serializer.ReadObject() 返回错误更基本的问题:从WebResponse.GetResponseStream() 返回的Stream 不能重新定位并从第二次读取。因此,通常您需要将响应复制到某个本地缓冲区并查询返回的内容。至少有两种方法。

首先,您可以将响应复制到本地MemoryStream 并尝试反序列化为ResponseDto。如果失败,请尝试SecondResponseDto。要在反序列化时区分这两种类型,可以用[DataMember(IsRequired = true)] 标记区分属性。

比如说ResponseDto 有一个成员dataSecondResponseDto 有一个成员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 answerNick改编的扩展方法:

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());
}

此解决方案利用了DataContractJsonSerializerDataContractSerializer 共享代码库这一事实,实际上是通过在反序列化期间在内部将 JSON 动态转换为 XML 来工作的。使用此解决方案,不再需要使用IsRequired = true 标记可区分的数据成员。

【讨论】:

  • 我知道 Stream 重新定位,这不是问题的一部分,因为我知道如何解决它
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-07-20
  • 1970-01-01
  • 2012-05-13
相关资源
最近更新 更多