Skip to content

Simplify Ryaml_yoink() and preserve class attributes so handlers work with column.major = FALSE#164

Open
Copilot wants to merge 6 commits intomainfrom
copilot/fix-date-handling-as-yaml
Open

Simplify Ryaml_yoink() and preserve class attributes so handlers work with column.major = FALSE#164
Copilot wants to merge 6 commits intomainfrom
copilot/fix-date-handling-as-yaml

Conversation

Copy link
Copy Markdown

Copilot AI commented Feb 28, 2026

Ryaml_yoink() extracts single elements from data frame columns for row-major output but strips class attributes, so Date/POSIXt/factor handlers never match.

df <- data.frame(date = as.Date(c("2012-10-10", "2014-03-28")))
handler <- list(Date = function(x) as.character(x))

as.yaml(df, handlers = handler)                          # ✓ dates formatted
as.yaml(df, handlers = handler, column.major = FALSE)    # ✗ emits 15623.0, 16157.0

Changes

  • src/r_emit.c: Simplify Ryaml_yoink() by removing the factor-specific code path that converted factors from INTSXP to STRSXP inline. Instead, always extract the raw value and call copyMostAttrib(s_vec, s_tmp) unconditionally, preserving class, tzone, levels, and other attributes on extracted elements. For factors, the existing emit_factor() in emit_object() handles level resolution correctly on the preserved single-element factor.
  • tests/testthat/test-as_yaml.R: Add tests for Date, POSIXct, and factor columns with column.major = FALSE, including tests with custom handlers.
Original prompt

This section details on the original issue you should resolve

<issue_title>Unexpected interaction between column.major and Date handlers in as.yaml()</issue_title>
<issue_description>In as.yaml(), setting column.major=FALSE appears to prevent the application of Date handlers. For a while, I thought I was specifying my handler functions incorrectly, but I don't believe that is the case. I used these references for the handlers:

I've included a minimal example showing the difference in date handling when column.major=TRUE (default) vs. when column.major=FALSE. This interaction doesn't appear when using the verbatim_logical handler, making the Date behaviour entirely unexpected.

library(yaml)
library(purrr)

## Working date handling
date_handler <- list(Date = function(x) as.character(x))
my_list <- list(date = as.Date(c("2012-10-10", "2014-03-28")))
yaml::as.yaml(my_list, handlers = date_handler)
#> [1] "date:\n- '2012-10-10'\n- '2014-03-28'\n"
## instead of a list, use tibble and df (with column.major = TRUE by default)
my_tbl <- tibble::tibble(date = as.Date(c("2012-10-10", "2014-03-28")))
yaml::as.yaml(my_tbl, handlers = date_handler) |> cat()
#> date:
#> - '2012-10-10'
#> - '2014-03-28'
## but the date_handler doesn't apply with column.major = FALSE?
yaml::as.yaml(my_tbl, handlers = date_handler, column.major = FALSE) |> cat()
#> - date: 15623.0
#> - date: 16157.0

## Same issue with data.frames
my_df <- as.data.frame(my_tbl)
yaml::as.yaml(my_df, handlers = date_handler)
#> [1] "date:\n- '2012-10-10'\n- '2014-03-28'\n"
yaml::as.yaml(my_df, handlers = date_handler, column.major = FALSE)
#> [1] "- date: 15623.0\n- date: 16157.0\n"

## What if convert the tibble to list first?
# purrr::transpose also does the weird number conversion
purrr::transpose(my_tbl)
#> [[1]]
#> [[1]]$date
#> [1] 15623
#> 
#> 
#> [[2]]
#> [[2]]$date
#> [1] 16157
# but not if you do as.list() first
as.list(my_tbl) |>
    purrr::list_transpose(simplify = FALSE)
#> [[1]]
#> [[1]]$date
#> [1] "2012-10-10"
#> 
#> 
#> [[2]]
#> [[2]]$date
#> [1] "2014-03-28"

# what about logical handlers?
my_tbl$bool <- c(TRUE, FALSE)
verbatim_logical <- function(x) {
    result <- tolower(as.logical(x))
    class(result) <- "verbatim"
    return(result)
}
yml_handlers <- c(date_handler, list(logical = verbatim_logical))
# Logical handlers work fine either way, but dates depend on column.major
my_tbl |> yaml::as.yaml(handlers = yml_handlers, column.major = FALSE)
#> [1] "- date: 15623.0\n  bool: true\n- date: 16157.0\n  bool: false\n"
my_tbl |> yaml::as.yaml(handlers = yml_handlers, column.major = TRUE)
#> [1] "date:\n- '2012-10-10'\n- '2014-03-28'\nbool:\n- true\n- false\n"

Created on 2024-03-18 with reprex v2.0.2

Session info
sessioninfo::session_info()
#> ─ Session info ───────────────────────────────────────────────────────────────
#>  setting  value
#>  version  R version 4.3.1 (2023-06-16)
#>  os       macOS Ventura 13.4.1
#>  system   aarch64, darwin20
#>  ui       X11
#>  language (EN)
#>  collate  en_US.UTF-8
#>  ctype    en_US.UTF-8
#>  tz       Australia/Melbourne
#>  date     2024-03-18
#>  pandoc   3.1.7 @ /opt/homebrew/bin/ (via rmarkdown)
#> 
#> ─ Packages ───────────────────────────────────────────────────────────────────
#>  package     * version date (UTC) lib source
#>  cli           3.6.1   2023-03-23 [1] CRAN (R 4.3.0)
#>  digest        0.6.33  2023-07-07 [1] CRAN (R 4.3.0)
#>  evaluate      0.21    2023-05-05 [1] CRAN (R 4.3.0)
#>  fansi         1.0.4   2023-01-22 [1] CRAN (R 4.3.0)
#>  fastmap       1.1.1   2023-02-24 [1] CRAN (R 4.3.0)
#>  fs            1.6.3   2023-07-20 [1] CRAN (R 4.3.0)
#>  glue          1.6.2   2022-02-24 [1] CRAN (R 4.3.0)
#>  htmltools     0.5.6   2023-08-10 [1] CRAN (R 4.3.0)
#>  knitr         1.43    2023-05-25 [1] CRAN (R 4.3.0)
#>  lifecycle     1.0.3   2022-10-07 [1] CRAN (R 4.3.0)
#>  magrittr      2.0.3   2022-03-30 [1] CRAN (R 4.3.0)
#>  pillar        1.9.0   2023-03-22 [1] CRAN (R 4.3.0)
#>  pkgconfig     2.0.3   2019-09-22 [1] CRAN (R 4.3.0)
#>  purrr       * 1.0.2   2023-08-10 [1] CRAN (R 4.3.0)
#>  R.cache       0.16.0  2022-07-21 [1] CRAN (R 4.3.0)
#>  R.methodsS3   1.8.2   2022-06-13 [1] CRAN (R 4.3.0)
#>  R.oo          1.25.0  2022-06-12 [1] CRAN (R 4.3.0)
#>  R.utils       2.12.2  2022-11-11 [1] CRAN (R 4.3.0)
#>  reprex        2.0.2   2022-08-17 [1] CRAN (R 4.3.0)
#>  rlang         1.1.1   2023-04-28 [1] CRAN (R 4.3.0)
#>  rmarkdown     2.24    2023-08-14 [1] CRAN (R 4.3.0)
#>  sessioninfo   1.2.2   2021-12-06 [1] CRAN (R 4.3.0)
#>  styler        1.10.1  2023-06-05 [1] CRAN (R 4.3.0)...

</details>



<!-- START COPILOT CODING AGENT SUFFIX -->

- Fixes r-lib/yaml#141

<!-- START COPILOT CODING AGENT TIPS -->
---Let Copilot coding agent [set things up for you](https://github.com/r-lib/yaml/issues/new?title=+Set+up+Copilot+instructions&body=Configure%20instructions%20for%20this%20repository%20as%20documented%20in%20%5BBest%20practices%20for%20Copilot%20coding%20agent%20in%20your%20repository%5D%28https://gh.io/copilot-coding-agent-tips%29%2E%0A%0A%3COnboard%20this%20repo%3E&assignees=copilot) — coding agent works faster and does higher quality work when set up for your repo.

In Ryaml_yoink(), use copyMostAttrib() to preserve class attributes
when extracting single elements from data frame columns for row-major
output. This ensures Date and POSIXt handlers are triggered correctly.

Fixes #161

Co-authored-by: krlmlr <1741643+krlmlr@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix unexpected interaction between column.major and Date handlers Preserve class attributes in Ryaml_yoink() so handlers work with column.major = FALSE Feb 28, 2026
@krlmlr
Copy link
Copy Markdown
Member

krlmlr commented Feb 28, 2026

@copilot Why are factors treated specially? Add tests.

…trib

Remove the factor-specific code path that converted factors to STRSXP
in Ryaml_yoink(). Instead, always copy the integer value and call
copyMostAttrib() unconditionally. The existing emit_factor() in
emit_object() handles factor resolution correctly.

This ensures factor handlers work with column.major = FALSE, just
like Date and POSIXt handlers. Added tests for factor columns.

Co-authored-by: krlmlr <1741643+krlmlr@users.noreply.github.com>
Copy link
Copy Markdown
Author

Copilot AI commented Feb 28, 2026

@copilot Why are factors treated specially? Add tests.

Good point — there's no reason to special-case factors. Removed the factor-specific code path entirely in 6175d25. Ryaml_yoink() now always extracts the raw value and calls copyMostAttrib() unconditionally. For factors, this preserves the "factor" class and "levels" attribute on the yoinked INTSXP element, so emit_object()'s existing emit_factor() handles resolution correctly. Added tests for factor columns with and without handlers using column.major = FALSE.

Copilot AI changed the title Preserve class attributes in Ryaml_yoink() so handlers work with column.major = FALSE Simplify Ryaml_yoink() and preserve class attributes so handlers work with column.major = FALSE Feb 28, 2026
@krlmlr krlmlr marked this pull request as ready for review March 3, 2026 08:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants