Записать CSV из вложенного анонимного объекта

Вопрос или проблема

Мне не удалось написать 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"
        }
    };
}

Объяснение кода

  1. Цикл по ключам: В методе ExportRecordedData() мы проходим по всем ключам в словаре _recordedData. Для каждого ключа создаем файл CSV.

  2. Упрощение структуры: Метод FlattenSample использует конструкцию switch, чтобы определить тип объекта data. В зависимости от типа извлекаются соответствующие свойства и создается новый анонимный объект, объединяющий все необходимые атрибуты.

  3. Запись в CSV: После того как все записи были преобразованы в простой формат, новый список передается в метод WriteRecords для записи в файл CSV.

Заключение

С помощью предоставленного решения вы сможете успешно экспортировать ваши данные в CSV, включая все свойства вложенных структур. Если в будущем вы добавите новые типы данных, просто добавьте дополнительный случай в switch внутри метода FlattenSample. Это решение гибкое и может быть адаптировано под различные требования.

Если у вас есть дополнительные вопросы или требуется уточнение по коду, пожалуйста, дайте знать.

Оцените материал
Добавить комментарий

Капча загружается...