Let’s start by repeating some definitions:
A schema is a machine-readable document that describes a data object.
An overlay modifies a schema by adding or changing semantic annotations for schema attributes, so the schema can be adjusted for a different use case.
A schema variant is a schema composed of a schema base and zero or more overlays. Note that a schema base itself is a schema variant.
In many applications, data objects are interconnected. Thus, a schema
defining a data object have to refer to other schemas defining the
connected objects. LSA represents such references using the
Reference
attribute type. For example, an application that deals
with Person
objects may keep the contact information for each
Person
as an array of Contact
objects. In this scenario, we have
two objects: a Person
that has an array field whose elements are
references to a Contact
object. This example can be seen in Github
repository.
The Person
schema is:
{
"@context": "https://layeredschemas.org/ls.json",
"@id": "http://example.org/Person/schemaBase",
"@type": "Schema",
"valueType": "https://example.org/Person",
"layer": {
"@type": "Object",
"@id": "http://example.org/Person",
"attributes": {
"http://example.org/Person/firstName": {
"@type": "Value",
"attributeName":"firstName"
},
"http://example.org/Person/lastName": {
"@type": "Value",
"attributeName": "lastName"
},
"http://example.org/Person/contact": {
"@type": "Array",
"attributeName": "contact",
"arrayElements": {
"@type": "Reference",
"@id": "http://example.org/Person/contact/items",
"ref": "https://example.org/Contact"
}
}
}
}
}
And the Contact
schema is:
{
"@context": "https://layeredschemas.org/ls.json",
"@id": "http://example.org/Contact/schema",
"@type": "Schema",
"valueType": "https://example.org/Contact",
"layer": {
"@type": "Object",
"@id": "http://example.org/Contact",
"attributes": {
"http://example.org/Contact/value": {
"@type": "Value",
"attributeName": "value"
},
"http://example.org/Contact/type": {
"@type": "Value",
"attributeName": "type"
}
}
}
}
When dealing with Person
data, we need access to both the Person
schema and the Contact
schema. Also, when working with a Person
schema variant, we have to use a matching Contact
variant. A
bundle connects those objects for a use case by defining the
schema variants for each schema in question. We may use a different
bundle that links different variants of Person
and Contact
for a
different use case.
For example, person.bundle.json
looks like the JSON file below. It
defines the https://example.org/Person
variant using
person.schema.json
file. This particular schema variant will be used
to ingest a Person
object. The Contact
s of a Person
will be
ingested using the variant specified in the same bundle.
{
"typeNames" : {
"https://example.org/Person": {
"schema" : "person.schema.json"
},
"https://example.org/Contact": {
"schema": "contact.schema.json"
}
}
}
Now let’s say we would like to add data privacy vocabulary terms to the underlying schemas. We introduce two overlays for this:
The person-dpv.overlay.json
defines the Person
object as an
instance of dpv:DataSubject
, and adds dpv:Name
and
dpv:Identifying
annotations to the firstName
and lastName
fields:
{
"@context": [
"https://layeredschemas.org/ls.json",
{
"dpv": "http://www.w3.org/ns/dpv#",
"hasPersonalDataCategory": {
"@id":"dpv:hasPersonalDataCategory",
"@type":"@id"
}
}
],
"@id": "http://example.org/Person/dpv",
"@type": "Overlay",
"valueType": "https://example.org/Person",
"layer": {
"@type": [ "Object","dpv:DataSubject"],
"@id": "http://example.org/Person",
"attributes": [
{
"@id": "http://example.org/Person/firstName",
"@type": "Value",
"hasPersonalDataCategory": [ "dpv:Name", "dpv:Identifying" ]
},
{
"@id": "http://example.org/Person/lastName",
"@type": "Value",
"hasPersonalDataCategory": [ "dpv:Name", "dpv:Identifying" ]
}
]
}
}
Similarly, the contact-dpv.overlay.json
adds dpv:TelephoneNumber
and dpv:Identifying
annotations to the value
field of the contact.
{
"@context":[ "https://layeredschemas.org/ls.json",
{
"dpv": "http://www.w3.org/ns/dpv#",
"hasPersonalDataCategory": {
"@id":"dpv:hasPersonalDataCategory",
"@type":"@id"
}
}
],
"@id": "http://example.org/Contact/ovl1",
"@type": "Overlay",
"valueType": "https://example.org/Contact",
"layer": {
"@type": "Object",
"@id": "http://example.org/Contact",
"attributes": {
"http://example.org/Contact/value": {
"@type": "Value",
"hasPersonalDataCategory": [ "dpv:TelephoneNumber", "dpv:Identifying" ]
}
}
}
}
We can define a new person-dpv.bundle.json
by adding these overlays:
{
"typeNames" : {
"https://example.org/Person": {
"schema" : "person.schema.json",
"overlays" : [
{
"schema" : "person-dpv.overlay.json"
}
]
},
"https://example.org/Contact": {
"schema": "contact.schema.json",
"overlays" : [
{
"schema" : "contact-dpv.overlay.json"
}
]
}
}
}
Alternatively, we can define a new bundle based on another one. This new bundle only adds new overlays to the base bundle.
{
"base": "person.bundle.json",
"typeNames" : {
"https://example.org/Person": {
"overlays" : [
{
"schema" : "person-dpv.overlay.json"
}
]
},
"https://example.org/Contact": {
"overlays" : [
{
"schema" : "contact-dpv.overlay.json"
}
]
}
}
}