
High-performance GeoJSON and JSON serialization for
sf and tabular objects
fastgeojson provides extremely fast conversion of
sf objects to GeoJSON FeatureCollection strings and
rectangular tabular objects (data.frame,
data.table, tibble) to JSON arrays of row
objects.
Implemented in Rust via the extendr framework, it uses parallel processing and low-level optimizations to deliver 2.4–16× speedups over existing R solutions on large datasets.
The resulting strings are ready for immediate use in web applications
(Shiny, Plumber), direct integration with
leaflet::addGeoJSON(), and other R packages that interface
with JavaScript.
Status: v0.1.2 — Stable for production use and compatible with
shinyapps.io. This is an early release offastgeojson, so we are actively looking for edge cases; bug reports and feature requests are very welcome.
Benchmarks conducted with microbenchmark (times in
milliseconds; lower is better).
| Package | Median_ms | Speedup.vs.yyjsonr |
|---|---|---|
| jsonify | 1573 | — |
| jsonlite | 1281 | — |
| yyjsonr | 235 | 1× |
| fastgeojson | 98 | 2.4× |
| Package | Median_ms | Speedup.vs.yyjsonr |
|---|---|---|
| geojsonsf | 1920 | — |
| yyjsonr | 586 | 1× |
| fastgeojson | 238 | 2.5× |
Once available on CRAN, you can install the stable version directly:
install.packages("fastgeojson")To install the latest development version or pre-compiled binaries for Windows/macOS (no Rust required) before the CRAN release:
options(repos = c(
firstzero = "https://firstzeroenergy.r-universe.dev",
CRAN = "https://cloud.r-project.org"
))
install.packages("fastgeojson")Note: If you are using the CRAN version, deployment works automatically.
If you are using the development version from
R-universe, you must tell shinyapps.io where to find the package.
The Fix: Add the following lines to the very top of
your app.R (or global.R) file.
options(repos = c(
firstzero = "https://firstzeroenergy.r-universe.dev",
CRAN = "https://cloud.r-project.org"
))This ensures the build server can find and install
fastgeojson from the custom repository.
The core performance advantages come from a carefully designed Rust backend:
rayon: large datasets are split into chunks (default ~2048
rows) and processed in parallel across available CPU cores.JsonWriter builds output directly into a
pre-allocated Vec<u8> using fast number formatting
(ryu for floats, itoa for integers) and manual
byte pushing.NA values in
properties are omitted rather than written as null,
reducing payload size.These low-level optimizations eliminate the bottlenecks found in general-purpose JSON libraries while preserving full compatibility with R’s data model.
Because fastgeojson returns pre-classed
json strings, you can bypass R’s internal serialization
when sending data to the browser.
# FAST: Direct handoff to Leaflet or deck.gl
observe({
# fastgeojson serializes in Rust
json_data <- sf_geojson_str(large_sf_object)
# Sent directly to client without re-encoding
session$sendCustomMessage("updateMap", json_data)
})sf_geojson_str(x) → GeoJSON FeatureCollection string
(class "geojson" "json")df_json_str(x) → JSON array of row objects (class
"json")Both return a single character string and include extensive input validation.
sf to
GeoJSONlibrary(sf)
library(fastgeojson)
nc <- st_read(system.file("shape/nc.shp", package = "sf"), quiet = TRUE)
geojson <- sf_geojson_str(nc)
class(geojson)
#> [1] "geojson" "json"
cat(substr(geojson, 1, 120))Direct use in Leaflet:
library(leaflet)
leaflet() %>%
addTiles() %>%
addGeoJSON(geojson, fillOpacity = 0.6)library(fastgeojson)
df <- data.frame(
id = 1:3,
name = c("Alice", "Bob", "Charlie"),
value = c(10.5, 20.1, NA),
active = c(TRUE, FALSE, TRUE),
stringsAsFactors = FALSE
)
json <- df_json_str(df)
class(json)
#> [1] "json"
cat(substr(json, 1, 120))NA in properties omitted for compact
outputTo prioritize stability and speed, the following complex types are currently not supported. Future updates may introduce support for these types, provided they do not compromise encoding performance.
c("tag1", "tag2") inside a cell) are omitted from the
output.
df$tags <- sapply(df$tags, paste, collapse = ", ")POSIXlt format is not supported due to its
underlying list-based structure.
POSIXct or
Character.df$time_col <- as.POSIXct(df$time_col)src/rust/ (managed via
rust-toolchain.toml for reproducible builds)R/Bug reports, feature requests, and contributions are very welcome
MIT © FirstZero Energy