Converting to JavaScript

Once webR has been loaded into a web page, objects can be converted into JavaScript from the R environment. For example, it is possible perform some computation within R and then convert the resulting R object into a JavaScript object for use.

Warning

At the moment, not all R objects can be converted to JavaScript objects. Attempting to convert an unsupported R object will throw a JavaScript exception.

Explicitly converting an RObject to JavaScript can be done by invoking the RObject.toJs() method, which returns a JavaScript representation of the associated R object. In most cases, JavaScript conversion has been implemented by serialising the R object to an WebRDataJs.

Subclasses of RObject provide additional methods to convert objects into a JavaScript representation.

Serialising R objects

Invoking RObject.toJs() on an R object serialises the object to a JavaScript object of type WebRDataJs. This type is designed to form a tree structure, supporting an unambiguous JavaScript representation for potentially nested R objects.

Most R objects are serialised to JavaScript objects that contain the following properties,

Property Value
type A description of the R object’s type as a string.
names An array of strings corresponding to the R object’s names attribute, or null for no names.
values An array containing the value(s) associated with the R object. A JavaScript null indicates a missing value, NA.

Some R objects, such as lists or environments, contain references to other R objects. Such objects are serialised recursively. Any R objects deeper than the maximum depth setting will not be serialised and instead included in the values array as an RObject. By default, R objects are serialised with infinite maximum depth.

An R NULL object is serialised to a JavaScript object of type WebRDataJsNull. This type does not include the names or values properties.

Warning

The structure of serialised R objects may be updated in future versions of webR, expanding to include more R object attributes. As such, compatibility of serialised R objects between versions of webR is not guaranteed.

Serialisation options

An options argument of type ToJsOptions can be provided to the RObject.toJs() method for fine-grained control over how objects are serialised.

The following options are available,

Property Description
depth How deep should nested R objects be serialised? A value of 0 indicates infinite depth.

Example: Serialising an R double atomic vector

const primes = await webR.evalR('c(2,3,5,7,11,13)');
await primes.toJs()
{
  type: 'double'
  names: null
  values: [2, 3, 5, 7, 11, 13]
}

Converting to JavaScript Object

R environments, lists, and atomic vectors provide a toObject() method that converts the R object into a JavaScript object. The result of this conversion differs from the serialisation described above in that the resulting JavaScript object properties will be directly named by the components of the R object.

webR.objs.globalEnv.bind('foo', [1, 10, 28, 44, 26, 52]);
webR.objs.globalEnv.bind('bar', [1, 2, 3, 401, 113, 22]);
await webR.objs.globalEnv.toObject();
{ bar: Proxy(Object), foo: Proxy(Object) }

By default, toObject() will not recurse into objects and will return R object references as JavaScript values. As an override, the depth option may be set to recurse into the object using the .toJs() serialisation method.

webR.objs.globalEnv.bind('foo', [1, 10, 28, 44, 26, 52]);
webR.objs.globalEnv.bind('bar', [1, 2, 3, 401, 113, 22]);
await webR.objs.globalEnv.toObject({ depth: 0 });
{
  bar: {
    type: "double",
    names: null,
    values: [1, 2, 3, 401, 113, 22],
  },
  foo: {
    type: "double",
    names: null,
    values: [1, 10, 28, 44, 26, 52]
  },
}

Object conversion options

The options argument may be used to control how R objects with empty or duplicated names are converted. In the case of duplicated R component names, first wins.

const obj = await webR.evalR(`
  list(foo = c(1, 2, 3), foo = c(4, 5, 6), c("x", "y", "z"))
`);

await obj.toObject({ allowEmptyKey: true, allowDuplicateKey: true});
import type { RList } from 'webr';

const obj = await webR.evalR(`
  list(foo = c(1, 2, 3), foo = c(4, 5, 6), c("x", "y", "z"))
`) as RList;

await obj.toObject({ allowEmptyKey: true, allowDuplicateKey: true});
{
  "": {
    type: "double",
    names: null,
    values: ['x', 'y', 'z'],
  },
  foo: {
    type: "double",
    names: null,
    values: [1, 2, 3]
  },
}

The following options are available,

Property Description
depth How deep should nested R objects be serialised? A value of 0 indicates infinite depth.
allowEmptyKey Allow an empty or null key when converting the object.
allowDuplicateKey Allow duplicate keys when converting the object.

When allowEmptyKey or allowDuplicateKey are false, an error is thrown in the case of empty or duplicated R component names.

Converting to JavaScript Array

R lists and atomic vectors may be converted into a JavaScript Array value using the method toArray().

const recurrence = await webR.evalR('c(1.1, 2.2, 3.3, 5.5, 8.8)');
await recurrence.toArray();
[1.1, 2.2, 3.3, 5.5, 8.8]
Note

When converting atomic vectors to JavaScript values, missing values of NA are represented as values of null in the resulting JavaScript representation. This conversion process may have a performance cost for very large vectors.

Accessing raw WebAssembly memory

For atomic vectors, the toTypedArray() method may be invoked to access a copy of the object data as it exists in WebAssembly memory.

Warning

The underlying raw memory buffer as managed by R will be returned as-is, including raw pointers for R character strings and sentinel values for missing values.

const primes = await webR.evalR('c(2,3,5,7,11,13)');
await primes.toTypedArray();
import type { RDouble } from 'webr';

const primes = await webR.evalR('c(2,3,5,7,11,13)') as RDouble;
await primes.toTypedArray();
Float64Array(6) [2, 3, 5, 7, 11, 13, buffer: ArrayBuffer(48), ... ]

Converting to primitive values

Scalar R values1 may be converted into JavaScript values using various subclass methods.

const double = await webR.evalR('20');
await double.toNumber();
await webR.objs.true.toBoolean();
import type { RDouble } from 'webr';

const double = await webR.evalR('20') as RDouble;
await double.toNumber();
await webR.objs.true.toBoolean();
20
true

Each type of atomic scalar is converted into a particular JavaScript type, and so the method names are specialised.

R object type Method JavaScript type
RLogical .toBoolean() Boolean
RInteger .toNumber() Number
RDouble .toNumber() Double
RComplex .toComplex() { re: ..., im: ... }
RCharacter .toString() String
RRaw .toNumber() Number

Converting from an R data.frame

R data.frame objects are list objects with an additional class attribute. As such, they may be converted into JavaScript objects using the toObject() method. When the R list object is a data.frame, webR will automatically convert the inner atomic columns into Array format.

const mtcars = await webR.evalR('mtcars');
await mtcars.toObject();
import type { RList } from 'webr';

const mtcars = await webR.evalR('mtcars') as RList;
await mtcars.toObject();
{
  am: [1, 1, 1, ..., 1],
  carb: [4, 4, 1, ..., 2],
  cyl: [6, 6, 4, ..., 4]
  ...,
  wt: [2.62, 2.875, 2.32, ..., 2.78],
}

R data.frame objects may also be converted into a D3-style data array format using the toD3() method. This method is only available for R objects of class data.frame.

const mtcars = await webR.evalR('mtcars');
await mtcars.toD3();
import type { RList } from 'webr';

const mtcars = await webR.evalR('mtcars') as RList;
await mtcars.toD3();
[
  { mpg: 21, cyl: 6, disp: 160, ... },
  { mpg: 21, cyl: 6, disp: 160, ... },
  { mpg: 22.8, cyl: 4, disp: 108, ...},
  ...
  { mpg: 21.4, cyl: 4, disp: 121, ...},
]

Cached R objects

WebR.objs contains named references to long-living R objects in the form of RObject proxies. WebR.objs is automatically populated at initialisation time, and its properties may be safely accessed once the promise returned by WebR.init() resolves.

WebR.objs contains references to the following R objects,

Property JavaScript Type R object
null RNull NULL
true RLogical TRUE
false RLogical FALSE
na RLogical Logical NA
globalEnv REnvironment The R global environment
baseEnv REnvironment The R base environment

Additional R object proxy hooks

The RObject proxies are implemented with so-called hooks, for instance a hook that forwards class method invocation to the webR worker thread. The following hooks provide additional R object functionality.

Executing R functions from JavaScript

At the moment, R functions cannot be directly converted into JavaScript functions. However, references to R functions can be executed from JavaScript in a limited way. It is possible to return an R function or closure with WebR.evalR() and wrap it as an RFunction proxy. The R function represented by the proxy can be called by either:

  • Invoking the RFunction.exec() method on the RFunction object.

  • Using (), i.e. the normal JavaScript function call syntax.

In either case, both JavaScript or RObject proxies can be passed as arguments to the associated R function. In the case of JavaScript values they are converted to R objects before function execution.

const fn = await webR.evalR('function(x) { 2 * x }');
const result = await fn.exec([1,2,3]);
await result.toArray()
import type { RDouble, RFunction } from 'webr';

const fn = await webR.evalR('function(x) { 2 * x }') as RFunction;
const result = await fn.exec([1,2,3]) as RDouble;
await result.toArray()
[2, 4, 6]

The result of the R computation is automatically converted back into a JavaScript representation using RObject.toJs() if the RFunction proxy was executed using ().

const sin = await webR.evalR('sin');
await sin([1,2,3]);
import type { RFunction } from 'webr';

const sin = await webR.evalR('sin') as RFunction;
await sin([1,2,3]);
{
  type: 'double'
  names: null
  values: [0.8414709848078965, 0.9092974268256817, 0.1411200080598672]
}

Looping over R vectors

RObject proxies are async iterable for objects with a length property. As such, R vectors can be looped over using the JavaScript for await...of syntax. For example,

const list = await webR.evalR('list(2,4,6,"abc")');
for await (const i of list){
  const out = await i.toJs();
  console.log(out);
}
import type { RList } from 'webr';

const list = await webR.evalR('list(2,4,6,"abc")') as RList;
for await (const i of list){
  const out = await i.toJs();
  console.log(out);
}
{ type: 'double', names: null, values: [2] }
{ type: 'double', names: null, values: [4] }
{ type: 'double', names: null, values: [6] }
{ type: 'character', names: null, values: ["abc"] }

Footnotes

  1. A scalar value in R is an atomic vector of length 1.↩︎