In Islandora, the JSON-LD Module transforms nodes (or media, or taxonomy terms) into the RDF that is synced into Fedora and the Triplestore. It uses RDF mappings, a concept defined by the RDF Module, and exposes them through the REST API at ?_format=jsonld
.
A quick overview of JSON-LD, the RDF module, and the REST API.
JSON-LD is a syntax which can be used to express RDF (like Turtle, or RDF XML), that is written in JSON, because devs like JSON and it's web-friendly. The JSON-LD syntax was designed for including Linked Data within HTML of web pages (similar to microdata or RDFa). Instead of nesting the RDF predicates within existing HTML tags as RDFa does, JSON-LD lets you put a solid blob of Linked Data inside a <script>
tag. JSON-LD can also function as a standalone document, which is how we're using it.
The RDF Module is part of Drupal Core, but has no official documentation. The RDF Module embeds RDFa, a form of linked data, within the Drupal-generated HTML when you load the web page for a node, media, or taxonomy term. Official line is that this will allow Google to provide "rich snippets" such as star-ratings, contact info, and business hours. As an example of Drupal-provided RDFa:
<h1 class="page-header">
<span property="schema:title">My cat</span>
</h1>
The property="schema:title"
is markup generated by Drupal's RDF module that identifies the value "My cat" as the schema.org title
of this page. A node's fields (such as field_tags
) and properties (such as author
) can be mapped to RDF according to a bundle-specific "mapping" that is stored within Drupal. In Drupal8-ese, RDF mappings are configuration entities. Drupal doesn't have a good UI for editing RDF mappings, but you can create, read, and update them as YAML files using Drupal's Configuration Synchronization interface (see section below on How to Edit an RDF Mapping)..
The pattern of using ?_format=
to get a different representation of content is provided by the RESTful Web Services (rest) module. It allows other services to interact with Drupal entities through HTTP requests (GET
, POST
, PATCH
, and DELETE
). Which operations are allowed, and with what formats (such as xml
, json
, and jsonld
) is configured at admin/config/services/rest/
. Note that only jsonld
uses RDF mappings; the json
and xml
formats expose a structured object based on how Drupal sees the entity. Access to these alternate formats throught the REST API corresponds to permissions on the entity, so anyone with access content
permission can view the JSON-LD version of that content. This is new as of Drupal 8.2.
For more information on interacting with Drupal entities via REST requests, see An Introduction to RESTful Web Services in Drupal 8.
Using the RDF mapping configurations provided by the RDF module, the JSON-LD Module exposes the RDF-mapped entity in JSON-LD, through the REST API, at node/[nid]?_format=jsonld
(for nodes; for media and terms, at media/[mid]?_format=jsonld
and taxonomy/term/[tid]?_format=jsonld
).
- The JSON-LD module will only work with mappings that include a value under
types
(which maps tordf:type
- see below, under Structure of an RDF Mapping). - The JSON-LD module provides a hook so other modules can alter the entity before it gets mapped. The
islandora
module uses this hook to trigger any "Map URI to Predicate" and "Alter JSON-LD Type" reactions that are configured in Contexts.islandora_defaults
provides the two Contexts - "All Media" and "Content" - that configure these to occur on Media and Repository Item nodes. - The JSON-LD module adds RDF datatypes to the RDF values, and includes a mapping of Drupal field types to RDF datatypes.
- The JSON-LD module provides a hook to alter its Drupal field type to RDF datatype mapping.
- The JSON-LD module has a configuration option that can cause the
?_format=jsonld
to be part of, or not part of, the URIs of Drupal objects. On an out-of-the-box islandora-playbook, this string is stripped, but by default on a fresh install of the jsonld module, it is not.
In an out-of-the-box islandora-playbook, the RDF mappings that exist were loaded from config files, and correspond to the rdf.mapping.[...].yml files located in:
[drupal modules directory]/islandora/modules/islandora_core_feature/config/install/
(media and taxonomy terms)[drupal modules directory]/islandora_defaults/config/install/
(repository_item
and theislandora_access
vocabulary)[drupal modules directory]/controlled_access_terms/modules/controlled_access_terms_defaults/config/install/
(the defaultcorporate_body
,family
,geo_location
,person
,resource_type
andsubject
vocabularies)[drupal web root]/core/profiles/standard/config/install/
(articles, pages, comments, and tags).
Once loaded by modules, configuration .yml files are not live so editing them will not change the existing configuration. However, for modules that are Features, it is possible to re-import the changed configuration files at admin/config/development/features
(todo: link to further reading on Features).
Once loaded, RDF mappings can be customized for the needs of a particular site through Drupal's Configuration Synchronization UI at admin/config/development/configuration
. They can be exported, modified, and re-imported one-at-a-time by choosing the "Single Item" option on the Export/Import tabs. You can also create new RDF mappings (e.g. for a custom content type) and load them through this interface, by copying an existing mapping and changing the appropriate values.
!!! note "Contributed module for RDF Mappings"
A custom module rdfui
exists, and is installed-but-not-enabled on boxes provisioned by the islandora-playbook. We don't use it because it is very rudimentary and limited to the schema.org vocabulary. We have an open ticket to develop a UI to support RDF mappings to any ontology. Contributions welcome.
- A number of namespaces such as
ldp
,ebucore
,pcdm
, arepremis
are registered inislandora.module
usinghook_rdf_namespaces()
. To register your own namespaces, you will need to create a custom module that implements that hook. - If you import a configuration that uses a namespace that is not registered, bad things will happen silently.
Below is an example of an RDF mapping as a .yml (YAML) file. It is the current version of the RDF mapping of the Repository Item (islandora_object
) bundle, provided by islandora_defaults
and exportable as rdf.mapping.node.islandora_object.yml
).
- The top level key
types
specifies therdf:type
of the resource or content model.field_model
, a required field of Islandora objects, also gets mapped tordf:type
through an arcane back-end process. - The top level key
fieldMappings
specifies fields attached to that bundle and their RDF property mappings. One field can be mapped to more than one RDF property. It is a simple flat list.
mapping_type:
: There are several mapping types which are provided out of the box.
rel
- standing for relationship, expresses a relationship between two resourcesproperty
- the default, or if a relationship is not provided, expresses the relationship between a resource and some literal text.
datatype_callback
: This is a custom function that transforms the output of the field. There are some provided to us by Drupal and some added by Islandora, such as:
Drupal\controlled_access_terms\EDTFConverter::dateIso8601Value
which converts dates to Iso formatDrupal\jsonld\EntityReferenceConverter::linkFieldPassthrough
which converts a referenced entity to the URI on the entity (which is configurable with thelink_field
argument An example usage of theDrupal\jsonld\EntityReferenceConverter::linkFieldPassthrough
is as follows:
field_subject:
properties:
- 'dcterms:subject'
datatype_callback:
callable: 'Drupal\jsonld\EntityReferenceConverter::linkFieldPassthrough'
arguments:
link_field: 'field_authority_link'
Which would convert a reference to the subject's taxonomy term entity to a reference to the URI provided in field_authority_link
of that subject's taxonomy term entity.
langcode: en
status: true
dependencies:
config:
- node.type.islandora_object
enforced:
module:
- islandora_defaults
module:
- node
id: node.islandora_object
targetEntityType: node
bundle: islandora_object
types:
- 'pcdm:Object'
fieldMappings:
field_alternative_title:
properties:
- 'dc:alternative'
field_edtf_date:
properties:
- 'dc:date'
datatype_callback:
callable: 'Drupal\controlled_access_terms\EDTFConverter::dateIso8601Value'
field_edtf_date_created:
properties:
- 'dc:created'
datatype_callback:
callable: 'Drupal\controlled_access_terms\EDTFConverter::dateIso8601Value'
field_edtf_date_issued:
properties:
- 'dc:issued'
datatype_callback:
callable: 'Drupal\controlled_access_terms\EDTFConverter::dateIso8601Value'
field_description:
properties:
- 'dc:description'
field_extent:
properties:
- 'dc:extent'
field_identifier:
properties:
- 'dc:identifier'
field_member_of:
properties:
- 'pcdm:memberOf'
mapping_type: rel
field_resource_type:
properties:
- 'dc:type'
mapping_type: rel
field_rights:
properties:
- 'dc:rights'
field_subject:
properties:
- 'dc:subject'
mapping_type: rel
field_weight:
properties:
- 'co:index'
title:
properties:
- 'dc:title'
created:
properties:
- 'schema:dateCreated'
datatype_callback:
callable: 'Drupal\rdf\CommonDataConverter::dateIso8601Value'
changed:
properties:
- 'schema:dateModified'
datatype_callback:
callable: 'Drupal\rdf\CommonDataConverter::dateIso8601Value'
uid:
properties:
- 'schema:author'
mapping_type: rel
{
"@graph":[
{
"@id":"http:\/\/future.islandora.ca\/node\/8",
"@type":[
"http:\/\/pcdm.org\/models#Object"
],
"http:\/\/purl.org\/dc\/terms\/title":[
{
"@value":"lasmomias de uninpahu",
"@language":"fa"
}
],
"http:\/\/schema.org\/author":[
{
"@id":"http:\/\/future.islandora.ca\/en\/user\/1"
}
],
"http:\/\/schema.org\/dateCreated":[
{
"@value":"2019-06-04T14:32:05+00:00",
"@type":"http:\/\/www.w3.org\/2001\/XMLSchema#dateTime"
}
],
"http:\/\/schema.org\/dateModified":[
{
"@value":"2019-06-04T17:02:51+00:00",
"@type":"http:\/\/www.w3.org\/2001\/XMLSchema#dateTime"
}
],
"http:\/\/purl.org\/dc\/terms\/description":[
{
"@value":"mpermmbklmh",
"@language":"fa"
}
],
"http:\/\/purl.org\/dc\/terms\/created":[
{
"@value":"2015-10-15",
"@type":"http:\/\/www.w3.org\/2001\/XMLSchema#string"
},
{
"@value":"2015-10-15",
"@type":"http:\/\/www.w3.org\/2001\/XMLSchema#date"
}
],
"http:\/\/purl.org\/dc\/terms\/extent":[
{
"@value":"1 item",
"@type":"http:\/\/www.w3.org\/2001\/XMLSchema#string"
}
],
"http:\/\/pcdm.org\/models#memberOf":[
{
"@id":"http:\/\/future.islandora.ca\/node\/7"
}
],
"http:\/\/purl.org\/dc\/terms\/type":[
{
"@id":"http:\/\/future.islandora.ca\/taxonomy\/term\/3"
}
],
"http:\/\/purl.org\/dc\/terms\/subject":[
{
"@id":"http:\/\/future.islandora.ca\/taxonomy\/term\/27"
}
]
},
{
"@id":"http:\/\/future.islandora.ca\/en\/user\/1",
"@type":[
"http:\/\/schema.org\/Person"
]
},
{
"@id":"http:\/\/future.islandora.ca\/node\/7",
"@type":[
"http:\/\/pcdm.org\/models#Object"
]
},
{
"@id":"http:\/\/future.islandora.ca\/taxonomy\/term\/3",
"@type":[
"http:\/\/schema.org\/Thing"
]
},
{
"@id":"http:\/\/future.islandora.ca\/taxonomy\/term\/27",
"@type":[
"http:\/\/schema.org\/Thing"
]
}
]
}