- OO-LD Schema
- Appendix
The Object Oriented Linked Data Schema based on JSON-LD and JSON-SCHEMA - work in process!
OO-LD Schema aims to connect the structural modelling of objects and subobjects with the modelling of the semantic relations without reinventing the wheel. It therefor combines existing standards, primary JSON-SCHEMA and a JSON-LD context in the same document.
Why OO-LD?
- OO-LD schema documents are supported by a wide range of existings tools (all JSON-SCHEMA and JSON-LD tooling!)
- OO-LD schema documents themself follow linked data principles to make them retrievable over the web to allow flexible schema compositions
- OO-LD schemas allow generic ex- and import of RDF
- OO-LD schemas are compatible with LLM APIs
- OO-LD schemas can be used as function (dataclasses) and API signatures (OpenAPI)
- OO-LD schemas can be used to define graphical user interfaces, in particular forms
JSON, JSON-SCHEMA and JSON-LD technology stack with OpenSemanticLab as example document store / platform
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119.
An OO-LD document is always a valid JSON document. This ensures that all of the standard JSON libraries work seamlessly with OO-LD documents.
An OO-LD instance document is always a valid JSON-LD document. This ensures that all of the standard JSON-LD libraries work seamlessly with OO-LD instance documents.
An OO-LD schema document is always both a valid JSON-SCHEMA document and JSON-LD remote context. This ensures that all of the standard JSON-SCHEMA and JSON-LD libraries work seamlessly with OO-LD schema documents.
A OO-LD schema document allows the developer to express the syntax of a JSON instance document side by side with its semantics in a single source.
In addition, syntactical and semantic definitions can also be applied to referenced external JSON instance documents.
This allows to specify well-defined patterns in a directed graph and enables tools relying on a hierarchical object structure to produce data for and consume data from such a graph.
OO-LD schema documents allow to specify all information that is needed to automatically transform data between semantically equivalent but syntactically different notations.
If you are not familiar yet with JSON-SCHEMA or JSON-LD you should first have a look at dedicated tutorials like OSW JSON-SCHEMA Tutorial and OSW JSON-LD Tutorial.
The core idea is that an OO-LD document is always both a valid JSON-SCHEMA and a reference-able JSON-LD remote context as defined in JSON-LD v1.1 section 3.1 ( != JSON-LD document). In this way a complete OO-LD class / schema hierarchy is consume-able by JSON-SCHEMA-only and JSON-LD-only tools while OO-LD aware tools can provide extended features on top (e.g. UI autocomplete dropdowns for string-IRI fields based e.g. on a SPARQL backend, SHACL shape or JSON-LD frame generation).
A minimal example:
{
"@context": {
"schema": "http://schema.org/",
"name": "schema:name"
},
"title": "Person",
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "First and Last name"
}
}
}You can explore this in the interactive playground
Please note that OO-LD schema documents MUST NOT be interpreted as JSON-LD documents because this would apply @context on the schema itself. The motivation behind this is to have a single document so schemas can be aggregated using both the JSON-SCHEMA $ref and the JSON-LD remote @context pointing the same resource.
%%{init: {'theme': 'neutral' } }%%
classDiagram
class OOLD_Class_A {
JSON-SCHEMA + JSON-LD
@context: ...
properties: a...
}
class OOLD_Class_B {
JSON-SCHEMA + JSON-LD
@context: ./A
allOf: ./A
properties: b...
}
class OOLD_Instance_B {
JSON
@context: ./B
$schema: ./B
a: ...
b: ...
}
OOLD_Class_A <-- OOLD_Class_B: extends
OOLD_Class_B <-- OOLD_Instance_B: type
You can read how this is implemented in OpenSemanticWorld/Lab in the introduction and schema documentation draft.
It MUST NOT be require to further process an OO-LD Schema document in order to interpret it as JSON-LD context. This implies that all occurrences of $ref in the schema are reflected in the JSON-LD context. $ref within properties of type: object MUST be listed as scoped JSON-LD context. $ref within all other property types and at the root level of the OO-LD schema MUST be listed at the root level of the JSON-LD context. In case of multiple $ref within allOf it lies within the responsibility of the importing OO-LD schema to ensure correctness of the merged remote JSON-LD context.
This SHOULD NOT be applied to oneOf and anyOf since conflicts are more likely.
At any time the importing OO-LD schema can define its own or override the imported JSON-LD context.
"@context"
- B.schema.json
- P1.schema.json
- p1:
"@context": P1.schema.json
- p2:
"@context":
- P2a.schema.json
- P2b.schema.json
- p3:
"@context":
keyword_in_P3a: ex:Property1
keyword_in_P3b: ex:Property2
$id: A.schema.json
allOf:
- $ref: B.schema.json
properties:
p0:
type: string
$ref: P0.schema.json
p1:
type: object
$ref: P1.schema.json
p2:
type: object
allOf:
$ref: P2a.schema.json
$ref: P2b.schema.json
p3:
oneOf:
$ref: P3a.schema.json
$ref: P3b.schema.jsonFull example
"@context":
name: ex:petName
$id: Pet.schema.json
properties:
name:
type: string"@context":
name: schema:name
pets:
"@id": ex:hasPet
"@context": Pet.schema.json
$id: Person.schema.json
properties:
name:
type: string
pets:
type: array
items:
$ref: Pet.schema.json"@context": Person.schema.json
$schema: Person.schema.json
name: Max
pets:
- name: Bruno"@context":
name: ex:name
pets:
"@id": ex:hasPet
"@context":
name: ex:petName
$schema: Person.schema.json
name: Max
pets:
- name: Bruno_:b0 <ex:hasPet> _:b1 .
_:b0 <schema:name> "Max" .
_:b1 <ex:petName> "Bruno" .https://json-schema.org/draft/2020-12/json-schema-core#name-linking-to-a-schema https://json-schema.org/draft/2020-12/json-schema-core#name-the-schema-keyword
"@context": Person.schema.json
$schema: Person.schema.jsonOO-LD schemas MUST have a $id (see https://json-schema.org/draft/2020-12/json-schema-core#section-8.2.1) which works as a global and unique identifier of the schema. The value of $id MAY be a absolute URI (details below). The schema SHOULD be resolveable via this URI. The schema SHOULD have a annotation x-oold-uuid with an UUID value.
$id: https://example.org/Foo.schema.json
x-oold-uuid: b5203131-7321-46bb-8a11-acb3d1015840
title: FooIt is recommended to use the UUID also in the $id:
$id: https://example.org/b5203131-7321-46bb-8a11-acb3d1015840.schema.json
x-oold-uuid: b5203131-7321-46bb-8a11-acb3d1015840
title: Foosee also
The schema version SHOULD be indicated by x-oold-version, a prior version MAY be indicated with oold-prior-version
$id: https://example.org/b5203131-7321-46bb-8a11-acb3d1015840.schema.json
x-oold-uuid: b5203131-7321-46bb-8a11-acb3d1015840
title: Foo
x-oold-version: 1.1.0
x-oold-prior-version: 1.0.0For single schema versioning version SHOULD be part of the $id the version SHOULD be appended, e.g.
https://example.org/b5203131-7321-46bb-8a11-acb3d1015840.schema.json/1.1.0
For schema package versioning (recommended) the version of the package SHOULD be prepended before the schemas ID, e.g.
https://example.org/my-package/2.0.0/b5203131-7321-46bb-8a11-acb3d1015840.schema.json
or using release tags on github:
https://raw.githubusercontent.com/MyOrg/my-package/refs/heads/2.0.0/b5203131-7321-46bb-8a11-acb3d1015840.schema.json
Note: Since a package combines multiple schemas the packages version does in general not match the schema version.
Schemas MAY indicate explicit backward-compatibility:
$id: https://example.org/my-package/2.1.0/b5203131-7321-46bb-8a11-acb3d1015840.schema.json
x-oold-uuid: b5203131-7321-46bb-8a11-acb3d1015840
title: Foo
x-oold-version: 1.1.0
x-oold-prior-version: 1.0.0
backward-compatible-with: https://example.org/my-package/2.0.0/b5203131-7321-46bb-8a11-acb3d1015840.schema.json
incompatible-with: https://example.org/my-package/1.0.0/b5203131-7321-46bb-8a11-acb3d1015840.schema.jsonSchemas within a package or package repository may use relative URIs (see https://www.rfc-editor.org/rfc/rfc3986#section-5.1), e.g.
https://raw.githubusercontent.com/MyOrg/my-package/refs/heads/2.0.0/A.schema.json
has the content
$id: B.schema.json
title: Foo
allOf:
- $ref: A.schema.jsonwhich expands to
$id: https://raw.githubusercontent.com/MyOrg/my-package/refs/heads/2.0.0/B.schema.json
title: Foo
allOf:
- $ref: https://raw.githubusercontent.com/MyOrg/my-package/refs/heads/2.0.0/A.schema.jsonInstance document SHOULD always use a versioned schema url to make clear with which schema version they comply, e.g.
"@context": https://example.org/my-package/1.0.0/b5203131-7321-46bb-8a11-acb3d1015840.schema.json
$schema: https://example.org/my-package/1.0.0/b5203131-7321-46bb-8a11-acb3d1015840.schema.jsonUpgrade-APIs MAY provide automated data migration between schema (package) versions, e.g. https://example.org/upgrade/my-package/1.0.0...2.0.0
Current support covers [v1.1] (https://www.w3.org/TR/json-ld/).
JSON-LD allows only a single keyword-IRI mapping (or more precisely, ignores all but the last mapping). Currently there is no way to express that a property has two ids (e. g. with "label": {"@id": ["schema:name", "skos:prefLabel"]}, see also json-ld/json-ld.org#160). As a workaround, an additional context notation is provided: <property>*(*) pointing to additional @id mappings to provide at least a documentation for alternative options or custom RDF generation.
see also: #12
{
"@context": [
{
"@version": 1.1,
"skos": "https://www.w3.org/TR/skos-reference/",
"schema": "https://schema.org/",
"label": "skos:prefLabel",
"label*": "schema:name",
"label**": "..."
}
],
"label": "test"
}Default JSON-LD processing would only interpret the preferred mapping:
_:b0 <skos:prefLabel> "test" .An OO-LD aware convert could also produce redundant triples for interoperability reasons:
_:b0 <skos:prefLabel> "test" .
_:b0 <schema:name> "test" .Furthermore, this notation can be used for data transformation and normalization As an example a dataset could consist of persons and organisations that report there relations in a syntactically inoperable way:
"@graph":
- id: demo:person1
type: schema:Person
name: Person1
works_for: demo:organizationA # forward relation
works_for*: demo:organizationB # forward relation but different property
- id: demo:organizationA
type: schema:Organization
- id: demo:organizationB
type: schema:Organization
- id: demo:organizationC
type: schema:Organization
employes: demo:person1 # backwards relationNormalizing would lead to a consistent unified dataset. For more information see #11
"@graph":
- employes:
- demo:person1
- demo:person2
- demo:person3
id: demo:organizationA
label:
- lang: en
text: organizationA
type: schema:Organization
- id: demo:person1
name: Person1
type: schema:Person
- id: demo:person2
name: Person2
type: schema:Person
- id: demo:person3
name: Person3
type: schema:PersonCurrent support covers [Draft 4] (https://json-schema.org/specification-links#draft-4).
Keywords title and description can be extended with additional keywords title* and description*, which hold and object with lang-keys (de, en, etc.) pointing to the translated strings.
Mapping of title*[lang] must be provided by schema preprocessing.
{
"title": "Default Title",
"title*": {"en": "Title (en)", "de": "Titel (de)"}
}JSON-SCHEMA itself supports linked data only in form of subobject. References to independent external object are just URL-strings without any further restrictions. To express constrains on the type of the object as we know it from OWL and SHACL the keyword range is introduced (see also json-schema-org/json-schema-vocabularies#55). Note: Same as $ref, range must point to a resolvable resource.
range is an IRI. While this is concise, there's no way to express unions and intersections of multiple schemas.
{
"@context": {
"schema": "http://schema.org/",
"works_for": "schema:worksFor"
},
"title": "Person",
"type": "object",
"properties": {
"works_for": {
"type": "string",
"range": "schema:Organization",
"description": "IRI pointing to an instance of schema:Organization",
}
}
}range is an OO-LD schema. By using ...Of keywords, unions (anyOf / oneOf) and intersections (allOf) of multiple schemas can be expressed.
This will allow the following constellations:
"range": {
"@context": {
"schema": "http://schema.org/",
"type": "@type"
},
"properties": {
"type": {
"type": "string",
"const": "schema:Organization",
}
}
}"range": {
"allOf": {
"$ref": "Organization.schema.json"
}
}Full Example:
{
"@context": {
"schema": "http://schema.org/",
"works_for": "schema:worksFor",
"type": "@type"
},
"$id": "Person.schema.json",
"title": "Person",
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "schema:Person",
},
"works_for": {
"type": "string",
"range": {
"allOf": {
"$ref": "Organization.schema.json"
}
},
"description": "IRI pointing to an instance of schema:Organization",
}
}
}{
"@context": {
"schema": "http://schema.org/",
"type": "@type"
},
"$id": "Organization.schema.json",
"title": "Organization",
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "schema:Organization",
}
}
}On the downside, range may build up large schema graphs with circular paths which can raise issues in JSON-SCHEMA bundlers following all $ref relations. However, [JSON-SCHEMA]{#JSONSCHEMA202012} recommends standard bundlers not to follow $ref within custom keywords in section 9.4.2. See also https://json-schema.org/blog/posts/bundling-json-schema-compound-documents.
There are many cases were relations are summetric, e.g. Organization employees Person <=> Person worksFor Organization.
However, usually we do not want to store this information in different schemas but allow users to edit it from both sides.
For this usecase the additional keywords x-oold-reverse-properties, x-oold-reverse-default-properties and x-oold-reverse-required are introduced
To make employees the reverse property of organization we have to
- define
employeesin the schema sectionx-oold-reverse-propertiesof Organization - define
works_forin the schema sectionx-oold-reverse-propertiesof Person - map
employeesto a semantic property, e.g.schema:worksForin the@contextof Person - map
employeeswith@reversein the@contextof Organization to the same property, compliant to JSON-LD @reverse
Example:
{
"@context": [
{
"employees": {
"@reverse": "schema:worksFor",
"@type": "@id"
}
}
],
"title": "Organizational",
"type": "object",
"required": [
"type"
],
"properties": {
"...": {}
},
"x-oold-reverse-required": [],
"x-oold-reverse-defaultProperties": [
"employees"
],
"x-oold-reverse-properties": {
"employees": {
"type": "array",
"title": "Employees",
"items": {
"type": "string",
"format": "autocomplete",
"title": "Person",
"range": "Person.schema.json"
}
}
}
}Organization.schema.json
{
"@context": [
{
"organization": {
"@id": "schema:worksFor",
"@type": "@id"
}
}
],
"title": "Person",
"defaultProperties": [
"organization"
],
"properties": {
"organization": {
"title": "Organization",
"description": "Organization(s) the person is affiliated with. E.g., university, research institute, company, etc.",
"type": "array",
"items": {
"type": "string",
"title": "Organization",
"format": "autocomplete",
"range": "Organization.schema.json"
}
}
}
}Person.schema.json
An OO-LD aware implementation can make use of this annotation to allow to read and modify properties that are actualle stored in another object. E.g., When loading a UI editor for an Organization, the editor will prepopulate the field employees by executing the query "Which persons work for this organization"?
When storing an Organization, the editor will also load the Persons referenced in employeesand stores the current Organization in their organization field, following the @context mappings of both schemas.
Deleting a Person in employees will also delete the Organization from the corresponding field.
Additional keywords defined by JSON-SCHEMA Editor, see Basic features and Further details
In general we want to keep keywords in 'instance' JSON-documents (=> property names in schemas) strict ^[A-z_]+[A-z0-9_]*$ to avoid escaping or replacing when mapping to other languages. This works well with aliasing, e.g.
{
"@context": {
"ex": "https://example.org/",
"schema": "http://schema.org/",
"name": "schema:name",
"type": "@type"
},
"iri": "ex:RawData",
"title": "Person",
"type": "object",
"properties": {
"type": {
"type": "string",
"default": "schema:Person"
},
"name": {
"type": "string",
"description": "First and Last name"
}
}
}The Person schema above translates smoothly to python (pydantic) via https://github.com/koxudaxi/datamodel-code-generator:
class Person(BaseModel):
model_config = ConfigDict(
json_schema_extra={
"@context": {
"ex": "https://example.org/",
"schema": "https://schema.org/",
"name": "schema:name",
"type": "@type"
},
"iri": "ex:RawData", # the IRI of the class
}
)
type: Optional[str] = "schema:Person"
name: Optional[str] = None
"""First and Last name"""what would not be the case if we use @type or schema:name as property names (See also python playground).
From pydantic it's also straight forward to (re)generate OO-LD and OpenAPI-Schemas, especially via FastAPI.
A commmon ground for workflow definitions are decorated dataclass-typed functions that are managed by a workflow-environment like prefect.
@flow
def my_node(param: MyInputClass) -> MyOutputClass:
...
return MyOutputClass(...)If these dataclasses are following OO-LD annoations as described above the semantics of the workflow (node) is inherently contained.
In this regard, OO-LD can be combined with standard code compiler/interpreter tooling, especially Abstract Syntax Trees and tracing provide a semantic description of software-defined workflows. More information see AWL
Recent support of Large Language Models (LLMs) for structured output is based on JSON-SCHEMA. This allows the direct application of OO-LD schemas with LLMs in order to generate, complete or validate structured data. Example usecases see osw-chatbot
- Python: oold-python
- Javascript Framework for graph visualization and editing: interactive-semantic-graph
- Fully integrated platform (currently) based on Semantic Mediawiki: docker-compose, Demo
- LLM Integration
- UI & RDF Generation
- Python Code Generation
- Python Class Annotation & UI Generation
- Semantic Workflow Description
- Human-in-the-Loop UI Workflow
| Slot | File extension (recommended) | Media type | RFC6906 profile | Description |
|---|---|---|---|---|
| schema | *.schema.json | application/oold-schema+json |
- | Full OO-LD schema |
| schema | *.schema.json | application/oold-schema+json |
oold-schema#bundled | Full OO-LD schema with all $ref and remote context bundled |
| schema | *.schema.json | application/oold-schema+json |
http://www.w3.org/ns/json-ld#context | Only the JSON-LD context |
| schema | *.schema.json | application/ld+json |
- | Only the JSON-LD context |
| schema | *.schema.json | application/schema+json |
- | Only the JSON-SCHEMA schema |
| data | *.data.json | application/oold-schema-instance+json |
- | Full OO-LD instance |
| data | *.data.json | application/ld+json |
http://www.w3.org/ns/json-ld#* | Full OO-LD instance. Profiles defined in https://www.w3.org/TR/json-ld/#iana-considerations apply |
| data | *.data.json | application/json |
- | Only the JSON data |
Both security consideration of JSON-LD v1.1 section C and JSON-SCHEMA 2020-12 section 13 apply.
- OpenSemanticWorld Package Registry, deployed e. g. OpenSemanticWorld
- In the context of YAML-LD: json-ld/yaml-ld#19
| RFC 2119 | Bradner, S., "Key words for use in RFCs to Indicate Requirement Levels", BCP 14, RFC 2119, DOI 10.17487/RFC2119, March 1997, https://www.rfc-editor.org/info/rfc2119. |
| RFC 8259 | Bray, T., Ed., "The JavaScript Object Notation (JSON) Data Interchange Format", STD 90, RFC 8259, DOI 10.17487/RFC8259, December 2017, https://www.rfc-editor.org/info/rfc8259. |
| JSON-LD | https://www.w3.org/TR/2020/REC-json-ld11-20200716/ |
| JSON-SCHEMA | https://json-schema.org/draft/2020-12/json-schema-core |
| W3C.REC-ldp-20150226 | Speicher, S., Arwe, J., and A. Malhotra, "Linked Data Platform 1.0", World Wide Web Consortium Recommendation REC-ldp-20150226, 26 February 2015, https://www.w3.org/TR/2015/REC-ldp-20150226. |
| RFC 7049 | Bormann, C. and P. Hoffman, "Concise Binary Object Representation (CBOR)", RFC 7049, DOI 10.17487/RFC7049, October 2013, https://www.rfc-editor.org/info/rfc7049. |
OO-LD as bridge between linked data and the general software domain
| Name | Description |
|---|---|
| JSON-SCHEMA | Base of this work. Does not include linked data concepts. |
| JSON-LD | Base of this work. Does not restrict the structure of a json file. |
| OWL | Focus on logical modelling. Only applicable to RDF. |
| SHACL | Only applicable to RDF. |
| Asset Administration Shell | Industry 4.0 related data schema for assets |
| Semantic Aspect Meta Model | SHACL subset / nested object-property schema written in turtle, e. g. used for the data models in Catena-X |
| SmartDataModels | JSON-SCHEMA defined data models used by FIWARE Foundation, TM Forum, OASC and IUDX |
| Common Data Model | Business related data models developed by Microsoft |
| Upper | Nested object-property schema written in turtle, similar to SAMM, developed by Netflix |
| LinkML | Custom schema language focussed on data modelling. Both importers and exporters to JSON-SCHEMA (and others) exists. Custom annotations for UI generation not (yet) supported (see #1618). |
| TreeLDR | Custom linked data schema language that can be converted to JSON-SCHEMA, JSON-LD context, RDF and Rust code |
| REST-API-LD | Annotated OpenAPI schemas with rendering support in Swagger-UI. Option to generate it from OO-LD. |
| dlite | Custom schema language focussed on scientific data |
| NOMAD | Custom schema language focussed on scientific data |
| Human Cell Atlas | Data schemas for the biology and medical domain |
| OTTR | Mixture of custom template and schema language. Limited toolset to convert from/to other formats. |
| TheWorldAvatar/ObjectGraphMapper | Class-RDF Mapping in Java via decorators |
| Cross-Domain Interoperability Framework (CDIF) | CDIF is a set of implementation recommendations, based on profiles of common, domain-neutral metadata standards which are aligned to work together to support core functions required by FAIR. |
| DDI-CDI | Cross domain meta data model between domain specific specifications and high - level specifications such as DCAT and Datacite |
| Name | Description |
|---|---|
| BatteryKnowledgeGraph | Battery related linked data set |
Asset Administion Shell combines schema and data in a single documents. Semantics are introduced by annotations keywords.
- assetInformation:
assetKind: Instance
globalAssetId: test
id: https://example.org/Simple_AAS
modelType: AssetAdministrationShell
submodels:
- keys:
- type: Submodel
value: https://example.org/Simple_Submodel
type: ModelReference
- id: https://example.org/Simple_Submodel
modelType: Submodel
submodelElements:
- idShort: ExampleProperty
modelType: Property
semanticId:
keys:
- type: GlobalReference
value: http://example.org/Properties/SimpleProperty
type: ExternalReference
value: exampleValue
valueType: xs:stringAAS
- id: https://example.org/Simple_AAS
x-aas-modelType: AssetAdministrationShell
- id: https://example.org/Simple_Submodel
@context:
ExampleProperty: http://example.org/Properties/SimpleProperty
x-aas-modelType: Submodel
allOf:
$ref: https://example.org/Simple_AAS
properties:
ExampleProperty:
type: string
default: exampleValue # works like a templateOO-LD Schemas
@context: https://example.org/Simple_Submodel
$schema: https://example.org/Simple_Submodel
ExampleProperty: exampleValueData
Semantic Aspect Meta Model (SAMM) is a lightweight language to model (partial) objects (aspects) and their properties. While building on RDF and using turtle as serialization SAMM forms tree like structures like JSON-SCHEMA. Instead of IRIs, Ressources are identified with URNs which are not meant to be resolveable in the sense of linked data / semantic web.
Example (see AddressAspect.ttl for an address, stripping everything but the post code attribute):
@prefix samm: <urn:samm:org.eclipse.esmf.samm:meta-model:2.1.0#> .
@prefix samm-c: <urn:samm:org.eclipse.esmf.samm:characteristic:2.1.0#> .
@prefix samm-e: <urn:samm:org.eclipse.esmf.samm:entity:2.1.0#> .
@prefix unit: <urn:samm:org.eclipse.esmf.samm:unit:2.1.0#> .
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
@prefix : <urn:samm:io.catenax.shared.address_characteristic:4.0.0#> .
:AddressAspect a samm:Aspect ;
samm:preferredName "Address Aspect"@en ;
samm:description "Aspect used for the Characteristic :PostalAddress to reference address data."@en ;
samm:properties ( :address ) ;
samm:operations ( ) ;
samm:events ( ) .
:address a samm:Property ;
samm:preferredName "Address"@en ;
samm:description "The address of the data provider."@en ;
samm:characteristic :PostalAddress .
:PostalAddress a samm:Characteristic ;
samm:preferredName "PostalAddress"@en ;
samm:description "A characteristic to express the postal address and which is intended to be referenced by other aspects."@en ;
samm:dataType :AddressEntity .
:AddressEntity a samm:Entity ;
samm:preferredName "Address Entity"@en ;
samm:description "Entity of an address. Model follows specification of BPDM (Business Partner Data Management)."@en ;
samm:properties ( :thoroughfare :locality [ samm:property :premise; samm:optional true ] [ samm:property :postalDeliveryPoint; samm:optional true ] :country :postCode ) .
:postCode a samm:Property ;
samm:preferredName "Post Code"@en ;
samm:description "Postal code of the address."@en ;
samm:characteristic :PostCodeCharacteristic .
:PostCodeCharacteristic a samm-c:SingleEntity ;
samm:preferredName "PostCode Characteristic"@en ;
samm:description "Characteristic for defining a postcode which can consist of a type (e.g. \"REGULAR\" for zip codes) and a value (e.g. \"98765-4321\"). Model follows the specification of BPDM."@en ;
samm:dataType :PostCodeEntity .
:PostCodeEntity a samm:Entity ;
samm:preferredName "PostCode Entity"@en ;
samm:description "Entity for a postcode which consists of a type plus a value."@en ;
samm:properties ( [ samm:property :postCodeValue; samm:payloadName "value" ] [ samm:property :postCodeTechnicalKey; samm:payloadName "technicalKey" ] ) .
:postCodeValue a samm:Property ;
samm:preferredName "Post Code Value"@en ;
samm:description "The value of a post code."@en ;
samm:characteristic :PostCodeTrait ;
samm:exampleValue "98765-4321" .
:postCodeTechnicalKey a samm:Property ;
samm:preferredName "Post Code Technical Key"@en ;
samm:description "The technical key of a post code."@en ;
samm:characteristic :PostCodeTechnicalKeyCharacteristic .
:PostCodeTrait a samm-c:Trait ;
samm-c:baseCharacteristic samm-c:Text ;
samm-c:constraint :PostCodeConstraint .
:PostCodeTechnicalKeyCharacteristic a samm-c:Enumeration ;
samm:preferredName "Post Code Technical Key Characteristic"@en ;
samm:description "Characteristic for the technical key of a post code."@en ;
samm:dataType xsd:string ;
samm-c:values ( "CEDEX" "LARGE_MAIL_USER" "OTHER" "POST_BOX" "REGULAR" ) .SAMM
{
"@context": {
"type": "@type",
"PostalAddress": "urn:samm:io.catenax.shared.address_characteristic:4.0.0#PostalAddress",
"postCode": "urn:samm:io.catenax.shared.address_characteristic:4.0.0#postCode",
"PostCodeEntity": "urn:samm:io.catenax.shared.address_characteristic:4.0.0#PostCodeEntity",
"value": "urn:samm:io.catenax.shared.address_characteristic:4.0.0#postCodeValue",
"technicalKey": "urn:samm:io.catenax.shared.address_characteristic:4.0.0#postCodeTechnicalKey"
}
"description" : "A characteristic to express the postal address and which is intended to be referenced by other aspects.",
"type" : "object",
"properties" : {
"type": {
"const": "PostalAddress"
},
"postCode" : {
"description" : "Postal code of the address. Entity for a postcode which consists of a type plus a value.",
"type" : "object",
"properties" : {
"type": {
"const": "PostCodeEntity"
},
"value" : {
"description" : "The value of a post code.",
"type" : "string",
"pattern" : "^[a-z0-9][a-z0-9\\- ]{0,10}$"
},
"technicalKey" : {
"description" : "The technical key of a post code.",
"type" : "string",
"enum" : [ "CEDEX", "LARGE_MAIL_USER", "OTHER", "POST_BOX", "REGULAR" ]
}
},
"required" : [ "value", "technicalKey" ]
},
"country" : {...}
},
"required" : [ "postCode", "..." ]
}OO-LD schema (see also generated JSON-SCHEMA)
{
"address" : {
"postCode" : {
"value" : "98765-4321",
"technicalKey" : "CEDEX"
}
}
}Data instance
In general LinkML schemas can be exported to JSON-SCHEMA and JSON-LD contexts in order to build a OO-LD schema. With linkml/linkml#2369 lifecycle methods being added to the LinkML jsonschemagen which allow to use annotations to extend the generated schema.
As an example applying
OOLDSchemaGenerator.py
from pprint import pprint
from linkml.generators.jsonschemagen import JsonSchemaGenerator, JsonSchema
from linkml.generators.jsonldcontextgen import ContextGenerator
import jsonasobj2
import json
import yaml
class OOLDSchemaGenerator(JsonSchemaGenerator):
def generate_annotations(self, target):
annotations = jsonasobj2._jsonobj.as_dict(target.source.annotations)
schema_annotations = {}
for key in annotations:
schema_annotations[annotations[key]['tag']] = annotations[key]['value']
if len(annotations) > 0:
target.schema_ = {**target.schema_, **schema_annotations}
return target
def after_generate_class(self, cls, sv):
self.generate_annotations(cls)
return cls
def after_generate_class_slot(self, slot, cls, sv):
self.generate_annotations(slot)
return slot
def generate(self):
_schema = super().generate()
_context = json.loads(ContextGenerator(self.schema).serialize())
oold = JsonSchema({"@context": _context["@context"], **_schema})
return oold
def serialize(self, **kwargs) -> str:
return self.generate().to_json(sort_keys=False, indent=self.indent if self.indent > 0 else None)
if __name__ == "__main__":
print(yaml.dump(json.loads(OOLDSchemaGenerator('Person.min.linkml.yaml', include_null=False).serialize()), sort_keys=False, indent=2))on an annotated LinkML schema, e.g.
Person.linkml.yaml
id: https://example.org/Person/
name: Person
prefixes:
linkml: https://w3id.org/linkml/
schema: http://schema.org/
imports:
- linkml:types
classes:
Organization:
class_uri: schema:Organization
attributes:
name:
slot_uri: schema:name
range: string
Address:
class_uri: schema:PostalAddress
attributes:
street:
range: string
slot_uri: schema:street
city:
range: string
slot_uri: schema:city
postal_code:
range: string
slot_uri: schema:postalCode
Person:
tree_root: true
class_uri: schema:Person
attributes:
name:
slot_uri: schema:name
range: string
description: the name of a person
required: true
annotations:
- tag: options
value:
hidden: false
- template: "{{first_name}} {{last_name}}"
- tag: watch
value:
first_name: first_name
last_name: last_name
first_name:
range: string
last_name:
range: string
birth_date:
slot_uri: schema:birthDate
range: date
recommended: true
annotations:
title: Birth date
address:
slot_uri: schema:address
range: Address
employer:
name: employer
range: Organization
inlined: false
inlined_as_list: falseproduces a OO-LD schema (JSON-LD context + JSON-SCHEMA with additional annotation for userinterface generation like, e.g. options and template)
Person.oold.yaml
'@context':
xsd: http://www.w3.org/2001/XMLSchema#
Person:
'@id': schema:Person
linkml: https://w3id.org/linkml/
schema: http://schema.org/
'@vocab': https://example.org/Person/
city:
'@id': schema:city
postal_code:
'@id': schema:postalCode
street:
'@id': schema:street
name:
'@id': schema:name
address:
'@type': '@id'
'@id': schema:address
birth_date:
'@type': xsd:date
'@id': schema:birthDate
employer:
'@type': '@id'
'@id': employer
first_name:
'@id': first_name
last_name:
'@id': last_name
Address:
'@id': schema:PostalAddress
Organization:
'@id': schema:Organization
$schema: https://json-schema.org/draft/2019-09/schema
$id: https://example.org/Person/
metamodel_version: 1.7.0
version: null
title: Person
type: object
additionalProperties: true
$defs:
Organization:
type: object
additionalProperties: false
description: ''
properties:
name:
type: string
title: Organization
Address:
type: object
additionalProperties: false
description: ''
properties:
street:
type: string
city:
type: string
postal_code:
type: string
title: Address
Person:
type: object
additionalProperties: false
description: ''
properties:
name:
type: string
description: the name of a person
options:
hidden: false
template: '{{first_name}} {{last_name}}'
watch:
first_name: first_name
last_name: last_name
first_name:
type: string
last_name:
type: string
birth_date:
type: string
format: date
title: Birth date
address:
$ref: '#/$defs/Address'
employer:
$ref: '#/$defs/Organization'
required:
- name
title: Person
description: ''
properties:
name:
type: string
description: the name of a person
options:
hidden: false
template: '{{first_name}} {{last_name}}'
watch:
first_name: first_name
last_name: last_name
first_name:
type: string
last_name:
type: string
birth_date:
type: string
format: date
title: Birth date
address:
$ref: '#/$defs/Address'
employer:
$ref: '#/$defs/Organization'
required:
- namewhich can be copy-pasted into OO-LD playground
to get an auto-generated userinterface (based on https://github.com/json-editor/json-editor):
Populating range in combination with a proper backend allows user to created non-inlined objects on the fly or link (= store the IRI) to existing ones (see https://opensemantic.world / https://demo.open-semantic-lab.org):
Minor unsolved issues:
annotationswith object-values cannot be written in compact form, only with additionalvalue-key in between- some information already encoded in LinkML is not yet part of the generated JSON-SCHEMA (e.g. default values)
NOMAD schemas have compareable concepts about reusable objects (sections) with typed properties (quantities). Example:
definitions:
sections:
Element:
quantities:
label:
type: str
density:
type: np.float64
unit: g/cm**3
isotopes:
type: int
shape: ['*']
Composition:
quantities:
composition:
type: str
sub_sections:
elements:
section: Element
repeats: truecan be expressed as the following JSON-SCHEMA (formated as yaml)
definitions:
sections:
Element:
properties:
label:
type: string
density:
type: number
unit: g/cm**3
isotopes:
type: array
items:
type: integer
format: table
Composition:
properties:
composition:
type: str
elements:
type: array
format: table
items:
$ref: '#/definitions/sections/Element'generating/validating the same JSON/YAML data (see also playground), e.g.
composition: H2O
elements:
- label: H
density: 8.375e-05
isotopes: [1, 2, 3]
- label: O
density: 1.141
isotopes: [16, 17, 18]by using the following mapping (work in progress):
| NOMAD Schema | JSON-Schema | Note |
|---|---|---|
| quantities | properties | |
| type (int, str, ...) | type (integer, string, ...) | specific python types like np.int32 can be annotated in the JSON-LD context |
| unit, m_annotations, .. | format / options | additional custom annotation keywords can be kept or mapped to format and options |
| shape[*] | type: array, items: type: number | specific values can be mapped to minItems and maxItems |
| shape[*, *] | type: array, items: type: array, items: type: number | nested array |
| sub_sections: ... : repeats: true | type: array, items: type: object | array of objects |
| ... |
Dlite already uses JSON-SCHEMA keywords like properties, type and description. Similar to NOMAD, annotations unit declare the unit of measure of quantity values and shape is used to describe array dimensions. However, different from NOMAD, shape refers to parameters declared under dimensions.
uri: http://onto-ns.com/meta/0.1/Person # identifier of the schema document
meta: http://onto-ns.com/meta/0.3/EntitySchema # links to a meta schema as type
description: A person.
dimensions:
nskills: Number of skills.
properties:
general:
type: $ref
$ref: http://onto-ns.com/meta/0.1/Thing # reference to a another schema document
name:
type: string
description: Full name.
age:
type: float32
unit: year
description: Age of person.
skills:
type: string
shape: [nskills]
description: List of skills.Person.dlite.yml
To overcome the missing expressivness in JSON-SCHEMA alone, specific JSON-LD @type annotations can be used (here xsd:float). dimension, unit, and shape can be expressed with custom keywords, prefixed by x-dlite-.
"@context":
xsd: http://www.w3.org/2001/XMLSchema
age:
"@type": xsd:float # see: https://www.w3.org/TR/xmlschema11-2/#float
$id: http://onto-ns.com/meta/0.1/Person
description: A person.
x-dlite-dimensions:
nskills: Number of skills.
properties:
general:
type: string
format: uri
x-oold-range: http://onto-ns.com/meta/0.1/Thing # reference to a another schema document
name:
type: string
description: Full name.
age:
type: number
x-dlite-unit: year
description: Age of person.
skills:
type: array
x-dlite-shape: [nskills]
description: List of skills.
#minItems: ? # can be set if nskills is known
#maxItems: ? # can be set if nskills is known
items:
type: stringPerson.oold.yml
On the instance level the main difference is the nesting of properties within a properties subobject. This can be interpreted as JSON-LD nested-properties. Links to other instance documents are UUIDs which should be interpreted as urn:uuid.
"@context":
- /remote/context/of/Person
- properties: "@nest" # skip this level
uuid: 8cbd4c09-734d-4532-b35a-1e0dd5c3e8b5
meta: http://onto-ns.com/meta/0.1/Person # like type
properties:
general: <UUID of a Thing instance document>
name: Sherlock Holmes
age: 34.0
skills:
- observing
- chemistry
- violin
- boxindSherlockHolmes.dlite.yml
"@context": http://onto-ns.com/meta/0.1/Person
$schema: http://onto-ns.com/meta/0.1/Person
uuid: 8cbd4c09-734d-4532-b35a-1e0dd5c3e8b5
general: urn:uuid:<UUID of a Thing instance document>
name: Sherlock Holmes
age: 34.0
skills:
- observing
- chemistry
- violin
- boxindSherlockHolmes.oold.yml


