/// <summary>
        /// Writes a single element record to the stream. This method does NOT write out the header row!
        /// </summary>
        /// <typeparam name="T">Type of business object</typeparam>
        /// <param name="instances">Instance of business object to write</param>
        /// <param name="writeHeader">We will write out the header ONLY if this is TRUE. Default: FALSE</param>
        public void WriteRecords <T>(IEnumerable <T> instances, bool writeHeader = false)
            where T : class, new()
        {
            ClassInfo objectInfo = TypeInspector.InspectOnlyMembersAnotated <T, FileFieldAttribute>();

            string[] headers = GetHeader <T>(objectInfo);

            if (writeHeader)
            {
                for (int h = 0; h < headers.Length; h++)
                {
                    string element = headers[h];
                    if (element != null)
                    {
                        writer.Write("\"");
                        writer.Write(element);
                        writer.Write("\"");
                    }
                    if (h < (headers.Length - 1))
                    {
                        writer.Write(DELIMITER);
                    }
                }
                writer.WriteLine();
            }

            foreach (T instance in instances)
            {
                WriteElements(GetRecord(instance, headers, objectInfo));
            }

            writer.Flush();
        }
        /// <summary>
        /// Writes a single element record to the stream. This method does NOT write out the header row!
        /// </summary>
        /// <typeparam name="T">Type of business object</typeparam>
        /// <param name="instance">Instance of business object to write</param>
        public void WriteRecord <T>(T instance)
            where T : class, new()
        {
            ClassInfo objectInfo = TypeInspector.InspectOnlyMembersAnotated <T, FileFieldAttribute>();

            string[] headers = GetHeader <T>(objectInfo);

            WriteElements(GetRecord(instance, headers, objectInfo));
        }
        /// <summary>
        /// Gets the record data as a string array
        /// </summary>
        /// <typeparam name="T">Type of business object</typeparam>
        /// <param name="instance">Instance of business object to turn into a record-array</param>
        /// <param name="headers">Headers for the object (NULL to auto-detect -- bad idea when fetching for multiple items)</param>
        /// <param name="objectInfo">Reflected object metadata (NULL to auto-detect -- bad idea when fetching for multiple items)</param>
        /// <returns>String array containing records</returns>
        public string[] GetRecord <T>(T instance, string[] headers = null, ClassInfo objectInfo = null)
            where T : class, new()
        {
            objectInfo ??= TypeInspector.InspectOnlyMembersAnotated <T, FileFieldAttribute>();
            headers ??= GetHeader <T>(objectInfo);

            string[] record = new string[objectInfo.Properties.Count + objectInfo.Fields.Count];
            foreach (PropertyInfo property in objectInfo.Properties)
            {
                object             objValue    = property.Read(instance);
                FileFieldAttribute ffAttribute = property.GetAttributes <FileFieldAttribute>().First();

                if (objValue == null)
                {
                    objValue = ffAttribute.NullValue;
                }
                else
                {
                    if (property.Type.IsEnum && (ffAttribute.UseEnumIntegerValue))
                    {
                        // by default, reflection would have given us the name (in objValue)
                        objValue = (int)objValue;
                    }
                    else if (property.Type == typeof(bool))
                    {
                        objValue = ((bool)objValue ? (ffAttribute.BooleanTrues?[0] ?? "true") : (ffAttribute.BooleanFalses?[0] ?? "false"));
                    }
                    else if (property.Type == typeof(bool?))
                    {
                        bool?b = (bool?)objValue;
                        if (b.HasValue)
                        {
                            objValue = (b.Value ? (ffAttribute.BooleanTrues?[0] ?? "true") : (ffAttribute.BooleanFalses?[0] ?? "false"));
                        }
                    }
                    else if (property.IsEnumerable)
                    {
                        string serialized = JsonSerializer.Serialize(
                            objValue, property.Type,
                            new JsonSerializerOptions()
                        {
                            Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
                        }
                            );

                        if ((serialized.Length > 0) && (serialized[0] == '['))
                        {
                            serialized = serialized[1..];
        /// <summary>
        /// Create the FIELD_PROPERTY_MAP
        /// </summary>
        /// <typeparam name="ClassT">Type of the class object we need to deserialize into</typeparam>
        private PropertyFieldMappingInfo[] MapHeaderToClass <ClassT>()
            where ClassT : class, new()
        {
            ClassInfo info = TypeInspector.InspectOnlyMembersAnotated <ClassT, FileFieldAttribute>();

            if (info == null)
            {
                throw new InvalidOperationException($"The type '{typeof(ClassT).Name}' contains no usable properties/fields. Ensure the properties/fields are decorated with the 'FileFieldAttribute' attribute.");
            }

            List <PropertyFieldMappingInfo> tempList = new List <PropertyFieldMappingInfo>();
            int maxColumnIndex = -1;

            for (int i = 0; i < info.Fields.Count; i++)
            {
                PropertyFieldMappingInfo map = new PropertyFieldMappingInfo(info.Fields[i]);
                if ((map.ColumnIndex < 0) && (HEADER != null))
                {
                    foreach (DataColumn col in HEADER.Columns)
                    {
                        if (col.Value == map.Attribute.Name)
                        {
                            map.ColumnIndex = col.Index;
                            break;
                        }
                    }
                }

                if (map.ColumnIndex >= 0)
                {
                    if (map.ColumnIndex > maxColumnIndex)
                    {
                        maxColumnIndex = map.ColumnIndex;
                    }

                    tempList.Add(map);
                }
            }
            for (int i = 0; i < info.Properties.Count; i++)
            {
                PropertyFieldMappingInfo map = new PropertyFieldMappingInfo(info.Properties[i]);
                if ((map.ColumnIndex < 0) && (HEADER != null))
                {
                    foreach (DataColumn col in HEADER.Columns)
                    {
                        if (col.Value == map.Attribute.Name)
                        {
                            map.ColumnIndex = col.Index;
                            break;
                        }
                    }
                }

                if (map.ColumnIndex >= 0)
                {
                    if (map.ColumnIndex > maxColumnIndex)
                    {
                        maxColumnIndex = map.ColumnIndex;
                    }

                    tempList.Add(map);
                }
            }

            if (tempList.Count == 0)
            {
                throw new InvalidOperationException($"The type '{typeof(ClassT).Name}' contains no usable properties/fields. Ensure the properties/fields are decorated with the 'FileFieldAttribute' attribute.");
            }

            PropertyFieldMappingInfo[] FIELD_PROPERTY_MAP = new PropertyFieldMappingInfo[maxColumnIndex + 1];
            foreach (PropertyFieldMappingInfo item in tempList)
            {
                FIELD_PROPERTY_MAP[item.ColumnIndex] = item;
            }

            return(FIELD_PROPERTY_MAP);
        }