在 C# 中处理 CSV 输出的递归嵌套对象列表

Handling Recursive Nested Object Lists in C# for CSV Output

提问人:K Y 提问时间:9/27/2023 最后编辑:K Y 更新时间:9/27/2023 访问量:41

问:

我正在努力处理用于 CSV 导出的 C# 对象结构。此结构具有多层嵌套列表,其中一些可能是空的,并且它们可以是递归的。虽然我在下面展示了一个简化的模型,但我正在处理的实际结构要复杂得多。

下面是一个说明性表示:

class A
{
    public string Prop1 { get; set; }
    public string Prop2 { get; set; }
    public List<B> BItems { get; set; }
    public List<C> CItems { get; set; }
}

class B
{
    public string Name { get; set; }
    public string ID { get; set; }
    public D RelationshipType { get; set; }
    public List<B> SubBItems { get; set; } // Recursive list
}

class C
{
    public string Year { get; set; }
}

class D
{
    public string Label { get; set; }
    public string Value { get; set; }
}

请考虑以下示例数据:

AItems = [{    Prop1: "Code1",    Prop2: "Title1",    BItems: [        {            Name: "Name1",             ID: "ID1",             RelationshipType: {Label: "Label1", Value: "Value1"},            SubBItems: [                {Name: "SubName1", ID: "SubID1", RelationshipType: {Label: "SubLabel1", Value: "SubValue1"}}            ]
        },
        {Name: "Name2", ID: "ID2", RelationshipType: {Label: "Label2", Value: "Value2"}}
    ],
    CItems: [{Year: "Year1"}]
}, {
    Prop1: "Code2",
    Prop2: "Title2",
    BItems: [{Name: "Name1", ID: "ID1", RelationshipType: {Label: "Label1", Value: "Value1"}}],
    CItems: [{Year: "Year1"}, {Year: "Year2"}]
}];

预期的 CSV 输出为:

Prop1, Prop2, B_Name, B_ID, D_Label, D_Value, B_SubB_Name, B_SubB_ID, D_SubB_Label, D_SubB_Value, C_Year
Code1, Title1, Name1, ID1, Label1, Value1, SubName1, SubID1, SubLabel1, SubValue1, Year1
Code1, Title1, Name2, ID2, Label2, Value2, , , , , Year1
Code2, Title2, Name1, ID1, Label1, Value1, , , , , Year1
Code2, Title2, Name1, ID1, Label1, Value1, , , , , Year2

我正在尝试生成行,但我的方法正在努力适当地合并并行列表中的对象,尤其是相同深度的列表。

public static void GenerateRows(
            List<Dictionary<string, string>> records,
            Dictionary<string, string> currentRow,
            object obj,
            string prefix,
            HashSet<string> allHeaders)
        {
            if (obj == null) return;

            var type = obj.GetType();

            if (obj is IEnumerable && type != typeof(string))
            {
                foreach (var item in obj as IEnumerable)
                {
                    var clonedRow = new Dictionary<string, string>(currentRow);
                    GenerateRows(records, clonedRow, item, prefix, allHeaders);
                }
                return;
            }

            // Process properties for the current object
            ProcessProperties(records, currentRow, obj, prefix, allHeaders);
        }

        private static void ProcessProperties(
            List<Dictionary<string, string>> records,
            Dictionary<string, string> row,
            object obj,
            string prefix,
            HashSet<string> allHeaders)
        {
            var type = obj.GetType();

            // Create a list to temporarily hold generated rows from lists.
            List<Dictionary<string, string>> tempListRows = new List<Dictionary<string, string>>();

            foreach (var prop in type.GetProperties())
            {
                if (prop.CanRead && prop.GetIndexParameters().Length == 0)
                {
                    var val = prop.GetValue(obj);
                    var propName = string.IsNullOrEmpty(prefix) ? prop.Name : $"{prefix}_{prop.Name}";

                    if (val is string || val is ValueType)
                    {
                        row[propName] = val?.ToString();
                        allHeaders.Add(propName);
                    }
                    else if (val is IEnumerable && prop.PropertyType != typeof(string))
                    {
                        List<Dictionary<string, string>> newRows = new List<Dictionary<string, string>>();
                        foreach (var item in val as IEnumerable)
                        {
                            var newRow = new Dictionary<string, string>();
                            GenerateRows(newRows, newRow, item, propName, allHeaders);
                        }
                        tempListRows.AddRange(newRows);
                    }
                    else
                    {
                        GenerateRows(records, row, val, propName, allHeaders);
                    }
                }
            }

            // Merge rows
            if (tempListRows.Count == 0)
            {
                records.Add(row);
            }
            else
            {
                foreach (var tempRow in tempListRows)
                {
                    var mergedRow = new Dictionary<string, string>(row);
                    foreach (var kvp in tempRow)
                    {
                        mergedRow[kvp.Key] = kvp.Value;
                    }
                    records.Add(mergedRow);
                }
            }
        }

任何建议将不胜感激。

C# 嵌套列表

评论

0赞 Fildor 9/27/2023
用于 CSV 导出的 C# 对象结构。这种结构具有多层嵌套列表,有些可能是空的,它们可以是递归的。(我强调)这是矛盾的。如果它是用于 CSV,那么它一开始就是一个扁平结构。
1赞 Fildor 9/27/2023
即使没有自我引用,那也会很混乱(非常混乱),但可行。它基本上是一个交叉连接。但是自我引用需要周期检测......
1赞 Fildor 9/27/2023
我不在乎重复。它就是这样。只是 B 中的自我引用就打破了这一点。即使你设法成功地实现了周期检测,嵌套也不是(真的)有界的,所以这很快就会在你的脸上爆炸
1赞 K Y 9/27/2023
是的,它必须 100% 导出为 csv。实际上,我以前曾试图提及您的担忧,但他们拒绝改变观点。我真的很高兴找到一个理解我在说什么的人。
1赞 Fildor 9/27/2023
在某种程度上反击可能是值得的。即使它只是一个软要求(纸面上的协议=>规范):如果你能让“他们”达到 5 个嵌套级别,你至少会知道你最坏的情况......照原样,你最坏的情况(即使它在现实生活中从未发生过)必须被假定为“无穷大”。

答: 暂无答案