@@ -804,6 +804,38 @@ export class Common {
804804 . replace ( new RegExp ( `[${ CONSTANTS . COMPLEX_FIELDS_QUERY_SEPARATOR } ]` , 'g' ) , CONSTANTS . COMPLEX_FIELDS_SEPARATOR ) ;
805805 }
806806
807+ /**
808+ * Normalizes CSV-like record values using the same null-token and type casting rules as CSV input.
809+ *
810+ * @param records - Records to normalize.
811+ * @param columnToColumnDataTypeMap - Optional column-to-type mapping.
812+ * @param preserveRawNullTokens - Keeps null tokens as raw string values when true.
813+ * @returns Normalized record copies.
814+ */
815+ public static normalizeCsvRecordValues (
816+ records : Array < Record < string , unknown > > ,
817+ columnToColumnDataTypeMap ?: Map < string , string > ,
818+ preserveRawNullTokens = false
819+ ) : Array < Record < string , unknown > > {
820+ if ( ! records . length ) {
821+ return records ;
822+ }
823+ const normalizedColumnDataTypeMap = Common . _normalizeCsvColumnDataTypeMap ( columnToColumnDataTypeMap ) ;
824+ return records . map ( ( record ) => {
825+ const normalized : Record < string , unknown > = { } ;
826+ Object . keys ( record ) . forEach ( ( columnName ) => {
827+ normalized [ columnName ] = Common . _normalizeCsvCellValue (
828+ record [ columnName ] ,
829+ columnName ,
830+ columnToColumnDataTypeMap ,
831+ normalizedColumnDataTypeMap ,
832+ preserveRawNullTokens
833+ ) ;
834+ } ) ;
835+ return normalized ;
836+ } ) ;
837+ }
838+
807839 /**
808840 * Reads CSV file from the disk.
809841 *
@@ -824,57 +856,27 @@ export class Common {
824856 const readDelimiter = useInternalCsvFormat ? Common . INTERNAL_CSV_FILE_DELIMITER : Common . csvReadFileDelimiter ;
825857 const readEncoding = useInternalCsvFormat ? Common . INTERNAL_CSV_FILE_ENCODING : Common . csvFileEncoding ;
826858 const shouldStripUtf8Bom = useInternalCsvFormat || ( Common . csvUseUtf8Bom && Common . _isUtf8Encoding ( readEncoding ) ) ;
827- const normalizedColumnDataTypeMap = new Map < string , string > ( ) ;
828- columnToColumnDataTypeMap ?. forEach ( ( fieldType , columnName ) => {
829- const normalizedName = columnName . trim ( ) . toLowerCase ( ) ;
830- if ( ! normalizedName ) {
831- return ;
832- }
833- if ( ! normalizedColumnDataTypeMap . has ( normalizedName ) ) {
834- normalizedColumnDataTypeMap . set ( normalizedName , fieldType ) ;
835- }
836- } ) ;
859+ const normalizedColumnDataTypeMap = Common . _normalizeCsvColumnDataTypeMap ( columnToColumnDataTypeMap ) ;
837860 function csvCast ( value : string , context : CastingContext ) : unknown {
838861 if ( context . header || typeof context . column === 'undefined' ) {
839862 return value ;
840863 }
841864
842- const rawValue = typeof value === 'string' ? value : String ( value ?? '' ) ;
843- const normalizedRawValue = rawValue . replace ( / \r + $ / g, '' ) ;
844- const trimmedValue = normalizedRawValue . trim ( ) ;
845- if ( ! trimmedValue . length ) {
846- if ( preserveRawNullTokens ) {
847- return normalizedRawValue ;
848- }
849- return Common . csvInsertNulls ? null : '' ;
850- }
851-
852- if ( ! preserveRawNullTokens && Common . _isCsvNullToken ( trimmedValue ) ) {
853- return null ;
854- }
855-
856865 const columnName = typeof context . column === 'string' ? context . column : String ( context . column ) ;
857- const fieldType = resolveFieldType ( columnName ) ?. toLowerCase ( ) ;
858- if ( ! fieldType ) {
859- return normalizedRawValue ;
860- }
861-
862- return Common . _castCsvValueByFieldType ( normalizedRawValue , trimmedValue , fieldType ) ;
866+ return Common . _normalizeCsvCellValue (
867+ value ,
868+ columnName ,
869+ columnToColumnDataTypeMap ,
870+ normalizedColumnDataTypeMap ,
871+ preserveRawNullTokens
872+ ) ;
863873 }
864874
865875 function resolveColumns ( header : string [ ] ) : Array < string | undefined > {
866876 void columnToColumnDataTypeMap ;
867877 return header ;
868878 }
869879
870- function resolveFieldType ( columnName : string ) : string | undefined {
871- const directFieldType = columnToColumnDataTypeMap ?. get ( columnName ) ;
872- if ( directFieldType ) {
873- return directFieldType ;
874- }
875- return normalizedColumnDataTypeMap . get ( columnName . trim ( ) . toLowerCase ( ) ) ;
876- }
877-
878880 return new Promise < Array < Record < string , unknown > > > ( ( resolve , reject ) => {
879881 if ( ! fs . existsSync ( filePath ) ) {
880882 resolve ( [ ] ) ;
@@ -2074,6 +2076,89 @@ export class Common {
20742076 return this . _CSV_NULL_TOKENS . has ( value . trim ( ) . toLowerCase ( ) ) ;
20752077 }
20762078
2079+ /**
2080+ * Normalizes column type map keys for case-insensitive lookups.
2081+ *
2082+ * @param columnToColumnDataTypeMap - Source column-to-type map.
2083+ * @returns Normalized type map.
2084+ */
2085+ private static _normalizeCsvColumnDataTypeMap ( columnToColumnDataTypeMap ?: Map < string , string > ) : Map < string , string > {
2086+ const normalizedColumnDataTypeMap = new Map < string , string > ( ) ;
2087+ columnToColumnDataTypeMap ?. forEach ( ( fieldType , columnName ) => {
2088+ const normalizedName = columnName . trim ( ) . toLowerCase ( ) ;
2089+ if ( ! normalizedName ) {
2090+ return ;
2091+ }
2092+ if ( ! normalizedColumnDataTypeMap . has ( normalizedName ) ) {
2093+ normalizedColumnDataTypeMap . set ( normalizedName , fieldType ) ;
2094+ }
2095+ } ) ;
2096+ return normalizedColumnDataTypeMap ;
2097+ }
2098+
2099+ /**
2100+ * Normalizes a CSV-like cell value using null-token and type casting rules.
2101+ *
2102+ * @param value - Raw value.
2103+ * @param columnName - Column name.
2104+ * @param columnToColumnDataTypeMap - Optional direct column type map.
2105+ * @param normalizedColumnDataTypeMap - Normalized column type map.
2106+ * @param preserveRawNullTokens - Keeps null tokens as raw string values when true.
2107+ * @returns Normalized value.
2108+ */
2109+ private static _normalizeCsvCellValue (
2110+ value : unknown ,
2111+ columnName : string ,
2112+ columnToColumnDataTypeMap ?: Map < string , string > ,
2113+ normalizedColumnDataTypeMap : Map < string , string > = new Map < string , string > ( ) ,
2114+ preserveRawNullTokens = false
2115+ ) : unknown {
2116+ const rawValue = typeof value === 'string' ? value : String ( value ?? '' ) ;
2117+ const normalizedRawValue = rawValue . replace ( / \r + $ / g, '' ) ;
2118+ const trimmedValue = normalizedRawValue . trim ( ) ;
2119+ if ( ! trimmedValue . length ) {
2120+ if ( preserveRawNullTokens ) {
2121+ return normalizedRawValue ;
2122+ }
2123+ return Common . csvInsertNulls ? null : '' ;
2124+ }
2125+
2126+ if ( ! preserveRawNullTokens && Common . _isCsvNullToken ( trimmedValue ) ) {
2127+ return null ;
2128+ }
2129+
2130+ const fieldType = Common . _resolveCsvColumnFieldType (
2131+ columnName ,
2132+ columnToColumnDataTypeMap ,
2133+ normalizedColumnDataTypeMap
2134+ ) ?. toLowerCase ( ) ;
2135+ if ( ! fieldType ) {
2136+ return normalizedRawValue ;
2137+ }
2138+
2139+ return Common . _castCsvValueByFieldType ( normalizedRawValue , trimmedValue , fieldType ) ;
2140+ }
2141+
2142+ /**
2143+ * Resolves a CSV column type by exact or normalized column name.
2144+ *
2145+ * @param columnName - Column name.
2146+ * @param columnToColumnDataTypeMap - Optional direct column type map.
2147+ * @param normalizedColumnDataTypeMap - Normalized column type map.
2148+ * @returns Field type when found.
2149+ */
2150+ private static _resolveCsvColumnFieldType (
2151+ columnName : string ,
2152+ columnToColumnDataTypeMap ?: Map < string , string > ,
2153+ normalizedColumnDataTypeMap : Map < string , string > = new Map < string , string > ( )
2154+ ) : string | undefined {
2155+ const directFieldType = columnToColumnDataTypeMap ?. get ( columnName ) ;
2156+ if ( directFieldType ) {
2157+ return directFieldType ;
2158+ }
2159+ return normalizedColumnDataTypeMap . get ( columnName . trim ( ) . toLowerCase ( ) ) ;
2160+ }
2161+
20772162 /**
20782163 * Casts a CSV cell value based on field metadata type.
20792164 *
0 commit comments