Key Features
Bidirectional Mapping
Seamless conversion between Dart objects and RDF representations
Type-Safe API
Fully typed API for safe RDF mapping operations
Extensible
Easy creation of custom mappers for domain-specific types
Flexible
Support for all core RDF concepts: IRI nodes, blank nodes, and literals
Dual API
Work with RDF strings or directly with graph structures
Compatible
Built on top of rdf_core
for seamless integration
What is RDF?
Resource Description Framework (RDF) is a standard model for data interchange on the Web. It extends the linking structure of the Web by using URIs to name relationships between things as well as the two ends of the link.
RDF is built around statements known as "triples" in the form of subject-predicate-object:
- Subject: The resource being described (identified by an IRI or blank node)
- Predicate: The property or relationship (always an IRI)
- Object: The value or related resource (an IRI, blank node, or literal value)
Quick Start
Installation
dependencies:
rdf_mapper: ^0.7.0
Or use the following command:
dart pub add rdf_mapper
Basic Setup
import 'package:rdf_mapper/rdf_mapper.dart';
// Create a mapper instance with default registry
final rdfMapper = RdfMapper.withDefaultRegistry();
Usage Examples
import 'package:rdf_core/rdf_core.dart';
import 'package:rdf_mapper/rdf_mapper.dart';
import 'package:rdf_vocabularies/schema.dart';
// Create mapper instance with default registry
final rdf = RdfMapper.withDefaultRegistry()
// Register our custom mappers
..registerMapper<Book>(BookMapper())
..registerMapper<Chapter>(ChapterMapper())
..registerMapper<ISBN>(ISBNMapper())
..registerMapper<Rating>(RatingMapper());
// Create a book with chapters
final book = Book(
id: 'hobbit', // Just the identifier, not the full IRI
title: 'The Hobbit',
author: 'J.R.R. Tolkien',
published: DateTime(1937, 9, 21),
isbn: ISBN('9780618260300'),
rating: Rating(5),
chapters: [
Chapter('An Unexpected Party', 1),
Chapter('Roast Mutton', 2),
Chapter('A Short Rest', 3),
],
);
// Convert the book to RDF Turtle format
final turtle = rdf.encodeObject(book);
// Result will be nicely formatted RDF Turtle
// RDF Turtle input to deserialize
final turtleInput = '''
@base <http://example.org/book/> .
@prefix schema: <https://schema.org/> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
<hobbit> a schema:Book;
schema:aggregateRating 5;
schema:author "J.R.R. Tolkien";
schema:datePublished "1937-09-20T23:00:00.000Z"^^xsd:dateTime;
schema:hasPart [ a schema:Chapter ; schema:name "An Unexpected Party" ; schema:position 1 ], [ a schema:Chapter ; schema:name "Roast Mutton" ; schema:position 2 ], [ a schema:Chapter ; schema:name "A Short Rest" ; schema:position 3 ];
schema:isbn <urn:isbn:9780618260300>;
schema:name "The Hobbit" .
''';
// Deserialize RDF back to our Book object with all its chapters
final book = rdf.decodeObject<Book>(turtleInput);
// Access complex properties
print('Title: ${book.title}');
print('Author: ${book.author}');
print('Published: ${book.published}');
print('Rating: ${book.rating.stars} stars');
print('Chapters: ${book.chapters.length}');
// --- Domain Model ---
// Primary entity with an identifier
class Book {
final String id;
final String title;
final String author;
final DateTime published;
final ISBN isbn;
final Rating rating;
final Iterable<Chapter> chapters;
Book({
required this.id,
required this.title,
required this.author,
required this.published,
required this.isbn,
required this.rating,
required this.chapters,
});
}
// Value object using blank nodes (no identifier)
class Chapter {
final String title;
final int number;
Chapter(this.title, this.number);
}
// Custom identifier type using IRI mapping
class ISBN {
final String value;
ISBN(this.value);
}
// Custom value type using literal mapping
class Rating {
final int stars;
Rating(this.stars) {
if (stars < 0 || stars > 5) {
throw ArgumentError('Rating must be between 0 and 5 stars');
}
}
}
// Book mapper - maps Book objects to IRI nodes
class BookMapper implements GlobalResourceMapper<Book> {
// Property predicates for semantic clarity
static final titlePredicate = SchemaBook.name;
static final authorPredicate = SchemaBook.author;
static final publishedPredicate = SchemaBook.datePublished;
static final isbnPredicate = SchemaBook.isbn;
static final ratingPredicate = SchemaBook.aggregateRating;
static final chapterPredicate = SchemaBook.hasPart;
static const String bookIriPrefix = 'http://example.org/book/';
@override
final IriTerm typeIri = SchemaBook.classIri;
@override
(IriTerm, List<Triple>) toRdfResource(
Book book,
SerializationContext context, {
RdfSubject? parentSubject,
}) {
return context
.resourceBuilder(IriTerm('$bookIriPrefix${book.id}'))
.addValue(titlePredicate, book.title)
.addValue(authorPredicate, book.author)
.addValue<DateTime>(publishedPredicate, book.published)
.addValue<ISBN>(isbnPredicate, book.isbn)
.addValue<Rating>(ratingPredicate, book.rating)
.addValues(chapterPredicate, book.chapters)
.build();
}
@override
Book fromRdfResource(IriTerm term, DeserializationContext context) {
final reader = context.reader(term);
return Book(
id: _extractIdFromIri(term.iri),
title: reader.require<String>(titlePredicate),
author: reader.require<String>(authorPredicate),
published: reader.require<DateTime>(publishedPredicate),
isbn: reader.require<ISBN>(isbnPredicate),
rating: reader.require<Rating>(ratingPredicate),
chapters: reader.getValues<Chapter>(chapterPredicate),
);
}
// Helper method to extract ID from IRI
String _extractIdFromIri(String iri) {
if (!iri.startsWith(bookIriPrefix)) {
throw ArgumentError('Invalid Book IRI format: $iri');
}
return iri.substring(bookIriPrefix.length);
}
}
// Chapter mapper - maps Chapters to blank nodes
class ChapterMapper implements LocalResourceMapper<Chapter> {
static final titlePredicate = SchemaChapter.name;
static final numberPredicate = SchemaChapter.position;
@override
final IriTerm typeIri = SchemaChapter.classIri;
@override
(BlankNodeTerm, List<Triple>) toRdfResource(
Chapter chapter,
SerializationContext context, {
RdfSubject? parentSubject,
}) {
return context.resourceBuilder()
.addValue(titlePredicate, chapter.title)
.addValue(numberPredicate, chapter.number)
.build();
}
@override
Chapter fromRdfResource(BlankNodeTerm term, DeserializationContext context) {
final reader = context.reader(term);
return Chapter(
reader.require<String>(titlePredicate),
reader.require<int>(numberPredicate),
);
}
}
// ISBN mapper - maps ISBN to IRI term
class ISBNMapper implements IriTermMapper<ISBN> {
static const String ISBN_URI_PREFIX = 'urn:isbn:';
@override
IriTerm toRdfTerm(ISBN value, SerializationContext context) {
return IriTerm('$ISBN_URI_PREFIX${value.value}');
}
@override
ISBN fromRdfTerm(IriTerm term, DeserializationContext context) {
final uri = term.iri;
if (!uri.startsWith(ISBN_URI_PREFIX)) {
throw ArgumentError('Invalid ISBN URI format: $uri');
}
return ISBN(uri.substring(ISBN_URI_PREFIX.length));
}
}
// Rating mapper - maps Rating to literal term
class RatingMapper implements LiteralTermMapper<Rating> {
@override
LiteralTerm toRdfTerm(Rating value, SerializationContext context) {
return LiteralTerm.typed(value.stars.toString(), 'integer');
}
@override
Rating fromRdfTerm(LiteralTerm term, DeserializationContext context) {
return Rating(int.parse(term.value));
}
}
Architecture
Mapper Hierarchy
-
Term Mappers: For simple values
IriTermMapper
: For IRIs (e.g., URIs, URLs)LiteralTermMapper
: For literal values (strings, numbers, dates)
-
Resource Mappers: For complex objects with multiple properties
GlobalResourceMapper
: For objects with globally unique identifiersLocalResourceMapper
: For anonymous objects or auxiliary structures
Context Classes
SerializationContext
: Provides access to the ResourceBuilderDeserializationContext
: Provides access to the ResourceReader
Fluent APIs
ResourceBuilder
: For conveniently creating RDF resourcesResourceReader
: For easily accessing RDF resource properties
Start Mapping Your Dart Objects to RDF Today
Empower your applications with the semantic web capabilities