Я работаю над приложением, в котором я получаю набор результатов JSON от стороннего API. Я десериализовал его в свою пользовательскую модель представления.
Существует список записей, который содержит записи с повторяющимися записями. Я хочу получить список со средним пользователем на эту дату.
Вот мой код и JSON. Я хочу использовать столбец даты dt_txt
в одной записи и получить среднюю температуру, скорость ветра и влажность. Он считает 2 из приведенного ниже JSON, чтобы получить среднее значение для вышеупомянутых полей упоминаний.
public async Task<WeatherViewModel?> GetForecast(string param)
{
var response = JsonConvert.DeserializeObject<WeatherReponseModel>(await httpClientApiService.GetRequestAsync(param, string.Format(WeatherEndPointsUrls.forecast, param)));
if (response?.list == null)
return null;
// Here I want to rearrange the records.
// var orderList = response.list.GroupBy(x => Convert.ToDateTime(x.dt_txt)).ToList();
return new WeatherViewModel();
}
Результирующий JSON:
"message":0,
"cnt":40,
"list":[
{
"dt":1668632400,
"main":{
"temp":5.98,
"feels_like":2.53,
"temp_min":5.23,
"temp_max":5.98,
"pressure":1000,
"sea_level":1000,
"grnd_level":1002,
"humidity":81,
"temp_kf":0.75
},
"weather":[
{
"id":803,
"main":"Clouds",
"description":"broken clouds",
"icon":"04n"
}
],
"clouds":{
"all":83
},
"wind":{
"speed":5.06,
"deg":91,
"gust":10.53
},
"visibility":10000,
"pop":0.29,
"sys":{
"pod":"n"
},
"dt_txt":"2022-11-16 21:00:00"
},
{
"dt":1668643200,
"main":{
"temp":4.69,
"feels_like":0.63,
"temp_min":3.86,
"temp_max":4.69,
"pressure":1003,
"sea_level":1003,
"grnd_level":1002,
"humidity":78,
"temp_kf":0.83
},
"weather":[
{
"id":803,
"main":"Clouds",
"description":"broken clouds",
"icon":"04n"
}
],
"clouds":{
"all":84
},
"wind":{
"speed":5.69,
"deg":94,
"gust":12.3
},
"visibility":10000,
"pop":0.27,
"sys":{
"pod":"n"
},
"dt_txt":"2022-11-17 00:00:00"
},
{
"dt":1668654000,
"main":{
"temp":3.12,
"feels_like":-1.59,
"temp_min":3.12,
"temp_max":3.12,
"pressure":1006,
"sea_level":1006,
"grnd_level":1001,
"humidity":73,
"temp_kf":0
},
"weather":[
{
"id":804,
"main":"Clouds",
"description":"overcast clouds",
"icon":"04n"
}
],
"clouds":{
"all":97
},
"wind":{
"speed":6.22,
"deg":93,
"gust":12.73
},
"visibility":10000,
"pop":0,
"sys":{
"pod":"n"
},
"dt_txt":"2022-11-17 03:00:00"
},
{
"dt":1668664800,
"main":{
"temp":3.11,
"feels_like":-1.81,
"temp_min":3.11,
"temp_max":3.11,
"pressure":1006,
"sea_level":1006,
"grnd_level":1000,
"humidity":74,
"temp_kf":0
},
"weather":[
{
"id":804,
"main":"Clouds",
"description":"overcast clouds",
"icon":"04n"
}
],
"clouds":{
"all":89
},
"wind":{
"speed":6.72,
"deg":100,
"gust":12.45
},
"visibility":10000,
"pop":0,
"sys":{
"pod":"n"
},
"dt_txt":"2022-11-17 06:00:00"
},
{
"dt":1668675600,
"main":{
"temp":5.35,
"feels_like":0.76,
"temp_min":5.35,
"temp_max":5.35,
"pressure":1006,
"sea_level":1006,
"grnd_level":1000,
"humidity":59,
"temp_kf":0
},
"weather":[
{
"id":804,
"main":"Clouds",
"description":"overcast clouds",
"icon":"04d"
}
],
"clouds":{
"all":100
},
"wind":{
"speed":7.59,
"deg":111,
"gust":12.21
},
"visibility":10000,
"pop":0,
"sys":{
"pod":"d"
},
"dt_txt":"2022-11-17 09:00:00"
},
{
"dt":1668686400,
"main":{
"temp":5.65,
"feels_like":1.15,
"temp_min":5.65,
"temp_max":5.65,
"pressure":1004,
"sea_level":1004,
"grnd_level":999,
"humidity":66,
"temp_kf":0
},
"weather":[
{
"id":804,
"main":"Clouds",
"description":"overcast clouds",
"icon":"04d"
}
],
"clouds":{
"all":100
},
"wind":{
"speed":7.6,
"deg":113,
"gust":13.15
},
"visibility":10000,
"pop":0,
"sys":{
"pod":"d"
},
"dt_txt":"2022-11-17 12:00:00"
}
],
"city":{
"id":2950159,
"name":"Berlin",
"coord":{
"lat":52.5244,
"lon":13.4105
},
"country":"DE",
"population":1000000,
"timezone":3600,
"sunrise":1668580182,
"sunset":1668611564
}
}
Классы моделей:
public class WeatherReponseModel
{
public string? cod { get; set; }
public dynamic? message { get; set; }
public int cnt { get; set; }
public List<ListViewModel>? list { get; set; }
public CityViewModel? city { get; set; }
}
public class Main
{
public double temp { get; set; }
public double feels_like { get; set; }
public double temp_min { get; set; }
public double temp_max { get; set; }
public int pressure { get; set; }
public int sea_level { get; set; }
public int grnd_level { get; set; }
public int humidity { get; set; }
public double temp_kf { get; set; }
}
public class CityViewModel
{
public int id { get; set; }
public string? name { get; set; }
public Coord? coord { get; set; }
public string? country { get; set; }
public int population { get; set; }
public int timezone { get; set; }
public int sunrise { get; set; }
public int sunset { get; set; }
}
public class Clouds
{
public int all { get; set; }
}
public class Coord
{
public double lat { get; set; }
public double lon { get; set; }
}
public class ListViewModel
{
public ListViewModel()
{
this.weather = new List<Weather>();
}
public int dt { get; set; }
public Main main { get; set; }
public List<Weather> weather { get; set; }
public Clouds clouds { get; set; }
public Wind wind { get; set; }
public int visibility { get; set; }
public double pop { get; set; }
public Sys sys { get; set; }
public DateTime dt_txt { get; set; }
public Rain rain { get; set; }
public Snow snow { get; set; }
}
public class Rain
{
[JsonProperty("3h")]
public double _3h { get; set; }
}
public class Root
{
}
public class Snow
{
[JsonProperty("3h")]
public double _3h { get; set; }
}
public class Sys
{
public string? pod { get; set; }
}
public class Weather
{
public int id { get; set; }
public string? main { get; set; }
public string? description { get; set; }
public string? icon { get; set; }
}
public class Wind
{
public double speed { get; set; }
public int deg { get; set; }
public double gust { get; set; }
}
Что-то не так с кодом, который у вас есть? Я имею в виду, помимо того, что свойство Convert.ToDateTime
не нужно, потому что свойство dt_txt
уже является свойством DateTime
. Также возникает вопрос, что означает «средний пользователь» в наборе данных без пользователей...
Вот довольно простой ответ, и я объясню его ниже:
var result = response.list
.GroupBy(listViewModelElement => DateOnly.FromDateTime(new DateTime(listViewModelElement.dt_txt)))
.Select(dateGroup => new {
dt = dateGroup.Key,
TemperatureAverage = dateGroup.Average(x => x.main.temp),
// not sure how "Wind" class looks, so replace "SPEED" with whatever the correct prop name is
WindSpeedAverage = dateGroup.Average(x => x.wind.SPEED),
HumidityAverage = dateGroup.Average(x => x.main.humidity),
});
Примечание: не уверен насчет синтаксиса new DateTime(listViewModelElement.dt_txt)
, возможно, вам нужно помассировать строку, чтобы она работала.
Примечание 2. Помните о возможных проблемах с производительностью при использовании этого упрощенного подхода. Если ваш ввод (количество элементов в response.list
) очень велик, это не самое оптимальное решение. Если производительность становится реальной проблемой, необходимо добавить больше ручных операций вместо того, чтобы полагаться на LINQ
.
Так что же делает этот код?
Во-первых, как говорится в вопросе, мы группируем содержимое respose.list
по DateTime, построенному из значения свойства dt_txt
, с полностью удаленным временем. Это .GroupBy(listViewModelElement => DateOnly.FromDateTime(new DateTime(listViewModelElement.dt_txt)))
часть. DateOnly.FromDateTime
выполняет часть «удалить бит времени».
Это GroupBy
создает своего рода «группу». Думайте об этом как о наборе ключ-значение, один ко многим. В этом случае «ключ» для каждой группы — это значение DateTime
, а «значение» для каждой группы — это набор (IEnumerable
) значений ListViewModel
.
После этого для каждой полученной «группы» (имеется в виду каждая отдельная дата во всех значениях dt_txt
здесь) мы создаем новый анонимный объект из результатов. Это кусок внутри Select
лямбды:
new {
dt_datetime = dateGroup.Key,
TemperatureAverage = dateGroup.Average(x => x.main.temp),
WindSpeedAverage = dateGroup.Average(x => x.wind.SPEED),
HumidityAverage = dateGroup.Average(x => x.main.humidity),
}
Вы можете видеть, что dt_datetime
установлено значение свойства Key
(которое присутствует в «группировке», созданной GroupBy
), а другие значения рассчитываются из значений dateGroup
.
Последнее примечание: если вы планируете повторно использовать значения в своем коде, я бы предложил создать правильный класс для хранения результатов. Что-то подобное:
class WeatherAveragesResult {
public dt_datetime DateTime;
public TemperatureAverage float;
public WindSpeedAverage float;
public HumidityAverage float;
public WeatherAveragesResult(datdt_datetime DateTime, tempAvg float, windSpeedAvg float, humidityAvg float) { this.dt = date; ... }
}
И, возможно, создание общедоступного метода в классе ListViewModel
для удаления шаблона DateOnly.FromDateTime(new DateTime(listViewModelElement.dt_txt))
:
public class ListViewModel {
// ...
public DateTime GetDateValue() {
return DateOnly.FromDateTime(new DateTime(this.dt_txt));
}
// ...
}
И делать
var result = response.list
.GroupBy(listViewModelElement => listViewModelElement.GetDateValue())
.Select(dateGroup => new WeatherAveragesResult (
dateGroup.Key,
dateGroup.Average(x => x.main.temp),
dateGroup.Average(x => x.wind.SPEED),
dateGroup.Average(x => x.main.humidity))
);
я думаю, OP хочет сгруппировать по дням, а не покупать dt (в течение дня есть несколько точек dt, см. dt_text
опору в json)
Он считает 2 из приведенного ниже JSON, чтобы получить среднее значение для упомянутых выше полей — 6 точек данных в течение 2 дней => 2 новых средних точки данных.
Исправлено на dt_txt
дней.
Можете ли вы добавить определение вашего класса
WeatherReponseModel
?