R/construct_serialize.R

Defines functions construct_serialize

Documented in construct_serialize

#' Build code to recreate an object from its serialized form
#'
#' `construct_serialize()` creates code that reconstructs an R object by serializing
#' it to raw bytes and then calling `unserialize()`. This is a fallback method that
#' works for almost any R object, including those that are difficult or impossible
#' to construct idiomatically.
#'
#' @details
#'
#' This function generates code in the form:
#' ```r
#' unserialize(as.raw(c(
#'   # --- HEADER ---
#'   c(0x58, 0x0a, ...),
#'   # --- DATA ---
#'   c(0x00, 0x00, ...)
#' )))
#' ```
#'
#' The generated code includes detailed comments explaining the binary structure,
#' making it useful for understanding R's serialization format.
#'
#' ## Limitations
#'
#' `construct_serialize()` reproduces faithfully what `serialize()` does, so it
#' shares its limitations. Some objects don't survive serialization or can be
#' restored only in some conditions:
#' - **External pointers**: The address is lost, we get a null pointer
#' - **Weak references**: The key and value are lost
#' - **Connections**: Only the connection number is stored, the reconstructed
#'   object is valid only in the same session, as long as the connection is open
#' - **Namespaces and package environments**: They are stored by name, so the
#'   package must be available when the code is run
#'
#' The function is designed for small objects, every byte is documented so the
#' output is long and large objects (hundreds of kilobytes once serialized) are
#' slow to process. Note that functions defined in a file keep a reference to
#' the whole file through their srcref, use `utils::removeSource()` to get rid of it.
#'
#' @param x An R object to serialize and reconstruct.
#' @param collapse_header Logical. If `TRUE`, the serialization header is displayed
#'   as a single collapsed line (`c(0x58, 0x0a, ..., 0x38)`). If `FALSE` (default),
#'   the header is displayed with detailed comments explaining each component.
#'
#' @return An object of class `"constructive_code"` containing the generated code
#'   as a character vector. Each element is one line of code.
#'
#' @seealso [construct()] for idiomatic object construction, [serialize()] for
#'   the underlying serialization mechanism.
#'
#' @export
#' @examples
#' # Simple objects
#' construct_serialize(1:5)
#' construct_serialize(c("hello", "world"))
#'
#' # Complex objects
#' construct_serialize(data.frame(a = 1:3, b = letters[1:3]))
#' construct_serialize(matrix(1:6, nrow = 2))
#'
#' # Objects with attributes
#' x <- c(a = 1, b = 2, c = 3)
#' construct_serialize(x)
#'
#' # Expressions and calls
#' construct_serialize(quote(mean(x)))
#' construct_serialize(expression(x + 1, y * 2))
#'
#' # Special values
#' construct_serialize(c(NA, NaN, Inf, -Inf))
#'
#' # Builtin functions
#' construct_serialize(sum)
#' construct_serialize(`if`)
#'
#' # The generated code can be evaluated to reconstruct the object
#' code <- construct_serialize(iris)
#' reconstructed <- eval(parse(text = paste(code, collapse = "\n")))
#' identical(reconstructed, iris)
construct_serialize <- function(x, collapse_header = FALSE) {
  # 1. Convert input object to a raw vector
  raw_vector <- serialize(x, connection = NULL, ascii = FALSE)

  # 2. Process the header part of the raw vector
  header_res <- serialize_header(raw_vector, collapse = collapse_header)

  # 3. Process the data part of the raw vector
  # the reference table is filled as we meet symbols, environments etc
  globals[["serialize_refs"]] <- character(0)
  data_res <- serialize_data(header_res$x, header_res$i)

  # all bytes should be consumed, no more no less
  if (length(data_res$x) > 0 || data_res$i != length(raw_vector) + 1) {
    abort(sprintf(
      "Internal error: parsed %s bytes out of %s, please report this issue",
      data_res$i - 1, length(raw_vector)
    ))
  }

  # 4. Trim the final trailing comma from each block of code
  # (trim_last_comma is defined in utils.R)
  data_code <- trim_last_comma(data_res$code)

  # 5. Combine the generated code parts, matching the example style
  if (collapse_header) {
    # Header is already a single collapsed line
    code <- c(
      "# --- HEADER ---",
      paste0("c(", header_res$code, "),"),
      "# --- DATA ---",
      "c(",
      paste0("  ", data_code),
      ")"
    )
  } else {
    header_code <- trim_last_comma(header_res$code)
    code <- c(
      "# --- HEADER ---",
      "c(",
      paste0("  ", header_code),
      "),",
      "# --- DATA ---",
      "c(",
      paste0("  ", data_code),
      ")"
    )
  }

  # 6. Format the code into a single string and wrap it
  code <- c(
    "unserialize(as.raw(c(",
    paste0("  ", code),
    ")))"
  )
  code <- structure(code, class = "constructive_code")
  return(code)
}

Try the constructive package in your browser

Any scripts or data that you put into this service are public.

constructive documentation built on Oct. 22, 2026, 5:07 p.m.