Reputation: 2427
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
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
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