You have a more basic problem here than making serializer.ReadObject()
return an error: the Stream
returned from WebResponse.GetResponseStream()
cannot be repositioned and read from a second time. Thus in general you will need to copy the response into some local buffer and query what was returned. There are at least two approaches to this.
Firstly, you could copy the response into a local MemoryStream
and attempt to deserialize into ResponseDto
. Then if that fails, try SecondResponseDto
. To distinguish between the two types during deserialization, you can mark distinguishing properties with [DataMember(IsRequired = true)]
.
Say for instance ResponseDto
has a member data
while SecondResponseDto
has a member results
. You could define them as follows:
[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; }
}
And then deserialize as follows:
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;
}
}
Where CopyTo()
is an extension method adapted from this answer by 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);
}
}
}
(This extension method is only required in .Net 3.5 as .Net 4.0 and later have Stream.CopyTo()
built in.)
The distinguishing data member(s) do not need to be present on the root data contract in this solution. As long as [DataMember(IsRequired = true)]
is present somewhere in the object graph the serializer will throw an exception if the object is present but the marked data member is not.
Secondly, you could load the response into an intermediate XElement
using an XmlReader
returned by JsonReaderWriterFactory.CreateJsonReader()
and query the returned results keeping in mind the mapping from JSON to XML defined in Mapping Between JSON and XML. Then deserialize the intermediate XML to the appropriate type depending on the elements present. In the case above your code might look like:
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());
}
This solution takes advantage of the fact that DataContractJsonSerializer
shares a code base with DataContractSerializer
and actually works by internally translating the JSON to XML on the fly during deserialization. With this solution it is no longer required to mark distinguishing data members with IsRequired = true
.