Lutaml::Model provides a unified rdf DSL that defines RDF mappings once for both JSON-LD and Turtle serialization. This follows the same principle as key_value do … end which serves JSON, YAML, and TOML from a single block.
Both JSON-LD and Turtle are RDF serialization formats representing the same subject–predicate–object triples. The rdf DSL lets you define the mapping once, and the format adapters handle syntax differences automatically.
Setup
Add the rdf-turtle gem to your Gemfile (required for Turtle output):
gem "rdf-turtle", "~> 3.3"No additional gem is needed for JSON-LD output.
When to Use rdf vs Separate jsonld/turtle Blocks
Use rdf do … end when: * Your model maps to RDF resources using standard vocabularies (SKOS, DC, etc.) * You need both JSON-LD and Turtle output from the same model * You want predicate-based mapping with automatic @context generation
Use jsonld do … end when: * You need full control over the JSON-LD @context structure * You are mapping to JSON properties that don’t map to RDF predicates
Use turtle do … end when: * You only need Turtle output * You need Turtle-specific features not available in the unified DSL
Both the unified rdf block and the format-specific blocks can coexist on the same model. If both are defined, format-specific blocks take precedence.
The rdf DSL
Basic Model
class Concept < Lutaml::Model::Serializable
attribute :code, :string
attribute :name, :string
rdf do
namespace SkosNamespace
subject { |m| "http://example.org/concept/#{m.code}" }
type "skos:Concept"
predicate :notation, namespace: SkosNamespace, to: :code
predicate :prefLabel, namespace: SkosNamespace, to: :name
end
endThis single rdf block creates mappings for both :turtle and :jsonld formats automatically.
DSL Methods
namespace
Declares the RDF namespaces used by predicates. Accepts one or more Lutaml::Rdf::Namespace subclass references:
namespace SkosNamespace, DctermsNamespaceIn Turtle output, each namespace becomes an @prefix declaration. In JSON-LD output, each becomes a prefix entry in @context.
subject
Required for top-level resources. A block that generates the subject URI from the model instance:
subject { |m| "http://example.org/concept/#{m.code}" }type
Sets the RDF type (rdf:type). Accepts a single compact IRI or an array of compact IRIs. Compact IRIs are resolved via declared namespaces:
type "skos:Concept"
# resolves to <http://www.w3.org/2004/02/skos/core#Concept>
# multiple types:
type ["skos:Concept", "dcterms:Agent"]In Turtle, each type produces a separate a triple. In JSON-LD, a single type produces "@type": "skos:Concept" while multiple types produce an array "@type": ["skos:Concept", "dcterms:Agent"].
predicate
Each predicate creates a mapping between an RDF predicate and a model attribute:
predicate :prefLabel, namespace: SkosNamespace, to: :name, lang_tagged: trueParameters:
-
name— the local name in the namespace (e.g.,:prefLabel) -
namespace:— theLutaml::Rdf::Namespacesubclass (required) -
to:— the model attribute to read (required) -
lang_tagged:(default:false) — if true, values are serialized with language tags (see Language-Tagged Values). Mutually exclusive withuri_reference. -
uri_reference:(default:false) — if true, values are serialized as URI references rather than string literals (see URI Reference Predicates). Mutually exclusive withlang_tagged.
members
Declares that a container model contains member resources that should be serialized as separate subjects in the output graph (see Graph Serialization).
Optional linking predicate parameters generate relationship triples from the container to each member:
members :concepts, predicate_name: :member, namespace: SkosNamespaceWhen predicate_name and namespace are provided:
-
Turtle: produces
skos:member <child-uri>triples on the container subject -
JSON-LD: adds a
memberterm to@contextwith"@type": "@id"and{"@id": "…"}references in the container resource
Parameters:
-
attr_name— the model attribute holding the member collection (required) -
predicate_name:— the local name for the linking predicate (optional) -
namespace:— theLutaml::Rdf::Namespacesubclass for the linking predicate (required whenpredicate_nameis given)
Serialization
Turtle
concept = Concept.new(code: "2119", name: "component")
turtle = concept.to_turtleProduces:
@prefix skos: <http://www.w3.org/2004/02/skos/core#> .
<http://example.org/concept/2119> a skos:Concept;
skos:notation "2119";
skos:prefLabel "component" .JSON-LD
jsonld = concept.to_jsonldProduces:
{
"@context": {
"skos": "http://www.w3.org/2004/02/skos/core#",
"notation": "skos:notation",
"prefLabel": "skos:prefLabel"
},
"@type": "skos:Concept",
"@id": "http://example.org/concept/2119",
"notation": "2119",
"prefLabel": "component"
}The @context is auto-generated from the declared namespaces and predicates. No manual context definition is needed.
Language-Tagged Values
When a predicate is declared with lang_tagged: true, values are serialized with language information:
Turtle:
skos:prefLabel "component"@eng ;
skos:prefLabel "composant"@fra ;JSON-LD:
"prefLabel": { "eng": "component", "fra": "composant" }In JSON-LD, the @context auto-generates a language container:
"prefLabel": { "@id": "skos:prefLabel", "@container": "@language" }For this to work, the attribute’s values must include the Lutaml::Rdf::LanguageTagged module (or be a Lutaml::Rdf::Literal). A simple value object is sufficient:
class LocalizedLiteral < Lutaml::Model::Serializable
attribute :value, :string
attribute :language_code, :string
endURI Reference Predicates
Predicates declared with uri_reference: true serialize attribute values as URI references rather than string literals:
predicate :related, namespace: SkosNamespace, to: :related, uri_reference: trueTurtle:
Values are emitted as URI objects without quotes. Compact IRIs (e.g. "skos:other") are resolved to full URIs using the declared namespaces. Full URIs (e.g. "http://example.org/foo") are used as-is.
<http://example.org/concept/1> skos:related skos:other .JSON-LD:
Values are wrapped in {"@id": …} objects. The auto-generated @context includes a term definition with "@type": "@id":
{
"@context": {
"related": { "@id": "http://www.w3.org/2004/02/skos/core#related", "@type": "@id" }
},
"related": [ { "@id": "skos:other" } ]
}Round-trip fidelity: compact IRI forms are preserved through serialization and deserialization. Values round-trip as Concept.from_turtle(concept.to_turtle) preserving the original "skos:other" compact form.
The uri_reference option is mutually exclusive with lang_tagged.
Graph Serialization
When a container model holds a collection of member resources, use members to serialize them as separate subjects in the same RDF graph.
Container Model
class Vocabulary < Lutaml::Model::Serializable
attribute :id, :string
attribute :concepts, Concept, collection: true
rdf do
namespace SkosNamespace
subject { |v| "http://example.org/vocab/#{v.id}" }
type "skos:ConceptScheme"
predicate :prefLabel, namespace: SkosNamespace, to: :id
members :concepts,
predicate_name: :member,
namespace: SkosNamespace
end
endThe predicate_name and namespace parameters on members generate linking triples from the container to each member.
Turtle Output with Members
vocab = Vocabulary.new(id: "iso1087", concepts: [concept1, concept2])
puts vocab.to_turtleProduces a single Turtle document with the container and all member triples:
@prefix skos: <http://www.w3.org/2004/02/skos/core#> .
<http://example.org/vocab/iso1087> a skos:ConceptScheme;
skos:prefLabel "iso1087";
skos:member <http://example.org/concept/2119>;
skos:member <http://example.org/concept/2120> .
<http://example.org/concept/2119> a skos:Concept;
skos:notation "2119";
skos:prefLabel "component" .
<http://example.org/concept/2120> a skos:Concept;
skos:notation "2120";
skos:prefLabel "intension" .JSON-LD Output with Members
puts vocab.to_jsonldProduces a JSON-LD document with @graph containing all resources:
{
"@context": {
"skos": "http://www.w3.org/2004/02/skos/core#",
"prefLabel": { "@id": "skos:prefLabel" },
"notation": { "@id": "skos:notation" },
"member": { "@id": "http://www.w3.org/2004/02/skos/core#member", "@type": "@id" }
},
"@graph": [
{
"@id": "http://example.org/vocab/iso1087",
"@type": "skos:ConceptScheme",
"prefLabel": "iso1087",
"member": [
{ "@id": "http://example.org/concept/2119" },
{ "@id": "http://example.org/concept/2120" }
]
},
{
"@id": "http://example.org/concept/2119",
"@type": "skos:Concept",
"notation": "2119",
"prefLabel": "component"
},
{
"@id": "http://example.org/concept/2120",
"@type": "skos:Concept",
"notation": "2120",
"prefLabel": "intension"
}
]
}Architecture
The unified RDF infrastructure consists of:
-
Lutaml::Rdf::Mapping— Unified mapping base class withnamespace,subject,type,predicate, andmembersDSL methods -
Lutaml::Rdf::MappingRule— Value object for predicate-to-attribute mappings -
Lutaml::Rdf::MemberRule— Value object formembersdeclarations -
Lutaml::Rdf::Transform— Base transform class with shared logic for subject URI resolution, type resolution, and language extraction -
Lutaml::Turtle::Transform— Inherits fromRdf::Transform, produces Turtle -
Lutaml::JsonLd::Transform— Inherits fromRdf::Transform, produces JSON-LD
Both format-specific transforms detect Rdf::Mapping instances and dispatch to unified serialization logic.