Вопрос или проблема
Мне не удалось написать CSV для следующей структуры, я уверен, что это должно быть возможно автоматически, но мне не хватает чего-то…
Переменная _recordedData является Словарем с Списком<RecordSample>, так как свойство данных RecordedSample для определенного ключа словаря всегда будет одинаковым.
Например, _recordedData[“eulerAngle”] вернет Список<RecordSample>, где свойство данных каждого RecordSample является структурой EulerAnglesMessage.
Но когда я пытаюсь экспортировать каждый Список<RecordSample> из словаря в методе ExportRecordedData(), результирующий csv-файл не содержит свойств структуры данных. Я не знаю, чего мне не хватает, чтобы это заработало…
Большое спасибо за вашу помощь!
private static Dictionary<string, List<RecordSample>> _recordedData = new Dictionary<string, List<RecordSample>>();
private class RecordSample
{
public double record_time_ms { get; set; } // Прошедшее время в мс с начала записи
public string record_id; // Строковый идентификатор для идентификации устройств сенсоров, на которых была сделана запись
public object data { get; set; } // Данные, которые записываются
}
public struct EulerAnglesMessage
{
public UInt64 timestamp { get; set; }
public float roll { get; set; }
public float pitch { get; set; }
public float yaw { get; set; }
}
public struct QuaternionMessage
{
public UInt64 timestamp { get; set; }
public float w { get; set; }
public float x { get; set; }
public float y { get; set; }
public float z { get; set; }
}
private static void ExportRecordedData()
{
foreach (string key in _recordedData.Keys)
{
string path = $"{export_path}\\{key}.csv";
using (var writer = new StreamWriter(path))
{
using (CsvWriter csv = new CsvWriter(writer, CultureInfo.InvariantCulture))
{
csv.WriteRecords(_recordedData[key]);
}
}
}
}
Чтобы сделать это, используя только CsvWriter
, не изменяя классы или что-то еще, вам нужно вручную написать заголовки и каждую строку. Но это не так сложно, как вы можете видеть в приведенном ниже примере кода:
using CsvHelper;
using System.Globalization;
const string export_path = "test.csv";
var data = new List<RecordData>();
data.Add(new RecordData());
data.Add(new RecordData());
data.Add(new RecordData());
using (var writer = new StreamWriter(export_path))
{
using (CsvWriter csv = new CsvWriter(writer, CultureInfo.InvariantCulture))
{
// Сначала пишем заголовок
csv.WriteHeader(data[0].GetType());
csv.WriteHeader(data[0].Value.GetType());
csv.NextRecord();
// Затем пишем каждую строку
foreach (var item in data)
{
csv.WriteRecord(item);
csv.WriteRecord(item.Value);
// Не забудьте начать новую запись в конце!
csv.NextRecord();
}
}
}
public class RecordData
{
public double Time { get; set; } = 42;
public string Id { get; set; } = Guid.NewGuid().ToString();
public object Value { get; set; } = new Data();
}
public class Data
{
public string Prop1 { get; set; } = Guid.NewGuid().ToString();
public string Prop2 { get; set; } = Guid.NewGuid().ToString();
}
Проблема в том, что когда вы пытаетесь записать данные словаря в CSV, CsvWriter видит только свойства RecordSample напрямую. Он не знает, как углубиться в свойство данных, которое может быть EulerAnglesMessage или QuaternionMessage.
Для исправления этого вам нужно “сплюснуть” структуру, то есть извлечь соответствующие поля из свойства данных и включить их при записи в CSV. Самый простой способ справиться с этим – преобразовать каждый RecordSample в более простой, плоский объект, который содержит все, что вы хотите в CSV.
Вот решение, которое должно сработать:
private static void ExportRecordedData()
{
foreach (var key in _recordedData.Keys)
{
var path = $"{export_path}\\{key}.csv";
using (var writer = new StreamWriter(path))
using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture))
{
var flattenedRecords = _recordedData[key]
.Select(sample => FlattenSample(sample))
.ToList();
csv.WriteRecords(flattenedRecords);
}
}
}
// Сплющивает RecordSample, извлекая поля данных в зависимости от его типа.
private static object FlattenSample(RecordSample sample)
{
return sample.data switch
{
EulerAnglesMessage euler => new
{
sample.record_time_ms,
sample.record_id,
DataType = "EulerAngles",
euler.timestamp,
euler.roll,
euler.pitch,
euler.yaw
},
QuaternionMessage quaternion => new
{
sample.record_time_ms,
sample.record_id,
DataType = "Quaternion",
quaternion.timestamp,
quaternion.w,
quaternion.x,
quaternion.y,
quaternion.z
},
_ => new
{
sample.record_time_ms,
sample.record_id,
DataType = "Unknown"
}
};
}
По сути, что мы здесь делаем:
Сплющивание данных:
Поскольку поле данных может содержать разные типы (такие как EulerAnglesMessage или QuaternionMessage), мы используем выражение switch, чтобы понять, с каким типом данных мы имеем дело. Как только мы знаем тип, мы извлекаем его отдельные поля (такие как roll, pitch или w, x и т. д.) и собираем все в анонимный объект.
Неизвестные данные:
Если в будущем вы когда-либо добавите другие типы сообщений, оператор switch вернется к основному объекту с “Неизвестным” как ТипомДанных.
Запись в CSV:
Теперь, когда каждый RecordSample преобразован в плоскую структуру, CsvWriter может справиться с этим легко и без проблем записать все в CSV.
Ответ или решение
Экспорт данных в CSV из вложенных анонимных объектов на C
В данном ответе мы рассмотрим, как правильно экспортировать данные из структуры, содержащей вложенные анонимные объекты, в формат CSV с использованием библиотеки CsvHelper. Вы описали, что сталкиваетесь с проблемой при попытке записать данные из Dictionary<string, List<RecordSample>>
, где каждый элемент данных имеет разные структуры (например, EulerAnglesMessage
и QuaternionMessage
).
Проблема
Проблема заключается в том, что CsvHelper по умолчанию может извлекать только непосредственно доступные свойства класса RecordSample
. Когда вы пытаетесь записать коллекцию RecordSample
, CsvWriter не знает, как получить доступ к свойствам вложенного объекта, находящегося в поле data
. Это и вызывает сложности при записи всех необходимых данных в CSV.
Решение
Чтобы успешно экспортировать данные, нам нужно "упростить" структуру, создавая новый объект, который включает все необходимые поля. Это можно сделать путем выбора свойств из RecordSample
и соответствующего типа данных, хранящегося в поле data
.
Примеры кода
Вот упрощенный код, который поможет вам экспортировать данные в CSV:
private static void ExportRecordedData()
{
foreach (var key in _recordedData.Keys)
{
var path = $"{export_path}\\{key}.csv";
using (var writer = new StreamWriter(path))
using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture))
{
var flattenedRecords = _recordedData[key]
.Select(sample => FlattenSample(sample))
.ToList();
csv.WriteRecords(flattenedRecords);
}
}
}
// Функция для "упрощения" RecordSample, извлекая данные в зависимости от их типа
private static object FlattenSample(RecordSample sample)
{
return sample.data switch
{
EulerAnglesMessage euler => new
{
sample.record_time_ms,
sample.record_id,
DataType = "EulerAngles",
euler.timestamp,
euler.roll,
euler.pitch,
euler.yaw
},
QuaternionMessage quaternion => new
{
sample.record_time_ms,
sample.record_id,
DataType = "Quaternion",
quaternion.timestamp,
quaternion.w,
quaternion.x,
quaternion.y,
quaternion.z
},
_ => new
{
sample.record_time_ms,
sample.record_id,
DataType = "Unknown"
}
};
}
Объяснение кода
-
Цикл по ключам: В методе
ExportRecordedData()
мы проходим по всем ключам в словаре_recordedData
. Для каждого ключа создаем файл CSV. -
Упрощение структуры: Метод
FlattenSample
использует конструкциюswitch
, чтобы определить тип объектаdata
. В зависимости от типа извлекаются соответствующие свойства и создается новый анонимный объект, объединяющий все необходимые атрибуты. -
Запись в CSV: После того как все записи были преобразованы в простой формат, новый список передается в метод
WriteRecords
для записи в файл CSV.
Заключение
С помощью предоставленного решения вы сможете успешно экспортировать ваши данные в CSV, включая все свойства вложенных структур. Если в будущем вы добавите новые типы данных, просто добавьте дополнительный случай в switch
внутри метода FlattenSample
. Это решение гибкое и может быть адаптировано под различные требования.
Если у вас есть дополнительные вопросы или требуется уточнение по коду, пожалуйста, дайте знать.