Try to serialize the response in two different models

Viewed 27

I am trying to handle response that is different from what I expect normally. Normally I would expect a response that looks like the Item model below:

  public class Item
  {
    public string Response { get; set; }
    public string StatusCode { get; set; }
  }

But if the DB is down or not responding, it returns this type of answer as seen from the DbError model below:

public class DbError
  {
    public string Code { get; set; }
    public string Title { get; set; }
    public string Message { get; set; }
    public string ErrorCode { get; set; }
    public string Cause { get; set; }
  }

Typically I would try to serialize the response like this:

Item? response = JsonSerializer.Deserialize<Item>(jsonString);

How can I try to serialize as an Item but in case that the db is returning the error, serialize with the DbError model?

2 Answers

There are at least two options. Assuming default System.Text.Json is used to handle json - either write custom converter similar to one in this answer (existence of a property should be used as discriminant not a property value as in that answer) or deserialize dynamically. For example using JsonNode API which became available since .NET 6 (docs):

var doc = JsonSerializer.Deserialize<JsonObject>(jsonString))
if (doc.ContainsKey("response")) 
{
    var item = doc.Deserialize<Item>();
}
else
{
    var error = doc.Deserialize<DbError>();
} 

For earlier versions:

using var doc = JsonSerializer.Deserialize<JsonDocument>(jsonString);
if (doc.RootElement.TryGetProperty("response", out var _))
{
    var item = doc.Deserialize<Item>();
}
else
{
    var error = doc.Deserialize<DbError>();
}

If you are using Newtonsoft's Json.NET both approaches can be implemented with it too.

you'd need what is normally called "envelope". Don't return your item directly, but wrap it into another object. Something like this:

public class Envelope<T>
{
   public T Data {get;}
   public Error Error {get;}
   public bool IsSuccessful => (this.Error is null);
}

your API consumer would have to check if IsSuccessful is true and otherwise inspect the contents of the Error property.

Related