1

I have this generic method to parse JSON

 public async Task<T> ProcessAsync<T>(HttpRequestMessage request, NamingStrategy namingStrategy)
    {
        if (!string.IsNullOrEmpty(_authToken))
        {
            request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _authToken);
        }

        HttpResponseMessage response = await _client.SendAsync(request);
        if (response.IsSuccessStatusCode)
        {
            _logger.LogSuccess(response.StatusCode.ToString(), request.Method.ToString(), request.RequestUri.ToString());

            var dezerializerSettings = new JsonSerializerSettings
            {
                ContractResolver = new DefaultContractResolver
                {
                    NamingStrategy = namingStrategy
                }
            };
            try
            {
                T responseModel = JsonConvert.DeserializeObject<T>(await response.Content.ReadAsStringAsync(), dezerializerSettings);
                return responseModel;

            }
            catch (Exception ex)
            {
                _logger.LogError(request.Method.ToString(), request.RequestUri.ToString(), ex);
                throw;
            }
        }
        else
        {
            throw await GetFailureResponseModel(response);

        }
    }

And this is working fine, But now I haven encountered an edge case, one of my API response contain Array in root. Like this

[
{
    "productId": "100013",
    "lastUpdate": "2018-02-07 15:07:09.0"
},
{
    "productId": "643927",
    "lastUpdate": "2018-07-05 15:25:48.0"
},
{
    "productId": "699292",
    "lastUpdate": "2018-07-05 15:22:24.0"
},
{
    "productId": "722579",
    "lastUpdate": "2018-07-05 15:20:52.0"
},
{
    "productId": "722580",
    "lastUpdate": "2018-07-05 15:20:53.0"
}

]

And I am getting issues parsing above JSON. This is how i am trying to parse

 var response = await _client.GetAsync<FavoriteProductResponseModel>($"v2/member/favourites?total={WebUtility.UrlEncode(total)}&offset={WebUtility.UrlEncode(offset)}");

And this is my model to which I am trying to parse,

 public class FavoriteProductResponseModel : BaseResponse
{
    public List<FavoriteProduct> favoriteProducts { get; set; }
}

Is there any way I can parse this type of JSON with my Generic method? FYI: As you can see my model is extended from BaseResponse, this is for Type Constraints in Generic Method. i.e

 public async Task<T> GetAsync<T>(string uri, NamingStrategy namingStrategy) where T : BaseResponse
    {
        using (var requestMessage = new HttpRequestMessage(HttpMethod.Get, uri))
        {
            return await ProcessAsync<T>(requestMessage, namingStrategy);
        }
    }
1
  • So, you are using JsonConvert.DeserializeObject in your current API, but the server where you request is using JsonConvert.SerializeObject, that server should give you correct objects. my suggestion would to config that server, give you strong-typed json, then you will do nothing here. My 2nd suggestion is: by your json response, when you call GetAsync<T>, the T should be List<FavoriteProduct>, instead of using FavoriteProductResponseModel. Commented Jun 24, 2019 at 17:32

2 Answers 2

2

I simplified your code to these:

public async Task<T> ProcessAsync<T>(HttpRequestMessage request)
{
    HttpResponseMessage response = await new HttpClient().SendAsync(request);
    T responseModel = JsonConvert.DeserializeObject<T>(await response.Content.ReadAsStringAsync());
    return responseModel;
}

public async Task<T> GetAsync<T>(string uri)
{
    return await ProcessAsync<T>(new HttpRequestMessage(HttpMethod.Get, uri));
}

Then your problems looks very clear now: You want to deserialize an array into an object that contains the array.

so your request should be:

var favorites = await _client.GetAsync<List<FavoriteProduct>>(....)
var response = new FavoriteProductResponseModel { favoriteProducts = favorites };

and your FavoriteProduct should have JsonPropertyAttribute configured:

public class FavoriteProduct
{
    [JsonProperty("productId")]
    public int ProductId { get; set; }

    [JsonProperty("lastUpdate")]
    public DateTime LastUpdate { get; set; }
}

If you do want to use type restriction, you have to code this:

public class BaseResponse<T>
{
    public T Data { get; set; }
}

public class FavoriteProductResponseModel : BaseResponse<List<FavoriteProduct>>
{
}

Then you GetAsync will be:

public async Task<Toutput> GetAsync<Tdata, Toutput>(string uri) where Toutput : BaseResponse<Tdata>, new()
{
    var data = await ProcessAsync<Tdata>(new HttpRequestMessage(HttpMethod.Get, uri));

    return new Toutput { Data = data };
}

and your call will be:

public async Task<FavoriteProductResponseModel> GetFavorites()
{
    return await GetAsync<List<FavoriteProduct>, FavoriteProductResponseModel>("your uri.....");
}
Sign up to request clarification or add additional context in comments.

8 Comments

But your code is missing Generics type constraints :) And I don't want to lose that
@Shabirjan Sure. They are no-use to my answer. does my solution work?
I have already tried the solution you posted, and it works but on cost of removing type constraints which i DON't want to do.
how does it fixes the type constraint issue?
@Shabirjan can you paste the type constraint error here?
|
0

Probably your easiest option, without losing the BaseResponse type constraint, is creating a custom JsonConverter:

public class FavoriveProductConverter : JsonConverter<FavoriteProductResponseModel>
{
    public override FavoriteProductResponseModel ReadJson(
        JsonReader reader,
        Type objectType,
        FavoriteProductResponseModel existingValue,
        bool hasExistingValue,
        JsonSerializer serializer)
    {
        var model = existingValue;
        if (model == null)
        {
            model = new FavoriteProductResponseModel();
        }

        // Here we deserialize the list under the hood
        // And assign it to the FavoriteProducts property.
        model.FavoriteProducts = serializer.Deserialize<List<FavoriteProduct>>(reader);
        return model;
    }

    public override void WriteJson(JsonWriter writer, FavoriteProductResponseModel value, JsonSerializer serializer)
    {
        if (value == null) return;

        // On serialization, we serialize the favorite products list instead
        serializer.Serialize(writer, value.FavoriteProducts);
    }
}

Then add the converter to FavoriteProductResponseModel with JsonConverterAttribute:

[JsonConverter(typeof(FavoriveProductConverter))]
public class FavoriteProductResponseModel : BaseResponse
{
    public List<FavoriteProduct> FavoriteProducts { get; set; }
}

This way you do not need to change your generic implementation and the class will be serialized/deserialized the way you desire.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.