Skip to contents

An R6 instance has no json_state() method of its own, because what an R6 class guarantees is what its methods say rather than what its bindings happen to hold: a private field is private precisely because it is not part of that contract. Writing one is therefore refused, naming the class and the method it wants.

Usage

r6_state(x)

r6_restore(class, state)

Arguments

x

The R6 instance whose bindings are to be recorded.

class

Empty object carrying the recorded class vector, which json_revive() dispatches on.

state

Whatever r6_state() returned.

Value

The r6_state() function returns a list carrying package, public and private, and r6_restore() the rebuilt instance.

Details

A class author is the party who knows whether a field is stored or derived, whether initialize establishes an invariant, and whether a reference should be recorded as a key rather than a value. One who has made that judgment and wants the instance recorded as its bindings anyway opts in with one method each way:

json_state.MyClass <- function(x) r6_state(x)
json_revive.MyClass <- function(class, state) r6_restore(class, state)

The pair records the class's package alongside the public and private bindings an instance holds, leaving out anything the generator chain declares as a method and anything bound actively. On the way back the generator is found again by name, an instance is allocated from a twin of it whose initialize does nothing, and the recorded bindings are written into that.

What the pair does not guarantee

The mechanism reaches past the interface the class offers, which is what makes it the author's call rather than the default. Four consequences are worth stating outright.

Bindings are reinstated past initialize. The instance that comes back is filled from the document rather than constructed, so an invariant initialize establishes is not re-established and a resource it acquires is not acquired. A finalize method still registers on the object, and so runs against a handle it never held.

The round trip is exact only while the class's shape is unchanged. Where the generator locks its instances, recorded state the class no longer declares has nowhere to go and is dropped with a warning naming it; a field the class has gained since arrives at its default. The lock itself comes from the generator, so one placed on a single object or binding by hand is not recorded and does not come back.

A revived instance is a new environment, so the trip holds up to the equivalence vignette("design") states for an environment recorded by its contents rather than under identical().

Both halves need the generator to be findable by name in the environment the class was defined in, which is checked on the way out as well as on the way in. A class defined inside a function, one whose name finds two generators, and a non-portable class are all refused where they are written.

Examples

Counter <- R6::R6Class("Counter",
  public = list(
    n = 0,
    initialize = function(n = 0) self$n <- n,
    bump = function() {
      self$n <- self$n + 1
      invisible(self)
    }
  )
)

json_state.Counter <- function(x) r6_state(x)
json_revive.Counter <- function(class, state) r6_restore(class, state)

counter <- Counter$new()$bump()$bump()

json_read_str(json_write_str(counter))$n
#> Error: an `R6` instance needs a `json_state()` method for class `Counter` at `x`