Semantic uplift

The Building block design allows for “semantic annotation” through the use of a context document that cross-references each schema element to a URI, using the JSON-LD syntax. The end result is still a valid JSON schema, but may also be parsed as flexible RDF graphs if desired.

This provides multiple significant improvements over non-annotated schemas:

  1. differentiates between the same and different meanings for common element names used in different places
  2. can be used to link to a semantic model further describing each element
  3. allows use of advanced, standardised validation of instance data
  4. allows automated annotation of schemas themselves for tools able ot exploit additional information

The JSON schema for a building block is optionally linked to a conceptual model by using a root-level x-jsonld-context property pointing to a JSON-LD context document (relative paths are ok). The Building Blocks Register can then annotate every property inside the JSON schemas with their corresponding RDF predicate automatically.

Building Blocks defining JSON schemas can be annotated with JSON-LD contexts using either:

  • including a file (context.jsonld) in the building block directory (this can be overriden with a file path or URL using the ldContext property in bblock.json)
  • using the property x-jsonld-context in the _schema.(yaml/json) for the building block - e.g. x-jsonld-context: ../../../sosa-ssn.jsonld

The JSON LD context:

  1. Maps JSON elements to URIs (which can be URIs of a richer semantic model)
  2. Allows validation of complex logical constraints using SHACL Shapes to validate examples
  3. Perform transforms to any other RDF model and validate results

Modularity support

JSON-LD contexts need to be aware of the underlying JSON schema - and in many applications schemas are complex with nested sub-schemas. Examples of this are metadata schemas such as STAC, but even the basic GeoJSON Feature model is surprisingly hard to model in a consistent fashion.

The Building Blocks design allows automatic combination of contexts based on the schema re-use patterns: a building block that has no context.jsonld (or x-jsonld-context) of its own can still resolve to a full, valid semantic context, assembled from whichever of its dependencies (dependsOn / isProfileOf, walked transitively) do define one. Dependencies without a context of their own are simply skipped over — they still contribute their JSON Schema, just not any semantic mappings.

The diagram below is a real “Context sources” graph, traced by the viewer for the Custom Result for Observation Feature building block, which defines no JSON-LD context of its own:

Context sources graph for the Custom Result for Observation Feature building block, showing its assembled context traced back through Observation Result, JSON-FG Feature/Feature Collection - Lenient, GeoPose Basic-YPR, JSON Link, JSON-FG time member, Feature, Feature Collection and GeoJSON

Its dependency chain runs several levels deep — through an intermediate Observation Result block, then through customized JSON-FG Feature/Feature Collection - Lenient blocks, down to GeoPose Basic-YPR, JSON Link, JSON-FG time member, Feature, Feature Collection and GeoJSON — yet the block itself just declares dependsOn/schema references; every one of those ancestors’ JSON-LD mappings is picked up and merged automatically.

As schema complexity grows through re-use of standard components, this stops being a convenience and becomes a critical enabler: authors of a mid-level or leaf building block don’t need to redeclare mappings already defined by the blocks they build on, and every consumer gets a single, coherent context regardless of how many blocks were combined to produce the schema.

How the context actually gets assembled

The composition isn’t a merge of pre-built context.jsonld files — it’s driven off the schema itself, using ogc-na-tools, as part of the register build. Every building block’s schema is first annotated in place with its JSON-LD mappings (as x-jsonld-* properties, following any context.jsonld it defines), then a second pass walks the compiled schema tree — through every $ref, allOf/anyOf/oneOf — from the root down, collecting whatever mappings it finds along the way into one flat @context. That walk happens for every building block, whether or not it defines a context of its own: a block with no mappings just contributes none, but the ones its dependencies defined are still picked up wherever they sit in the tree.

So the “assembly” is really schema traversal, which is why the graph the viewer draws for a block’s dependencies doubles as the map of where its context’s mappings came from. Curious readers can dig into the ogc-na-tools source for the details.

Why not just reuse a general-purpose vocabulary like schema.org?

A general-purpose vocabulary has to cover every domain at once, so it ends up large, loosely defined, and only loosely typed against any particular schema — good for broad discovery, but weak for validation and precise tooling. A context assembled from Building Blocks is the opposite: each mapping is scoped to the exact schema it annotates, tested against real examples (via semantic uplift and SHACL validation), and versioned alongside the schema it describes. The resulting contexts are small and stable enough to be identified and cached per building block, rather than re-fetched wholesale — which matters as much for tooling and AI agents consuming these schemas as it does for the infrastructure serving them.

Context design

If contexts are being combined, then a number of possibilities emerge, but need careful design and testing.

One such possibility is the conversion of tokens into URIs depending on where these are encountered in a schemas.

This can be achieved through several mechanisms.

TBD: document local contexts and use of @base mappings - and link to examples of different patterns

Additional semantic uplift steps

Sometimes, using JSON-LD is not enough to convert a JSON document into RDF, so additional steps may be required. This is a common occurrence, for example, when defining JSON-LD contexts for already-existing or legacy JSON schemas, which are hard or even impossible to adapt to better fit a given semantic data model.

Semantic uplift pre- and/or post-processing (i.e., before and/or after applying the building block’s JSON-LD context) can be defined in a semantic-uplift.yaml file in the building block directory, with the following format:

additionalSteps:
  - type: jq                            # Type of transform
    code: |                             # Code for this transform
      .a = .b + 1
  - type: sparql-construct
    ref: semantic-uplift/constr.sparql  # A ref (from the bblock directory) can be used instead

Types of semantic uplift steps

The following types are supported and will be automatically processed when uplifting examples and test resources:

  • Pre-processing (on JSON document, before applying JSON-LD context)
  • Post-processing (on RDF graph, after applying JSON-LD context and parsing).
    • shacl: SHACL AF ruleset. The original graph plus the entailed triples (if any) will be returned.
    • sparql-construct: A SPARQL CONSTRUCT query that will replace the graph obtained from the JSON-LD uplift.
    • sparql-update: A SPARQL UPDATE query that will be applied on the graph. The full resulting graph will be returned.

Semantic uplift in run-time

If extra steps are required to map a schema to a model, then it becomes an implementation challenge to implement these steps. It is a work in progress to consider the reusability (FAIR) of transformations, and how these may be related to known profiles and allow software to automatically apply a small number of well-tested standard transformations.