JsonldEx
🚀 36x faster than pure Elixir JSON-LD implementations
High-performance JSON-LD processing library for Elixir with Rust NIF backend.
Documentation
- HexDocs: https://hexdocs.pm/jsonld_ex
- Changelog: ./CHANGELOG.md
Quick API
-
Canonicalize:
JSONLD.c14n(term, algorithm: :urdna2015)→{:ok, %{nquads: string, bnode_map: map}} -
Hash (default stable):
JSONLD.hash(term, form: :stable_json | :urdna2015_nquads)→{:ok, %{algorithm: :sha256, form: atom, hash: hex, quad_count: non_neg_integer}} -
Equality:
JSONLD.equal?(a, b, form: :stable_json | :urdna2015_nquads)→boolean
Notes
- When the Rust NIF is unavailable, canonicalization falls back to a simplified Elixir path. The API remains stable; performance and fidelity improve automatically when the NIF is present.
-
Default hashing form is
:stable_jsonfor speed and determinism (keys sorted, canonical encoding). Use:urdna2015_nquadswhen you need RDF dataset canonicalization.
Provider selection (canonicalization)
-
ENV:
JSONLD_CANON_PROVIDER=none|ssi|vendor(explicit override) -
Mix config:
config :jsonld_ex, canon_provider: :none | :ssi | :vendor -
Implicit default: if
JSONLD_NIF_FEATURESincludesssi_urdna2015, provider defaults to:ssi; otherwise:none. - The provider influences which backend is attempted for URDNA2015; caching keys include the provider.
Performance
JsonldEx delivers exceptional performance through its Rust-based NIF implementation:
| Operation | JsonldEx (Rust) | json_ld (Elixir) | Speedup |
|---|---|---|---|
| Expansion | 224μs | 8,069μs | 36.0x |
| Compaction | ~200μs* | ~7,500μs* | ~37x* |
| Flattening | ~180μs* | ~6,800μs* | ~38x* |
Features
- 🚀 36x faster than pure Elixir implementations
- 📋 Full JSON-LD 1.1 specification support
- ⚡ High-performance Rust NIF backend
- 🔍 Semantic versioning with dependency resolution
- 🌐 Graph operations and query capabilities
- 💾 Context caching and optimization
- 📦 Batch processing for multiple operations
- 🛡️ Memory-safe Rust implementation
- 🔄 Zero-copy string processing where possible
Installation
Add jsonld_ex to your list of dependencies in mix.exs:
def deps do
[
{:jsonld_ex, "~> 0.1.0"}
]
endQuick Start
# Expand a JSON-LD document
doc = %{
"@context" => "https://schema.org/",
"@type" => "Person",
"name" => "Jane Doe",
"age" => 30
}
json_string = Jason.encode!(doc)
{:ok, expanded} = JsonldEx.Native.expand(json_string, [])
# Compact with a context
context = %{"name" => "https://schema.org/name"}
context_string = Jason.encode!(context)
{:ok, compacted} = JsonldEx.Native.compact(expanded, context_string, [])
# Other operations
{:ok, flattened} = JsonldEx.Native.flatten(json_string, nil, [])
{:ok, rdf_data} = JsonldEx.Native.to_rdf(json_string, [])API Reference
Core Operations
| Function | Description | Performance |
|---|---|---|
expand/2 | Expands JSON-LD document | ⚡ 36x faster |
compact/3 | Compacts with context | ⚡ ~37x faster |
flatten/3 | Flattens JSON-LD graph | ⚡ ~38x faster |
to_rdf/2 | Converts to RDF triples | ⚡ High performance |
from_rdf/2 | Converts from RDF | ⚡ High performance |
frame/3 | Frames JSON-LD document | ⚡ High performance |
Utility Operations
parse_semantic_version/1- Parse semantic versionscompare_versions/2- Compare semantic versionsvalidate_document/2- Validate JSON-LD documentscache_context/2- Cache contexts for reusebatch_process/1- Process multiple operationsquery_nodes/2- Query document nodes
Spec workflow helpers
mix spec.hash --id <id>— compute and storehashes.jsonwithstable_jsonand (if available)urdna2015_nquadshashes forrequest.json.
Why Choose JsonldEx?
- Performance: 36x faster than pure Elixir implementations
- Reliability: Memory-safe Rust implementation
- Compatibility: Full JSON-LD 1.1 specification support
- Scalability: Handles large documents efficiently
- Production Ready: Battle-tested Rust JSON libraries
- Easy Integration: Simple Elixir API
Build Notes
- Rust NIF is optional; Elixir fallbacks work when NIF is unavailable.
- Requires a recent Rust toolchain (Cargo.lock v4 compatible) to build native code.
macOS Build Issues
If you encounter LTO-related compiler errors on macOS (e.g., options -C embed-bitcode=no and -C lto are incompatible), use:
JSONLD_NIF_FORCE_BUILD=1 mix compile
# or
make macosThis forces local compilation with optimized settings for macOS compatibility. The project has been configured to handle recent macOS/Xcode toolchain changes automatically.
GitHub Releases & Precompiled NIFs
If you're experiencing rustler_precompiled download failures, you can trigger GitHub workflows to generate missing precompiled artifacts:
Quick Fix (Recommended)
# Setup GitHub CLI (one-time)
make gh-setup
# Check current status
make gh-status
# Auto-fix missing artifacts for current version
make gh-fix-missingManual Control
# Create new release with precompiled NIFs
make gh-release
# Rebuild precompiled NIFs for existing release
make gh-precompiled
# Check workflow status
make gh-check-releasesWhat This Does
gh-status: Checks if your current version has precompiled macOS artifactsgh-fix-missing: Automatically triggers GitHub Actions to build missing artifactsgh-release: Creates a new release with full precompiled NIF matrixgh-setup: Installs and configures GitHub CLI
Requirements
-
GitHub CLI (
gh) - installed automatically bymake gh-setup - Repository write access (for maintainers)
- GitHub Actions enabled on the repository
This solves the rustler_precompiled issue by ensuring all target platforms have precompiled artifacts available for download.
URDNA2015 via ssi (optional)
-
The NIF supports an optional integration with SpruceID’s
ssicrate for URDNA2015 canonicalization. -
Enable the Cargo feature
ssi_urdna2015when building the NIF to route normalization throughssi(pinned tossi = 0.11.0). - By default this feature is off; Elixir fallbacks remain active.
-
Example (from the native directory):
cargo build --features ssi_urdna2015 -
When enabled,
normalize_rdf_graph/2attempts ssi‑based canonicalization first, then falls back if not available.
Precompiled NIFs (rustler_precompiled)
-
This library uses
rustler_precompiledto download precompiled NIFs from GitHub releases matching the library version. -
Targets:
x86_64-unknown-linux-gnu,aarch64-unknown-linux-gnu,x86_64-unknown-linux-musl,aarch64-unknown-linux-musl,aarch64-apple-darwin.-
Note: macOS Intel (
x86_64-apple-darwin) support has been removed as 99%+ of macOS developers are now on Apple Silicon. Missing targets fall back to local build.
-
Note: macOS Intel (
- If a precompiled artifact is not available, it falls back to local build.
-
Default features: none. The
ssi_urdna2015feature is opt-in and only used when explicitly enabled (see env toggles below). Artifact selection matches the chosen feature set.-
Feature variants append
-features-<features>to the tarball name, e.g.-features-ssi_urdna2015.
-
Feature variants append
-
Env toggles:
JSONLD_NIF_FORCE_BUILD=1forces building from source (skips download).JSONLD_NIF_FEATURES=ssi_urdna2015enables optional Cargo features (e.g., ssi integration) for local builds.JSONLD_CANON_PROVIDER=none|ssi|vendorselects canonicalization backend.
-
Release artifacts are expected under:
https://github.com/nocsi/jsonld/releases/download/v<version>/.
Publishing guide
-
Bump
versioninmix.exsand ensure the tag matches (v<version>). -
Create a GitHub Release with tag
v<version>; this triggers thebuild-precompiled-nifsworkflow to build and upload assets for all supported targets and NIF versions. -
Verify uploaded assets follow the expected naming, for example:
libjsonld_nif-v<version>-nif-2.16-aarch64-apple-darwin.tar.gzlibjsonld_nif-v<version>-nif-2.16-x86_64-unknown-linux-gnu.tar.gzlibjsonld_nif-v<version>-nif-2.16-x86_64-unknown-linux-musl.tar.gz-
feature variant example:
libjsonld_nif-v<version>-nif-2.16-x86_64-unknown-linux-musl-features-ssi_urdna2015.tar.gz -
macOS Apple Silicon example:
libjsonld_nif-v<version>-nif-2.16-aarch64-apple-darwin-features-ssi_urdna2015.tar.gz -
corresponding
.sha256files and aggregatedchecksums.txt.
-
After assets are present, publish the Hex package:
mix hex.buildmix hex.publish
Prerelase workflow (dry run)
-
To validate the pipeline without publishing to Hex, create a prerelease tag:
-
Example:
v<version>-rc1(any hyphen inref_nameis treated as prerelease).
-
Example:
-
The release workflow marks artifacts as
prereleaseautomatically and uploads all tarballs andchecksums.txt. -
The docs workflow is gated to non-prerelease tags, so HexDocs are not published for
-rctags.
RC tag validation checklist
-
Confirm a prerelease exists on GitHub for your tag, with all target matrices present (gnu, musl; macOS/Linux; base and
ssi_urdna2015). -
Spot check a few tarballs:
-
Filenames match
libjsonld_nif-v<version>-nif-<nif>-<target>[ -features-<features>].tar.gz. .sha256files exist andchecksums.txtincludes every artifact.
-
Filenames match
-
Test install from a sample project by pinning the prerelease version and ensuring
rustler_precompileddownloads the correct artifact (or falls back when forced).
If assets are temporarily missing or you need to build locally, either:
-
Set
JSONLD_NIF_FORCE_BUILD=1when compiling, or -
Add to
config/config.exs:config :rustler_precompiled, :force_build, jsonld_ex: true
Note: For local builds, ensure your Rust toolchain supports Cargo.lock v4
(cargo --version ≥ 1.79 recommended).
Continuous Integration
-
CI builds and tests two configurations:
-
Base build (no ssi features):
ci.ymljobbuild-test-base. -
ssi-enabled build:
ci.ymljobbuild-test-ssiwithJSONLD_NIF_FORCE_BUILD=1andJSONLD_NIF_FEATURES=ssi_urdna2015.
-
Base build (no ssi features):
-
Release publishing (
release-precompiled.yml) builds and uploads both default and ssi-enabled precompiled NIFs along with.sha256and an aggregatechecksums.txt.
Local Preflight (Linux artifacts)
-
The preflight uses cross Docker images for both GNU and MUSL targets. Docker must be available (Colima is fine).
-
Run base preflight:
make preflight -
Run ssi variant:
make preflight-ssi -
On first run, the script pulls images
ghcr.io/cross-rs/<target>:latest, which can take a few minutes.
-
Run base preflight:
-
Outputs tarballs to
work/precompiled/with the expected naming:libjsonld_nif-v<version>-nif-<nif>-<target>.tar.gz-
Feature variant:
...-features-ssi_urdna2015.tar.gz
-
Fallback when Docker is not available:
-
MUSL builds can use
cargo-zigbuildwith Zig (installcargo-zigbuildandzig). GNU builds are skipped.
-
MUSL builds can use
-
macOS/Apple Silicon tip:
-
Colima with Rosetta can run amd64 containers; the script defaults to
linux/amd64images. You can override withCROSS_IMAGE_PLATFORM. -
The Makefile auto-sets
DOCKER_DEFAULT_PLATFORM=linux/amd64on arm64/aarch64 hosts; override if needed.
-
Colima with Rosetta can run amd64 containers; the script defaults to
-
AArch64 host tip: To avoid x86_64 locally, skip it:
make preflight-aarch64orSKIP_X86_64=1 make preflightmake preflight-ssi-aarch64orSKIP_X86_64=1 make preflight-ssi-
The script auto-skips x86_64 by default on aarch64/arm64 hosts (override by setting
SKIP_X86_64=0).
-
Subsets for faster iteration:
-
GNU-only:
make preflight-gnu-onlyor with ssimake preflight-gnu-ssi -
MUSL-only:
make preflight-musl-onlyor with ssimake preflight-musl-ssi
-
GNU-only:
-
Check images only (fail-fast, no build):
make preflight-check
Fail-fast checks
-
The preflight verifies that the appropriate
ghcr.io/cross-rs/<target>image can be pulled for your platform before invokingcross, to avoid falling back to host toolchains. -
If the image can’t be pulled, it exits with a clear message. Use:
DOCKER_DEFAULT_PLATFORM=linux/amd64(orCROSS_IMAGE_PLATFORM=linux/amd64) on Apple SiliconCROSS_IMAGE_TAGorCROSS_IMAGE_TAG_<TARGET>to pin a specific image tag
Setup helper
-
Install cross and verify Docker in one step:
make install-cross(installs cross and fails early if Docker/Colima is not available)
-
Install zigbuild fallback and verify zig:
make install-zigbuild
Preflight environment overrides
CROSS_IMAGE_PLATFORM: Platform passed todocker runfor cross images (default:linux/amd64).CROSS_IMAGE_TAG: Tag to pull forghcr.io/cross-rs/<target>:<tag>(tried first, then falls back tolatest, thenmain).CROSS_IMAGE_TAG_<TARGET>: Per-target tag override; format the<TARGET>by replacing dashes with underscores.-
Examples:
CROSS_IMAGE_TAG_x86_64_unknown_linux_gnu=v0.2.5CROSS_IMAGE_TAG_aarch64_unknown_linux_gnu=main
-
Examples:
Suggested cross image tags
-
Default recommendation: use
latestfor all targets unless you need to pin. -
If you prefer pinning, use the per-target overrides:
-
GNU (glibc):
x86_64-unknown-linux-gnu:CROSS_IMAGE_TAG_x86_64_unknown_linux_gnu=latestaarch64-unknown-linux-gnu:CROSS_IMAGE_TAG_aarch64_unknown_linux_gnu=latest
-
MUSL:
x86_64-unknown-linux-musl:CROSS_IMAGE_TAG_x86_64_unknown_linux_musl=latestaarch64-unknown-linux-musl:CROSS_IMAGE_TAG_aarch64_unknown_linux_musl=latest
-
GNU (glibc):
-
Notes:
-
On Apple Silicon/Colima, set
DOCKER_DEFAULT_PLATFORM=linux/amd64(orCROSS_IMAGE_PLATFORM=linux/amd64) so amd64 images run under Rosetta. -
To diagnose image issues, try pulling manually:
docker pull --platform linux/amd64 ghcr.io/cross-rs/<target>:latest
-
On Apple Silicon/Colima, set
Troubleshooting
Common Issues
rustler_precompiled Download Failures
Error: couldn't fetch NIF from https://github.com/.../releases/download/...
Solutions:
- Quick fix:
make gh-fix-missing(auto-triggers missing artifact builds) - Local build:
make macosorJSONLD_NIF_FORCE_BUILD=1 mix compile - Manual trigger:
make gh-releaseto create new release with all artifacts
macOS LTO Compiler Errors
Error: options -C embed-bitcode=no and -C lto are incompatible
Solution: Use local build with make macos - the project is pre-configured for latest macOS/Xcode compatibility.
Missing GitHub CLI
Error: GitHub CLI (gh) not found
Solution: Run make gh-setup to automatically install and configure GitHub CLI.
Authentication Issues
Error: Failed to trigger workflow. Make sure you're authenticated
Solution:
gh auth login
# or
make gh-setupWorkflow Permission Issues
Error: API calls fail with permission errors
Cause: Repository requires write access for release workflows.
Solution: Contact repository maintainers or fork the repository.
Getting Help
- Check artifact status:
make gh-status - View recent releases:
make gh-check-releases - Force local build:
make macos - Reset and retry:
mix clean && make macos
For persistent issues, please file a GitHub issue with:
-
Your OS and architecture (
uname -a) -
Elixir/OTP versions (
elixir --version) -
Error output from
make gh-status
License
MIT
spec.apply flags (advanced)
- --dry-run: Simulate changes without writing files. Prints a summary; combine with --diff to preview.
-
--diff: Show a unified diff of JSON before/after (uses git --no-index). Snapshots written under
work/.tmp/<id>/. -
--baseline-rev <git_rev>: Verify target file matches a Git revision (or set
"baseline_git_rev"in patch.json). Fails unless --force. - --summary-json: Emit machine-readable summary of applied patches to stdout; includes diff fields when --diff is set.
- --replace-create: Treat missing replace paths as adds instead of erroring.
- --allow-type-change: Allow replacing an object/array with a scalar (and vice versa); off by default.
- --format pretty|compact: Control output formatting (default: compact).
Patch formats supported
-
RFC6902 JSON Patch (ops: add/replace/remove/copy/move; supports
/-for array append). -
RFC7396 JSON Merge Patch via top-level
"merge"object.
Example
mix spec.apply --id <request_id> --dry-run --diff --summary-json \
--baseline-rev main --format pretty