Shabir jan
Shabir jan

Reputation: 2427

Parsing JSON with Array Root

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);
        }
    }

Upvotes: 1

Views: 193

Answers (2)

Jesse de Wit
Jesse de Wit

Reputation: 4177

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.

Upvotes: 0

Dongdong
Dongdong

Reputation: 2508

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.....");
}

Upvotes: 2

Related Questions