Dani Aguado
Dani Aguado

Reputation: 215

RestSharp How to deal with different JSON response formats for same query

I've been fighting with this for a while and I think is time to ask for some help.

I'm using RestSharp to connect to my local PHP REST API. The format I'm returning the api results is as follows (JSON):

{
"estado": "login_correcto",
"content": {
   "id": 1,
   "nombreusuario": "daniaguado",
   "contrasena": "qwerty",
   "email": "[email protected]",
   "nivelacceso": 5,
    "created_at": "2017-08-01 10:31:16",
    "updated_at": "-0001-11-30 00:00:00"
    }
}

For this, I've created a custom Usuario class (User) and a custom ResponseUsuario as follows:

class Usuario
{
    public int id { get; set; }
    public string nombreusuario { get; set; }
    public string contrasena { get; set; }
    public string email { get; set; }
    public int nivelacceso { get; set; }
    public string created_at { get; set; }
    public string updated_at { get; set; }
}

Then the ResponseUsuario:

class ResponseUsuario
{
    public string estado { get; set; }
    public Usuario content { get; set; }
}

The response is being parsed ok when the response is ok (202) and the user exists.

But, when the login is incorrect, in "content" I'm returning a message, not an User:

{
 "estado": "login_incorrecto",
 "content": "La contraseña es incorrecta / Password incorrect"
}

So, If I use ResponseUsuario there, queryResult.Data is null, because it cannot map the content to the Usuario class. The fact is that if I don't use ResponseUsuario and instead use a Response class, in which content variable is type object, I cannot cast it to Usuario and I cannot deal with it.

This is the more general Response class which I understand I should be using for all my queries:

class Response
{
    public string estado { get; set; }
    public object content { get; set; }
}

Finally, this is my RestSharp query:

ApiClient api = new ApiClient(); // Just create the client with some common parameters

var request = new RestRequest("usuarios/login", RestSharp.Method.POST);

request.AddParameter("nombreusuario", textbox_usuario.Text);
request.AddParameter("contrasena", textbox_contrasena.Text);

var queryResult = api.cliente.Execute<ResponseUsuario>(request);

if (queryResult.StatusCode == System.Net.HttpStatusCode.OK)
{
    Usuario u = (Usuario) queryResult.Data.content; // Gives error when using Execute<Response> (object content)
    String s = queryResult.Data.estado;

    Console.Out.WriteLineAsync($"OK - {u.nombreusuario} - {u.email} - Acceso: {u.nivelacceso}");
} 
else
{
    Console.Out.WriteLineAsync($"Query Failed: {queryResult.StatusDescription}, estado: {queryResult.Data.estado}"); // Giving error when using Execute<ResponseUsuario>
}

How can I fix the cast to Usuario (or the matching class for content)? I think the Response class should be common to all my queries to the API and then cast the Content part to it's appropiate class, but I don't know how to do it.

Upvotes: 2

Views: 2917

Answers (4)

Alexei - check Codidact
Alexei - check Codidact

Reputation: 23078

Really late to the party. I have met a similar problem when an API returned different structures on error vs. 200. I gave up RestSharp's typed call and explicitly deserialize the payload.

var response = RestClient.Execute<IdamValidationResultApiModel>(request);
if (response.IsSuccessful)
   var t1 = JsonConvert.DeserializeObject<FirstType>(response.Content);
else
   var t2 = JsonConvert.DeserializeObject<SecondType>(response.Content);

Upvotes: 2

Dani Aguado
Dani Aguado

Reputation: 215

I finally solved the issue by generating a custom Reflection from the Dictionary Object to the class, that is, at the end, a custom parser.

I created a new class Tools and added a conversion function:

public static T CastToObject<T>(IDictionary<string, object> dict) where T : class
{
   Type type = typeof(T);
   T result = (T)Activator.CreateInstance(type);
   foreach (var item in dict)
   {
       type.GetProperty(item.Key).SetValue(result, item.Value, null);
   }
   return result;
 }

And on the Api Query, added a switch-case depending on the Status code:

var queryResult = api.cliente.Execute<Response>(request);

switch(queryResult.StatusCode)
{
    case System.Net.HttpStatusCode.OK:
        Usuario u = Tools.CastToObject<Usuario>((IDictionary<string, object>)queryResult.Data.content);
        String status = queryResult.Data.estado;
        Console.Out.WriteLineAsync($"OK - {u.nombreusuario} - {u.email} - Acceso: {u.nivelacceso}");
        break;
    case System.Net.HttpStatusCode.NotFound:
            Console.Out.WriteLineAsync($"Login Failed: User does not exists");
            break;
    case System.Net.HttpStatusCode.Unauthorized:
            Console.Out.WriteLineAsync($"Login Failed: Incorrect Password");
            break;
    default:
            Console.Out.WriteLineAsync($"Error when connecting to the API");
            break;
}

Further I'll need to deal with responses containing list inside the content field but I think the procedure will be something similar to this one: to create a custom parser for the content field and cast it to my class.

Upvotes: 0

alteraki
alteraki

Reputation: 70

Does each response return a different Http status code? I'm guessing that you're returning a 401 from your rest api. If this is the case:

You can then use a switch to deal with other status codes.

    HttpStatusCode statusCode = queryResult.StatusCode;
    int numericStatusCode = (int)statusCode;

    switch (numericStatusCode)
    {
        case 202:
            dynamic content = queryResult.Data.content;
            Usuario u = content.content; 
            String s = queryResult.Data.estado;
            Console.Out.WriteLineAsync($"OK - {u.nombreusuario} - {u.email} - Acceso: {u.nivelacceso}");
            break;
        case 401:
            //Deal with the failed login
            break;
    }

Finally, the reason that you're getting an error, is because you're trying to cast the response object which is of type UsarioResponse to Usario. You need to navigate the json to find content.content (as seen above)

Upvotes: 0

Tom S&#246;hne
Tom S&#246;hne

Reputation: 522

check if (queryResult.Data.content is Usuario) and cast only if it is true

else cast to string.

Upvotes: 0

Related Questions