jeiem
jeiem

Reputation: 127

JArray Objects to a Class with array property

I have a service that consumes API methods and it returns a json string. I have a base response object below:

public class BaseResponse
{
    public bool HasError { get; set; }
    public string Message { get; set; }
}

and a generic type parser

 Task<R> ParseResponse<R>(HttpResponseMessage response) where R : BaseResponse,  new()

That returns:

 return JsonConvert.DeserializeObject<R>(responseText);      

Note that I don't have control over the response of the api. So I get this response, an array of objects.

 [{"created":"0001-01-01T00:00:00","updated":null,"name":"Test 1","decscription":null,"id":1},{"created":"0001-01-01T00:00:00","updated":null,"name":"Test 2","decscription":null,"id":2},{"created":"0001-01-01T00:00:00","updated":null,"name":"Test 3","decscription":null,"id":3},{"created":"0001-01-01T00:00:00","updated":null,"name":"Test 4","decscription":null,"id":4}]

The problem is I would like to parse it to this class:

public class TestResponse : BaseResponse
{
    public Test[] Tests { get; set; }
}

The HasError and Message will have a value when an error occurred on the server or the api returned a Bad Request etc.

Any information will be appreciated. Thank you.

Upvotes: 1

Views: 290

Answers (1)

dbc
dbc

Reputation: 117046

From your comment, it sounds as though a successful response will appear as a JSON array, but if an error occurs then a JSON object will be returned with an error flag and message. In order to parse both possible responses, you can create a custom JsonConverter that checks whether the received JSON is an array or an object, and proceeds accordingly:

public class TestResponseConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return objectType == typeof(TestResponse);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (reader.TokenType == JsonToken.Null)
            return null;
        else if (reader.TokenType == JsonToken.StartArray)
        {
            // A valid response.  Populare the Tests array.
            var tests = serializer.Deserialize<Test[]>(reader);
            return new TestResponse { Tests = tests };
        }
        else if (reader.TokenType == JsonToken.StartObject)
        {
            // An error response.  Populate HasError and Message
            var response = new TestResponse();
            serializer.Populate(reader, response);
            return response;
        }
        else
        {
            throw new JsonSerializationException("Unexpected reader.TokenType: " + reader.TokenType);
        }
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        var test = (TestResponse)value;
        if (test.HasError && test.Tests != null && test.Tests.Length > 0)
        {
            serializer.Serialize(writer, new { HasError = test.HasError, Message = test.Message, Tests = test.Tests });
        }
        else if (test.HasError || test.Tests == null)
        {
            serializer.Serialize(writer, new { HasError = test.HasError, Message = test.Message });
        }
        else
        {
            serializer.Serialize(writer, test.Tests);
        }
    }
}

Then apply it to the class using JsonConverterAttribute like so:

[JsonConverter(typeof(TestResponseConverter))]
public class TestResponse : BaseResponse
{
    public Test[] Tests { get; set; }
}

Or add it to JsonSerializerSettings like so:

var settings = new JsonSerializerSettings { Converters = new[] { new TestResponseConverter() } };
var response = JsonConvert.DeserializeObject<R>(responseText, settings);

Prototype fiddle.

Upvotes: 1

Related Questions