jsonapi_core
A typed JSON:API v1.1 serialization library for Rust.
Features
- Full type model —
Document,Resource,Relationship,Link,ApiError, and all JSON:API 1.1 types with custom serde implementations - Derive macro —
#[derive(JsonApi)]maps Rust structs to JSON:API resource envelopes - Fuzzy deserialization — accepts camelCase, snake_case, kebab-case, and PascalCase variants of field names
- Registry — typed lookups from
includedarrays viaRelationship<T>references - Recursive resolver — kitsu-core-style flattened output with cycle detection
- Query builder — JSON:API-aware query strings with bracket encoding and RFC 3986 percent-encoding
- Content negotiation —
ext/profilemedia-type parsing,Content-Typevalidation,Acceptnegotiation - Sparse fieldsets — typed and dynamic filtering paths
- Include path validation — relationship graph walking with static type metadata
- Atomic Operations extension — feature-gated
atomicmodule implementing the JSON:API v1.1 Atomic Operations extension (add/update/remove, lid cross-refs)
Install
Requires Rust 1.88+ and the 2024 edition.
Quick Example
use ;
// Deserialize a JSON:API response
let json = r#"{
"data": {
"type": "articles", "id": "1",
"attributes": {"title": "Hello JSON:API"},
"relationships": {
"author": {"data": {"type": "people", "id": "9"}}
}
},
"included": [{
"type": "people", "id": "9",
"attributes": {"name": "Dan Gebhardt"}
}]
}"#;
// Use Document<Resource> to handle mixed types in `included`
let doc: = from_str.unwrap;
let registry = doc.registry.unwrap;
// Typed lookup — deserializes the stored Value into a Person
let author: Person = registry.get_by_id.unwrap;
assert_eq!;
Feature Flags
| Feature | Default | Description |
|---|---|---|
derive |
yes | Re-exports #[derive(JsonApi)] from jsonapi_core_derive |
atomic-ops |
off | Atomic Operations extension types (atomic module) |
See docs/feature-flags.md for details.
Examples
Runnable examples ship with the crate:
Documentation
- The jsonapi_core Guide — chapter-by-chapter walkthrough covering documents, resources, relationships, the registry, the query builder, sparse fieldsets, content negotiation, atomic operations, and a cookbook of common recipes.
- API docs on docs.rs — type-level reference for every public item.
The guide is laid out as an mdbook under
docs/. To build it locally:
Or browse the markdown directly starting at docs/introduction.md.
Repository layout
| Path | Contents |
|---|---|
jsonapi_core/ |
The library crate. |
jsonapi_core_derive/ |
The proc-macro crate (re-exported via the derive feature). |
acceptance/ |
Spec-conformance integration tests. |
docs/ |
The guide book (this is what you're reading). |
Versioning policy
jsonapi_core follows Semantic Versioning 2.0.0.
Lockstep workspace versions
The jsonapi_core and jsonapi_core_derive crates are versioned in lockstep
via workspace.package.version. They are always released together. Pin only
jsonapi_core in your Cargo.toml; the derive crate is re-exported via the
derive feature.
What is public API
The following are public API and changes to them are governed by SemVer:
- All items re-exported at the
jsonapi_corecrate root (Document,PrimaryData,Resource,ResourceObject,ResourceIdentifier,Identity,Relationship,RelationshipData,Links,Link,LinkObject,Hreflang,Meta,JsonApiObject,ApiError,ErrorLinks,ErrorSource,Registry,ResolveConfig,TypeRegistry,TypeInfo,QueryBuilder,FieldsetConfig,SparseSerializer,sparse_filter,CaseConfig,CaseConvention,Error,Result,JsonApiMediaType,validate_content_type,negotiate_accept,validate_member_name,MemberNameKind). - All items re-exported under the
atomic-opsfeature (AtomicRequest,AtomicResponse,AtomicResult,AtomicOperation,OperationTarget,OperationRef,ATOMIC_EXT_URI). - The
#[derive(JsonApi)]attribute set:type,caseon the struct;id,lid,relationship,meta,links,rename,skip, and relationshiptypeon fields. - Default behaviours documented in the crate-level rustdoc and the
guide: the fuzzy-deserialization alias set, the
Option::None→ omitted-on-serialize rule, thenull→Nonedeserialize fall-through, the registry's silent skip on shape mismatch, the resolver's cycle detection. - The minimum supported Rust version (MSRV).
What is not public API
- Anything inside a
pub(crate)or private module path. Items not re-exported at the crate root may be relocated or removed in any release. - The exact text of error
Displaymessages (theErrorenum variants are public; the formatted strings are not). - The exact alias ordering inside the fuzzy-deserialization fall-through chain (the set of accepted aliases is public; ties resolve in implementation order).
- Internals of the
jsonapi_core_deriveproc-macro crate. Use the derive only throughjsonapi_core'sderivefeature; do not depend onjsonapi_core_derivedirectly. - Unreleased items behind unstable feature flags (none currently exist; this
applies to any future
unstable_*flags).
#[non_exhaustive] guarantees
All public enums (PrimaryData, Document, RelationshipData, Identity,
Hreflang, Link, CaseConvention, MemberNameKind, Error, …) carry
#[non_exhaustive]. New variants may be added in minor releases. Match
arms in consumer code must include a _ => fall-through.
MSRV policy
The minimum supported Rust version is currently 1.94.1. MSRV bumps require
a minor-version release (≥ 0.x.0 while pre-1.0; ≥ x.0.0 post-1.0) and
will be called out in the changelog.
Pre-1.0 caveat
While at 0.x, breaking changes follow the SemVer pre-1.0 convention: a bump
to 0.(x+1).0 may include breaking changes. We will continue to maintain a
detailed changelog so each upgrade has a clear migration path.
The typed parse-error story is now complete — TypeMismatch,
MalformedRelationship, MissingAttribute, and IncludedRefMissing all
flow through Document::from_str / from_slice / from_value. A 1.0 tag
will follow once the next round of consumer-feedback churn settles: if no
breaking changes land for two consecutive minor releases after this work,
1.0 becomes a tag-when-ready event.
Changelog
See CHANGELOG.md for a release-by-release record.
License
Licensed under either of Apache License, Version 2.0 or MIT license at your option.