Title
csvDelimiter / project CSV delimiter not consistently respected in exports and imports (exportRecordsTyped, exportProjectInformation, exportDags, importDags, writeDataForImport)
Summary
When the REDCap project/user setting "Delimiter for CSV file downloads" is changed from the default comma (,) to semicolon (;) (or potentially other supported delimiters), several redcapAPI functions start to fail or misbehave:
exportRecordsTyped() (and related export helpers)
exportProjectInformation()
exportDags()
importDags()
writeDataForImport() (used by multiple import functions)
The core problem appears to be that the package:
- Does not consistently pass the desired
csvDelimiter to the API in body, and
- Always parses/creates CSV with comma, regardless of the project/user delimiter.
This leads to parsing errors on exports and HTTP 400 responses on imports when the project delimiter is set to ;.
Environment
- Package:
redcapAPI
- REDCap: 14.x (exact version probably not critical, behaviour is tied to the CSV delimiter setting)
- R: recent 4.x
- REDCap project setting:
Control Center → User Settings → "Delimiter for CSV file downloads" = ;
Minimal example (exports)
-
In REDCap, set:
Control Center → User Settings → "Delimiter for CSV file downloads" = semicolon ( ; )
-
In R:
library(redcapAPI)
rcon <- redcapConnection(
url = "https://your.redcap.server/api/",
token = "…"
)
# This may error or produce malformed data when delimiter is ';'
dat <- exportRecordsTyped(rcon = rcon, forms = "some_form")
Observed issues:
- In some cases:
Error in rbind(deparse.level, ...) : numbers of columns of arguments do not match
- In other cases, fields / columns are misaligned or missing.
Root cause (from reading the code):
exportRecords* and related helpers rely on makeApiCall() returning a CSV string, then use read.csv / as.data.frame.response without specifying sep.
- When the REDCap project is configured to use
; as delimiter, the API returns semicolon-separated CSV, but read.csv still assumes sep = ",", causing incorrect parsing.
Minimal example (projectInformation)
With the same project setting (; as delimiter):
rcon <- redcapConnection(url = "…", token = "…")
# Triggers exportProjectInformation.redcapApiConnection()
pi <- rcon$projectInformation()
# Somewhere later in the code:
if (rcon$projectInformation()$is_longitudinal == 0) {
...
}
Observed error:
Error in if (rcon$projectInformation()$is_longitudinal == 0) { :
argument is of length zero
Root cause:
-
exportProjectInformation.redcapApiConnection() currently does:
body <- list(content = "project",
format = "csv",
returnFormat = "csv")
as.data.frame(makeApiCall(rcon, body, ...))
-
as.data.frame.response() uses read.csv(text = mapped, ...) with the default comma separator.
-
When the API returns semicolon-separated CSV, the entire header line is treated as one column; there is no is_longitudinal column, hence the argument is of length zero error.
Minimal example (DAG import)
Again with project delimiter set to ;:
rcon <- redcapConnection(url = "…", token = "…")
new_data_access_groups <- data.frame(
data_access_group_name = "AXS_2025-12-17 11:09:17.164299",
unique_group_name = NA_character_,
stringsAsFactors = FALSE
)
redcapAPI::importDags(rcon = rcon, data = new_data_access_groups)
Observed behaviour:
- With comma delimiter, the call succeeds.
- With semicolon delimiter, the underlying API call returns HTTP 400.
The data frame is fine (checked via str() / inspection), so the issue is in how the CSV payload is constructed.
Root cause:
-
importDags.redcapApiConnection() does:
body <- list(content = "dag",
action = "import",
format = "csv",
returnFormat = "csv",
data = writeDataForImport(data))
-
writeDataForImport() currently does:
writeDataForImport <- function(data){
coll <- checkmate::makeAssertCollection()
checkmate::assert_data_frame(x = data, add = coll)
checkmate::reportAssertions(coll)
output <-
utils::capture.output(
utils::write.csv(data,
file = "",
na = "",
row.names = FALSE)
)
paste0(output, collapse = " ")
}
-
write.csv() is hard-coded to use , as separator (it ignores any sep argument).
-
When the REDCap project is configured to use ; as CSV delimiter, it appears REDCap applies this also to DAG imports and expects semicolon-separated input, but gets comma-separated CSV → HTTP 400.
Proposed changes (backwards compatible)
1. Connection-level CSV delimiter
Introduce a delimiter state on the redcapApiConnection object with a small set of helper methods:
rcon$csv_delimiter() → returns one of c(",", " ", ";", "|", "^"), default ",".
rcon$set_csv_delimiter(d) → validates and sets the delimiter.
rcon$csv_delimiter_api() → maps the delimiter to the API value ("comma", "semicolon", "tab", "pipe", "caret").
Example helper:
getCsvDelimiterAPI <- function(csv_delimiter) {
switch(
csv_delimiter,
"," = "comma",
" " = "tab",
";" = "semicolon",
"|" = "pipe",
"^" = "caret",
"comma"
)
}
Then in redcapConnection():
- add private state variables
csv_delim and csv_delim_api,
- expose them via
csv_delimiter(), set_csv_delimiter(), csv_delimiter_api().
That allows user code to do:
rcon <- redcapConnection(url = "…", token = "…")
rcon$set_csv_delimiter(";") # or keep default ","
independent of REDCap’s UI/user setting.
2. Use the connection’s delimiter in CSV-based exports
For all exported CSV that the package parses back (e.g. content = "project", "dag", "record", "exportFieldNames"), pass the API delimiter and parse with the same delimiter, e.g.:
body <- list(content = "project",
format = "csv",
returnFormat = "csv",
csvDelimiter = rcon$csv_delimiter_api())
project_response <- makeApiCall(rcon, body, ...)
as.data.frame(project_response, sep = rcon$csv_delimiter())
Similarly for:
exportDags.redcapApiConnection()
exportProjectInformation.redcapApiConnection()
exportRecords.redcapApiConnection()
.exportFieldNamesApiCall() (internal helper used e.g. for imports)
This makes request and parsing consistent for any supported delimiter.
3. Make writeDataForImport() delimiter-aware
Change writeDataForImport() to accept a csv_delimiter argument (default ",") and use write.table() instead of write.csv():
writeDataForImport <- function(data, csv_delimiter = ","){
coll <- checkmate::makeAssertCollection()
checkmate::assert_data_frame(x = data, add = coll)
checkmate::reportAssertions(coll)
output <-
utils::capture.output(
utils::write.table(data,
file = "",
sep = csv_delimiter,
na = "",
row.names = FALSE,
col.names = TRUE,
qmethod = "double")
)
paste0(output, collapse = " ")
}
Then, in importDags.redcapApiConnection() (and any other import* that relies on writeDataForImport()), pass the connection delimiter:
body <- list(content = "dag",
action = "import",
format = "csv",
returnFormat = "csv",
data = writeDataForImport(data, csv_delimiter = rcon$csv_delimiter()))
This keeps the default behaviour (comma) for all existing code and makes it possible to use semicolon (or other) consistently when desired.
Why this matters
-
Right now, changing the project/user CSV delimiter setting in REDCap can silently break redcapAPI for some functions, particularly:
exportRecordsTyped() and related helpers,
projectInformation() / exportProjectInformation(),
exportDags() / importDags().
-
The package already mentions csvDelimiter usage in the documentation and examples (e.g. exporting with pipe "|"), but the handling is not consistently wired through all helpers and parsing logic.
-
A connection-level CSV delimiter plus consistent usage in exports and imports would:
- be fully backwards compatible (default comma),
- decouple
redcapAPI behaviour from the end-user GUI setting in REDCap,
- and align the package with REDCap’s own
csvDelimiter API parameter.
Title
csvDelimiter/ project CSV delimiter not consistently respected in exports and imports (exportRecordsTyped, exportProjectInformation, exportDags, importDags, writeDataForImport)Summary
When the REDCap project/user setting "Delimiter for CSV file downloads" is changed from the default comma (
,) to semicolon (;) (or potentially other supported delimiters), severalredcapAPIfunctions start to fail or misbehave:exportRecordsTyped()(and related export helpers)exportProjectInformation()exportDags()importDags()writeDataForImport()(used by multiple import functions)The core problem appears to be that the package:
csvDelimiterto the API inbody, andThis leads to parsing errors on exports and HTTP 400 responses on imports when the project delimiter is set to
;.Environment
redcapAPIControl Center → User Settings → "Delimiter for CSV file downloads" =
;Minimal example (exports)
In REDCap, set:
In R:
Observed issues:
Error in rbind(deparse.level, ...) : numbers of columns of arguments do not matchRoot cause (from reading the code):
exportRecords*and related helpers rely onmakeApiCall()returning a CSV string, then useread.csv/as.data.frame.responsewithout specifyingsep.;as delimiter, the API returns semicolon-separated CSV, butread.csvstill assumessep = ",", causing incorrect parsing.Minimal example (projectInformation)
With the same project setting (
;as delimiter):Observed error:
Root cause:
exportProjectInformation.redcapApiConnection()currently does:as.data.frame.response()usesread.csv(text = mapped, ...)with the default comma separator.When the API returns semicolon-separated CSV, the entire header line is treated as one column; there is no
is_longitudinalcolumn, hence theargument is of length zeroerror.Minimal example (DAG import)
Again with project delimiter set to
;:Observed behaviour:
The data frame is fine (checked via
str()/ inspection), so the issue is in how the CSV payload is constructed.Root cause:
importDags.redcapApiConnection()does:writeDataForImport()currently does:write.csv()is hard-coded to use,as separator (it ignores anysepargument).When the REDCap project is configured to use
;as CSV delimiter, it appears REDCap applies this also to DAG imports and expects semicolon-separated input, but gets comma-separated CSV → HTTP 400.Proposed changes (backwards compatible)
1. Connection-level CSV delimiter
Introduce a delimiter state on the
redcapApiConnectionobject with a small set of helper methods:rcon$csv_delimiter()→ returns one ofc(",", " ", ";", "|", "^"), default",".rcon$set_csv_delimiter(d)→ validates and sets the delimiter.rcon$csv_delimiter_api()→ maps the delimiter to the API value ("comma","semicolon","tab","pipe","caret").Example helper:
Then in
redcapConnection():csv_delimandcsv_delim_api,csv_delimiter(),set_csv_delimiter(),csv_delimiter_api().That allows user code to do:
independent of REDCap’s UI/user setting.
2. Use the connection’s delimiter in CSV-based exports
For all exported CSV that the package parses back (e.g.
content = "project","dag","record","exportFieldNames"), pass the API delimiter and parse with the same delimiter, e.g.:Similarly for:
exportDags.redcapApiConnection()exportProjectInformation.redcapApiConnection()exportRecords.redcapApiConnection().exportFieldNamesApiCall()(internal helper used e.g. for imports)This makes request and parsing consistent for any supported delimiter.
3. Make
writeDataForImport()delimiter-awareChange
writeDataForImport()to accept acsv_delimiterargument (default",") and usewrite.table()instead ofwrite.csv():Then, in
importDags.redcapApiConnection()(and any otherimport*that relies onwriteDataForImport()), pass the connection delimiter:This keeps the default behaviour (comma) for all existing code and makes it possible to use semicolon (or other) consistently when desired.
Why this matters
Right now, changing the project/user CSV delimiter setting in REDCap can silently break
redcapAPIfor some functions, particularly:exportRecordsTyped()and related helpers,projectInformation()/exportProjectInformation(),exportDags()/importDags().The package already mentions
csvDelimiterusage in the documentation and examples (e.g. exporting with pipe"|"), but the handling is not consistently wired through all helpers and parsing logic.A connection-level CSV delimiter plus consistent usage in exports and imports would:
redcapAPIbehaviour from the end-user GUI setting in REDCap,csvDelimiterAPI parameter.