Skip to content

DynamicObject WriteDynamicHeader issue #2355

@ghost

Description

Describe the bug
CsvWriter.WriteRecords doesn't call WriteDynamicHeader

To Reproduce

using CsvHelper;
using CsvHelper.Configuration;
using System.Dynamic;
using System.Globalization;
using System.Text;

namespace DynCsv
{
    public class DynamicCsvRecord : DynamicObject
    {
        public string FirstFixedProp { get; set; }
        public string SecondFixedProp { get; set; }

        public override DynamicMetaObject GetMetaObject(System.Linq.Expressions.Expression parameter)
        {
            return base.GetMetaObject(parameter);
        }

        private Dictionary<string, object?> _Dict = new();

        public bool TryAdd(string key, object? value) => _Dict.TryAdd(key, value);

        public override IEnumerable<string> GetDynamicMemberNames()
        {
            List<string> names = new();

            foreach (var prop in GetType().GetProperties())
            {
                names.Add(prop.Name);
            }

            foreach (var key in _Dict.Keys)
            {
                names.Add(key);
            }

            return names;
        }

        public override bool TrySetMember(SetMemberBinder binder, object? value) => _Dict.TryAdd(binder.Name, value);

        public override bool TryGetMember(GetMemberBinder binder, out object? result)
        {
            result = _Dict[binder.Name];
            return true;
        }

    }

    public class Program
    {
        static void Main(string[] args)
        {
            List<DynamicCsvRecord> records = new();

            var rec = new DynamicCsvRecord();
            rec.FirstFixedProp = "1st Fixed Value";
            rec.SecondFixedProp = "2nd Fixed Value";
            rec.TryAdd("FirstDynamicProp", "1st Dynamic Value");
            rec.TryAdd("SecondDynamicProp", "2nd Dynamic Value");

            records.Add(rec);

            var config = new CsvConfiguration(CultureInfo.InvariantCulture)
            {
                HasHeaderRecord = true,
            };

            using (var stream = File.Open("Out.csv", FileMode.Create))
            using (var writer = new StreamWriter(stream, Encoding.UTF8))
            using (var csv = new CsvWriter(writer, config))
            {
                if (records?.ElementAtOrDefault(0) is IDynamicMetaObjectProvider provider)
                {
                    // unless I explicitly call this method, dynamic column names won't be written
                    csv.WriteDynamicHeader(provider);
                    csv.NextRecord();
                }

                csv.WriteRecords(records);
            }
        }
    }
}

Expected behavior
WriteDynamicHeader should be called from WriteRecords

Screenshots

Image

Additional context
I think the problem is in CsvWriter.cs at line 415-425. At line 425 WriteHeaderFromRecord(enumerator.Current) would call the WriteDynamicHeader(dynamicObject) but at line 415 WriteHeaderFromType<T>() writes the fixed members header and sets hasHeaderBeenWritten true, so WriteDynamicHeader won't be called from WriteHeaderFromRecord

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions