Nested objects list is not working for GetRecords
Describe the bug
Nested objects list is not working for GetRecords
error: Property 'System.String Id' is not defined for type 'System.Collections.Generic.List`1[csvHelperTest.Program+B]' (Parameter 'property')
To Reproduce run this code:
using System.IO;
using System.Linq;
using CsvHelper.Configuration;
using CsvHelper;
using System.Globalization;
using System.Collections.Generic;
namespace csvHelperTest
{
internal class Program
{
static void Main(string[] args)
{
using (var stream = new MemoryStream())
using (var reader = new StreamReader(stream))
using (var writer = new StreamWriter(stream))
using (var csv = new CsvReader(reader, CultureInfo.InvariantCulture))
{
csv.Context.RegisterClassMap<AMap>();
csv.Context.RegisterClassMap<BMap>();
List<A> listA = new List<A>();
A a1 = new A() { Id = "a1" };
a1.B = new List<B>();
a1.B.Add(new B() { Id = "b1" });
listA.Add(a1);
writer.WriteLine("AId,BId");
foreach (var a in listA)
{
foreach (var b in a.B)
{
writer.WriteLine(a);
writer.WriteLine(b);
}
}
writer.Flush();
stream.Position = 0;
var list = csv.GetRecords<A>().ToList();
for (var i = 0; i < 4; i++)
{
var rowId = i + 1;
var row = list[i];
System.Console.WriteLine(row);
}
System.Console.ReadLine();
}
}
private class A
{
public string Id { get; set; }
public List<B> B { get; set; }
}
private class B
{
public string Id { get; set; }
}
private sealed class AMap : ClassMap<A>
{
public AMap()
{
Map(m => m.Id).Name("AId");
References<BMap>(m => m.B);
}
}
private sealed class BMap : ClassMap<B>
{
public BMap()
{
Map(m => m.Id).Name("BId");
}
}
}
}
Expected behavior
GetRecords without errors I will see the filled object with nested list
Additional context I saw the similar issue there and I think it is still not working.
A year later, Is this still not working? I am also unable to serialize a list navigation property when calling GetRecords. Same exception is thrown. Thanks.
Unfortunately, you are not able to use nested lists of objects. CSV does not lend itself to flattening lists of objects. @LasVegasIs example is way too simple. It gets much more complicated when you have classes with more properties and multiple levels of nesting.
You can have an A class with a List of primitives like List<int> or List<string>.
private class A
{
public string Id { get; set; }
public List<string> B { get; set; }
}
private sealed class AMap : ClassMap<A>
{
public AMap()
{
Map(m => m.Id).Name("AId");
Map(m => m.B).Index(1);
}
}
void Main()
{
var input = @"AId,B1,B2,B3
a1,b1,b2,b3
a2,b4,b5,b6";
using var reader = new StringReader(input);
using var csv = new CsvReader(reader, CultureInfo.InvariantCulture);
csv.Context.RegisterClassMap<AMap>();
var records = csv.GetRecords<A>().ToList();
records.Dump();
}
You can have a single B class in your A class
private class A
{
public string Id { get; set; }
public B B { get; set; }
}
private class B
{
public string Id { get; set; }
public string Name { get; set; }
}
private sealed class AMap : ClassMap<A>
{
public AMap()
{
Map(m => m.Id).Name("A_Id");
References<BMap>(m => m.B);
}
}
private sealed class BMap : ClassMap<B>
{
public BMap()
{
Map(m => m.Id).Name("B_Id");
Map(m => m.Name).Name("B_Name");
}
}
void Main()
{
var input = @"A_Id,B_Id,B_Name
a1,b1,Bee 1
a2,b2,Bee 2";
using var reader = new StringReader(input);
using var csv = new CsvReader(reader, CultureInfo.InvariantCulture);
csv.Context.RegisterClassMap<AMap>();
var records = csv.GetRecords<A>().ToList();
records.Dump();
}
If you know that B is simple and there is a set number of B, you could manually read List<B>.
private class A
{
public string Id { get; set; }
public string Name { get; set; }
public List<B> B { get; set; }
}
private class B
{
public string Id { get; set; }
public string Name { get; set; }
}
private sealed class AMap : ClassMap<A>
{
public AMap()
{
Map(m => m.Id).Name("A_Id");
Map(m => m.Name).Name("A_Name");
}
}
void Main()
{
var input = @"A_Id,A_Name,B_Id1,B_Name1,B_Id2,B_Name2
a1,Alpha 1,b1,Bee 1,b2,Bee 2
a2,Alpha 2,b3,Bee 3,b4,Bee 4";
using var reader = new StringReader(input);
using var csv = new CsvReader(reader, CultureInfo.InvariantCulture);
csv.Context.RegisterClassMap<AMap>();
csv.Read();
csv.ReadHeader();
var records = new List<A>();
while (csv.Read())
{
var record = csv.GetRecord<A>();
record.B = new List<B>
{
new B {
Id = csv.GetField("B_Id1"),
Name = csv.GetField("B_Name1")
},
new B {
Id = csv.GetField("B_Id2"),
Name = csv.GetField("B_Name2")
}
};
records.Add(record);
}
records.Dump();
}
Thanks @AltruCoder, I will give this a try. However in my case the main type A isn't known at compile time. I am using reflection to invoke the generic method that kicks off the read. Type B in my case is a constant from a base class. Your example is a good starting point for me though.