HDF5 and RDF: FAIR Attributes#

According to F1 of the FAIR Principles attributes shall be assigned to globally unique and persistent identifiers.

Here’s what www.go-fair.org says about it:

“Globally unique and persistent identifiers remove ambiguity in the meaning of your published data by assigning a unique identifier to every element of metadata and every concept/measurement in your dataset. In this context, identifiers consist of an internet link (e.g., a URL that resolves to a web page that defines the concept such as a particular human protein). Many data repositories will automatically generate globally unique and persistent identifiers to deposited datasets. Identifiers can help other people understand exactly what you mean, and they allow computers to interpret your data in a meaningful way (i.e., computers that are searching for your data or trying to automatically integrate them). Identifiers are essential to the human-machine interoperation that is key to the vision of Open Science. In addition, identifiers will help others to properly cite your work when reusing your data.”

The h5rdmtoolbox allows assigning attributes (and their data) to identifiers. For this, each name and value of an attribute may obtain an IRI (internationalized resource identifier). The following outlines, how it is done.

Concept#

We can interpret HDF5 objects, their attribute names and attribute values as RDF triples (subject-predicate-object), where…

  • … a group or dataset is a subject

  • … the attribute name is a predicate

  • … and the attriute value is an object

In the following, we would like to describe the content of an HDF5 file. There will be a dataset or random data generated by a person, which can be identified/described by a researcher ID (ORCID).

We as humans may understand the content of such an HDF5 file. For machines to interpret the data, we need to associate URIs with the HDF5 objects. In fact, sometimes it may also not very clear to humans, what is meant with a certain attribute. To be unambiguous about it, a URI helps. Think of the attribute “contact”, we will define. Is it a person or an organization? Note, that URI and IRI may be used synonymously - IRI is built on URI by expanding the set of permitted characters.

Let’s build the example step by step. We start with creating the group “contact”:

import h5rdmtoolbox as h5tbx

Describing an HDF5 file with persistent metadata#

Example part 1: A contact person#

The file is written by an author. We create a group. It contains all relevant contact data, i.e. the ORCID. The content if the group thus describes the contact person and therefore is a person. The group itself, however, gets the predicate has author:

with h5tbx.File(mode='w') as h5:
    grp = h5.create_group('contact', attrs=dict(orcid='https://orcid.org/0000-0001-8729-0482'))   
    grp.rdf.predicate = 'https://schema.org/author'
    grp.rdf.type = 'http://xmlns.com/foaf/0.1/Person'  # what the content of group is, namely a foaf:Person
    grp.rdf.subject = 'https://orcid.org/0000-0001-8729-0482'  # corresponds to @ID in JSON-LD
    grp.rdf.predicate['orcid'] =  'http://w3id.org/nfdi4ing/metadata4ing#orcidId'
    grp.attrs['first_name', 'http://xmlns.com/foaf/0.1/firstName'] = 'Matthias'

    o = grp.rdf.predicate['orcid']
    
    h5.dump(collapsed=False)

hdf_filename = h5.hdf_filename

Using the rdf accessory, we can assign the objects (dataset, groups, attributes) with the internationalized resource identifier (IRI). An IRI a web resource and points to the definition in an ontology, e.g. “contact” is a “Person” and is defined in the ontology FOAF: ‘http://xmlns.com/foaf/0.1/Person’. The person “has a researcher ID”. This predicate is described in the M4i (metadata4ing) ontology: ‘http://w3id.org/nfdi4ing/metadata4ing#orcid’

From now on, let’s same some work and use the package namespacelib, which simplifies the work with the namespaces, so that we don’t have to type the full IRI address. Some popular ones are implemented in the rdflib package, too:

from ontolutils.namespacelib import M4I, OBO, QUDT_UNIT, QUDT_KIND
from rdflib.namespace import FOAF

As a result, we can type the following:

M4I.orcidId  # equal to http://w3id.org/nfdi4ing/metadata4ing#orcidId
rdflib.term.URIRef('http://w3id.org/nfdi4ing/metadata4ing#orcidId')

Example part 2: A random data dataset#

Next, we add the random data dataset with units. We can even describe what type the data is. In our case it shall be velocity data. Without this specification it would otherwise not be clear to the user (or a machine):

import numpy as np

with h5tbx.File(hdf_filename, mode='r+') as h5:    
    ds = h5.create_dataset('grp/random_velocity', data=np.random.random(100))
    ds.attrs.create('units',
                    rdf_predicate=M4I.hasUnit,
                    data='m/s',
                    rdf_object=QUDT_UNIT.M_PER_SEC)
    ds.attrs.create('quantity_kind',
                     data='velocity',
                     rdf_predicate=M4I.hasKindOfQuantity,
                     rdf_object=QUDT_KIND.Velocity)

    h5.dump(collapsed=False)

Now, let’s go further and describe how the random dataset was created and that the contact was involved in it:

from datetime import datetime

with h5tbx.File(hdf_filename, mode='r+') as h5:  
    proc = h5.create_group('processing_info')
    proc.rdf.subject = M4I.ProcessingStep
    proc.attrs['has_participants', OBO.has_participant] = h5['contact']
    start_time = datetime.today()
    end_time = datetime.today()
    proc.attrs.create('start_time', data=start_time,
                      rdf_predicate='https://schema.org/startTime')
    proc.attrs.create('end_time', data=end_time,
                      rdf_predicate='https://schema.org/startTime')
    proc.attrs['output', 'http://purl.obolibrary.org/obo/RO_0002234'] = h5['grp/random_velocity'].name
h5tbx.dump(hdf_filename, collapsed=False)

Example part 3: Assigning JSON-LD to describe data#

Until now, we used IRIs to assign meaning to HDF5 attributes, e.g. proc.rdf.subject = M4I.ProcessingStep.

Sometimes, data cannot be expressed by a single IRI, because there is no globally unique identifier. Let’s examine this case by using the SSNO Ontology.

In the example below, the attribute “standard_name” of the dataset “u” refers to “x_velocity” being the Standard name of the HDF5 dataset “u”. A Standard name has a name, description and SI unit and may be associated to a Standard Name Table in which it is listed. In our case, the Standard name “x_velocity” has no globally unique identifier, hence we need to describe it by a JOSN-LD string:

sn_xvel = """{
    "@context": {
        "ssno": "https://matthiasprobst.github.io/ssno#"
    },
    "@type": "ssno:StandardName",
    "ssno:standardName": "x_velocity",
    "ssno:unit": "http://qudt.org/vocab/unit/M-PER-SEC",
    "ssno:description": "X-component of a velocity vector."
}"""
with h5tbx.File() as h5:
    h5.create_dataset("u", data=[1,2,3], attrs={"standard_name": "x_velocity"})
    h5.u.rdf["standard_name"].predicate = "https://matthiasprobst.github.io/ssno#hasStandardName"
    # h5.u.rdf["standard_name"].object = sn_xvel
    h5.u.rdf["standard_name"].object = sn_xvel
    h5.dump(False)
    h5jld = h5.dump_jsonld(indent=2, structural=False)

The JSON-LD dump shows that “standard_name” is correctly associated with our JSON-LD string for the ssno:StandardName:

print(h5jld)
{
  "@context": {
    "ssno": "https://matthiasprobst.github.io/ssno#",
    "standard_name": "https://matthiasprobst.github.io/ssno#hasStandardName"
  },
  "@graph": [
    {
      "@id": "_:N1",
      "standard_name": [
        {
          "@id": "_:N3",
          "@type": "ssno:StandardName",
          "ssno:description": "X-component of a velocity vector.",
          "ssno:standardName": "x_velocity",
          "ssno:unit": "http://qudt.org/vocab/unit/M-PER-SEC"
        },
        "x_velocity"
      ]
    }
  ]
}

How to make use of the FAIR HDF5 file?#

There are three ways, how the above IRI assignments help us and how we might want to use the information:

  1. Visual inspection by dumping the content to screen: This will outline the file (meta) content and we can click on the attributes with IRIs, which will explain the attribute (data)

  2. We can extract a JSON-LD file. This is useful for other processes. We can also investigate this file further with tools like JSON-LD-playground.

  3. Access IRI in (Python) code

1. Visual inspection#

The dump() method will now add IRI-icons. Click on it and get redirected to the resources:

h5tbx.dump(hdf_filename, collapsed=False)

2. JSON-LD extraction#

Write the JSON-LD file and share it with others or a repository. The toolbox provides dump-methods through the jsonld module. It might look a bit overwelming, however dedicated scripts can perfectly work with it while humans still can read it (with a bit of practice and patience…):

print(
    h5tbx.dump_jsonld(
        hdf_filename,
        indent=2,
        context={'m4i': 'http://w3id.org/nfdi4ing/metadata4ing#',
                 'foaf': 'http://xmlns.com/foaf/0.1/'}
    )
)
{
  "@context": {
    "end_time": "https://schema.org/startTime",
    "first_name": "http://xmlns.com/foaf/0.1/firstName",
    "foaf": "http://xmlns.com/foaf/0.1/",
    "has_participants": "http://purl.obolibrary.org/obo/RO_0000057",
    "hdf5": "http://purl.allotrope.org/ontologies/hdf5/1.8#",
    "m4i": "http://w3id.org/nfdi4ing/metadata4ing#",
    "orcid": "http://w3id.org/nfdi4ing/metadata4ing#orcidId",
    "output": "http://purl.obolibrary.org/obo/RO_0002234",
    "quantity_kind": "http://w3id.org/nfdi4ing/metadata4ing#hasKindOfQuantity",
    "start_time": "https://schema.org/startTime",
    "units": "http://w3id.org/nfdi4ing/metadata4ing#hasUnit"
  },
  "@graph": [
    {
      "@id": "_:N5",
      "@type": "hdf5:File",
      "hdf5:rootGroup": {
        "@id": "_:N4",
        "@type": "hdf5:Group",
        "hdf5:member": [
          {
            "@id": "https://orcid.org/0000-0001-8729-0482",
            "@type": [
              "hdf5:Group",
              "foaf:Person"
            ],
            "first_name": "Matthias",
            "hdf5:attribute": [
              {
                "@id": "_:N6",
                "@type": "hdf5:Attribute",
                "hdf5:name": "first_name",
                "hdf5:value": "Matthias"
              },
              {
                "@id": "_:N7",
                "@type": "hdf5:Attribute",
                "hdf5:name": "orcid",
                "hdf5:value": {
                  "@type": "http://www.w3.org/2001/XMLSchema#anyURI",
                  "@value": "https://orcid.org/0000-0001-8729-0482"
                }
              }
            ],
            "hdf5:name": "/contact",
            "orcid": {
              "@type": "http://www.w3.org/2001/XMLSchema#anyURI",
              "@value": "https://orcid.org/0000-0001-8729-0482"
            }
          },
          {
            "@id": "_:N8",
            "@type": "hdf5:Group",
            "hdf5:member": {
              "@id": "_:N9",
              "@type": "hdf5:Dataset",
              "hdf5:attribute": [
                {
                  "@id": "_:N10",
                  "@type": "hdf5:Attribute",
                  "hdf5:name": "quantity_kind",
                  "hdf5:value": [
                    "velocity",
                    {
                      "@id": "http://qudt.org/vocab/quantitykind/Velocity"
                    }
                  ]
                },
                {
                  "@id": "_:N11",
                  "@type": "hdf5:Attribute",
                  "hdf5:name": "units",
                  "hdf5:value": [
                    "m/s",
                    {
                      "@id": "http://qudt.org/vocab/unit/M-PER-SEC"
                    }
                  ]
                }
              ],
              "hdf5:datatype": "H5T_FLOAT",
              "hdf5:dimension": 1,
              "hdf5:name": "/grp/random_velocity",
              "hdf5:size": 100,
              "hdf5:value": "[0.19535433445279526, 0.3016920924997958, 0.8180079636997661, 0.7963907512556723, 0.008341516229149026, 0.4420186236496202, 0.7495803491856404, 0.5541705262184656, 0.09815301886035654, 0.8315132528379355, 0.9910227718069782, 0.539145093410935, 0.5783423160480538, 0.37755995761764594, 0.6006505491556546, 0.5741037494484976, 0.646306677900649, 0.5398808827819606, 0.5699970655964391, 0.3009179697836677, 0.03446350366019324, 0.4876355520088378, 0.23767247763743138, 0.4840962858962251, 0.5695645349920513, 0.24955235760099537, 0.11533929360789574, 0.7216644681698695, 0.7724988643464787, 0.27828166922307385, 0.6144120040722922, 0.1173765262937897, 0.5429065514418718, 0.3710869059134698, 0.8598032509034697, 0.7104447967486879, 0.23478330565579175, 0.16078524989757048, 0.8815111963431153, 0.1904178517045957, 0.3273661171523268, 0.7205776622749475, 0.23635370731449956, 0.4200203837588192, 0.8629172363541198, 0.46780150277226595, 0.005352756198479081, 0.8865397571769833, 0.21310576614102184, 0.1974227010508819, 0.3718906793107333, 0.8912558223158765, 0.4903921563958378, 0.6059004489202523, 0.31333156296028297, 0.6450630749158838, 0.21902296824380874, 0.4345820916556361, 0.5903628958135083, 0.3969377231511293, 0.11786338625275561, 0.38067107572841985, 0.9499727540899271, 0.5687760112632516, 0.09329790011345362, 0.2646273689662215, 0.8888672307770343, 0.8543991200607354, 0.7249663406517606, 0.9601088513854692, 0.9972578785127192, 0.7712518377572973, 0.46346655446767004, 0.7798657569697317, 0.9733821724348822, 0.09437437334964416, 0.7575047653543993, 0.9319626028613961, 0.4320413387270722, 0.875164773177871, 0.33600352059407124, 0.9313192287495761, 0.5727600291530709, 0.250270177762422, 0.6960556722139657, 0.7271861814666633, 0.10164517287754815, 0.9740011884056936, 0.48148858253430615, 0.1880432652562296, 0.6359894303366759, 0.9369449499521771, 0.1349752528889533, 0.9194854347844412, 0.9924400912687299, 0.3537172553440947, 0.6923549029361733, 0.44514273519637026, 0.2604199167098117, 0.8566990580866326]",
              "quantity_kind": {
                "@id": "http://qudt.org/vocab/quantitykind/Velocity"
              },
              "units": {
                "@id": "http://qudt.org/vocab/unit/M-PER-SEC"
              }
            },
            "hdf5:name": "/grp"
          },
          {
            "@id": "m4i:ProcessingStep",
            "@type": "hdf5:Group",
            "end_time": "2024-10-30T10:11:55.320160",
            "has_participants": "/contact",
            "hdf5:attribute": [
              {
                "@id": "_:N12",
                "@type": "hdf5:Attribute",
                "hdf5:name": "end_time",
                "hdf5:value": "2024-10-30T10:11:55.320160"
              },
              {
                "@id": "_:N13",
                "@type": "hdf5:Attribute",
                "hdf5:name": "has_participants",
                "hdf5:value": "/contact"
              },
              {
                "@id": "_:N14",
                "@type": "hdf5:Attribute",
                "hdf5:name": "output",
                "hdf5:value": "/grp/random_velocity"
              },
              {
                "@id": "_:N15",
                "@type": "hdf5:Attribute",
                "hdf5:name": "start_time",
                "hdf5:value": "2024-10-30T10:11:55.320154"
              }
            ],
            "hdf5:name": "/processing_info",
            "output": "/grp/random_velocity",
            "start_time": "2024-10-30T10:11:55.320154"
          }
        ],
        "hdf5:name": "/"
      }
    }
  ]
}

Dump it to the file rather than to the screen:

from h5rdmtoolbox import jsonld
with open('hdf_meta.jsonld', 'w') as f:
    jsonld.dump(hdf_filename, f, indent=2,
        context={'m4i': 'http://w3id.org/nfdi4ing/metadata4ing#',
                 'foaf': 'http://xmlns.com/foaf/0.1/'})
                # context={'foaf': 'http://xmlns.com/foaf/0.1/',
                #                                    'm4i': 'http://w3id.org/nfdi4ing/metadata4ing#'})

3. Access IRI in code#

You may want to access the IRI of an attribute with Python within the HDF5 file. E.g. while working with the file, you may ask “Hey, what is ‘contact’ exactly?” or “What does the attribute ‘orcid’ mean?”

with h5tbx.File(hdf_filename) as h5:
    person_iri = h5.contact.rdf.subject
    orcid_iri = h5.contact.rdf.predicate['orcid']

… Well “contact” is a “Person” defined by the FOAF ontology:

person_iri
'https://orcid.org/0000-0001-8729-0482'

… and “orcid” is a predicate defined by the metadata4ing ontology:

orcid_iri
'http://w3id.org/nfdi4ing/metadata4ing#orcidId'

3.1 Find data based on IRIs#

import rdflib.graph as g

graph = g.Graph()
graph.parse('hdf_meta.jsonld', format='json-ld')
<Graph identifier=N8839178a4d4742608c402489fc3d906c (<class 'rdflib.graph.Graph'>)>

Note, that we need to provide the PREFIXES, if the json-ld data/file does not include the context.

res = graph.query("""
PREFIX foaf: <http://xmlns.com/foaf/0.1/>
PREFIX m4i: <http://w3id.org/nfdi4ing/metadata4ing#>

SELECT ?id ?orcid
WHERE {
    ?id a foaf:Person .
    ?id m4i:orcid ?orcid .
    }
""")
for r in res:
    print(r)

4. Examples:#

4.1 Read metadata from JSON and write to HDF5#

Suppose we want to store information about the used software to the HDF5 file. It exists as a JSON-LD file based on the codemeta ontolog. For this example, we use the h5rdmtoolbox codemeta.json file from the github repository:

from h5rdmtoolbox.utils import download_file
from pprint import pprint

Download the file:

codemeta_url = 'https://raw.githubusercontent.com/matthiasprobst/h5RDMtoolbox/main/codemeta.json'
dowloaded_filename = download_file(codemeta_url)
2024-10-30_10:11:55,847 WARNING  [utils.py:70] No hash given! This is recommended when downloading files from the web.

Read the data with ontolutils.dquery:

from ontolutils import dquery
data = dquery(subject='schema:SoftwareSourceCode',
              source=dowloaded_filename,
              context={"schema": "http://schema.org/"})
pprint(data[0])
{'@context': {'applicationCategory': 'http://schema.org/applicationCategory',
              'author': 'http://schema.org/author',
              'codeRepository': 'http://schema.org/codeRepository',
              'description': 'http://schema.org/description',
              'license': 'http://schema.org/license',
              'name': 'http://schema.org/name',
              'operatingSystem': 'http://schema.org/operatingSystem',
              'programmingLanguage': 'http://schema.org/programmingLanguage',
              'version': 'http://schema.org/version'},
 '@id': '_:Nf5d3118e25a143aeb6f97190912edac2',
 '@type': 'http://schema.org/SoftwareSourceCode',
 'applicationCategory': 'file:///home/docs/.cache/h5rdmtoolbox/1.5.2/Engineering',
 'author': [{'@id': 'https://orcid.org/0000-0001-8729-0482',
             '@type': 'http://schema.org/Person',
             'affiliation': {'@id': 'https://ror.org/04t3en479',
                             '@type': 'http://schema.org/Organization',
                             'name': 'Karlsruhe Institute of Technology, '
                                     'Institute of Thermal Turbomachinery'},
             'email': 'matth.probst@gmail.com',
             'familyName': 'Probst',
             'givenName': 'Matthias'},
            {'@id': 'https://orcid.org/0000-0001-9560-500X',
             '@type': 'http://schema.org/Person',
             'affiliation': {'@id': 'https://ror.org/04t3en479',
                             '@type': 'http://schema.org/Organization',
                             'name': 'Karlsruhe Institute of Technology, '
                                     'Institute of Thermal Turbomachinery'},
             'familyName': 'Pritz',
             'givenName': 'Balazs'},
            {'@id': 'https://orcid.org/0000-0002-4116-0065',
             '@type': 'http://schema.org/Person',
             'affiliation': {'@id': 'https://ror.org/04t3en479',
                             '@type': 'http://schema.org/Organization',
                             'name': 'Karlsruhe Institute of Technology, '
                                     'Institute of Thermal Turbomachinery'},
             'familyName': 'Büttner',
             'givenName': 'Lucas'}],
 'codeRepository': 'git+https://github.com/matthiasprobst/h5RDMtoolbox.git',
 'description': 'Supporting a FAIR Research Data lifecycle using Python and '
                'HDF5.',
 'license': 'https://spdx.org/licenses/MIT',
 'name': 'h5RDMtoolbox',
 'operatingSystem': ['Linux', 'Windows', 'macOS'],
 'programmingLanguage': ['Python 3',
                         'Python 3.8',
                         'Python 3.9',
                         'Python 3.10',
                         'Python 3.11',
                         'Python 3.12'],
 'version': '1.5.1'}

The data are written into the HDF5 file by using jsonld.to_hdf():

with h5tbx.File('test.hdf', 'w') as h5:
    jsonld.to_hdf(data=data[0],
                 grp=h5.create_group('software_code'))
    h5.dump(False)
import ontolutils
with h5tbx.File(mode='w') as h5:
    _ = h5.create_dataset('test_dataset', data=np.array([[1, 2], [3, 4], [5.4, 1.9]]))
    _ = h5.create_dataset('test_dataset 2', data=4.5)
    h5.create_dataset('grp/subgrp/vel', data=4)
    h5.attrs['name', ontolutils.SCHEMA.name] = 'test attr'
    # _ = h5.create_dataset('test_dataset', data=5.4)
    jd = jsonld.dumpd(h5, structural=True)
    jds = jsonld.dumps(h5, structural=True, indent=2)
from pprint import pprint
pprint(jd, indent=1)
{'@context': {'hdf5': 'http://purl.allotrope.org/ontologies/hdf5/1.8#'},
 '@graph': [{'@id': '_:N29',
             '@type': 'hdf5:File',
             'hdf5:rootGroup': {'@id': '_:N28',
                                '@type': 'hdf5:Group',
                                'hdf5:attribute': {'@id': '_:N30',
                                                   '@type': 'hdf5:Attribute',
                                                   'hdf5:name': 'name',
                                                   'hdf5:value': 'test attr'},
                                'hdf5:member': [{'@id': '_:N31',
                                                 '@type': 'hdf5:Group',
                                                 'hdf5:member': {'@id': '_:N32',
                                                                 '@type': 'hdf5:Group',
                                                                 'hdf5:member': {'@id': '_:N33',
                                                                                 '@type': 'hdf5:Dataset',
                                                                                 'hdf5:datatype': 'H5T_INTEGER',
                                                                                 'hdf5:dimension': 0,
                                                                                 'hdf5:name': '/grp/subgrp/vel',
                                                                                 'hdf5:size': 1,
                                                                                 'hdf5:value': '4'},
                                                                 'hdf5:name': '/grp/subgrp'},
                                                 'hdf5:name': '/grp'},
                                                {'@id': '_:N34',
                                                 '@type': 'hdf5:Dataset',
                                                 'hdf5:datatype': 'H5T_FLOAT',
                                                 'hdf5:dimension': 2,
                                                 'hdf5:name': '/test_dataset',
                                                 'hdf5:size': 6,
                                                 'hdf5:value': '[[1.0, 2.0], '
                                                               '[3.0, 4.0], '
                                                               '[5.4, 1.9]]'},
                                                {'@id': '_:N35',
                                                 '@type': 'hdf5:Dataset',
                                                 'hdf5:datatype': 'H5T_FLOAT',
                                                 'hdf5:dimension': 0,
                                                 'hdf5:name': '/test_dataset 2',
                                                 'hdf5:size': 1,
                                                 'hdf5:value': 4.5}],
                                'hdf5:name': '/',
                                'https://schema.org/name': 'test attr'}}]}

Describing attribute meanings without RDF#

Sometimes, there is no IRI (yet) defined but the need to give an additional comment on the attribute. This can be done by as follows:

with h5tbx.File() as h5:
    grp = h5.create_group('contact')

    # Set an attribute as usual
    grp.attrs['type'] = 'Contact'

    # Update the attribute definition afterwards:
    grp.rdf['type'].definition = 'The role of the Person'

    # Alternatively, it can be assigned simultaneously via h5tbx.Attribute:
    grp.attrs['fname'] = h5tbx.Attribute(value='Matthias',
                                        definition='The first name of the contact')
    h5.dump(False)

    jdict = h5.dump_jsonld(h5.hdf_filename, indent=2)
      • fname
        DThe first name of the contact
        : Matthias
      • type
        DThe role of the Person
        : Contact
print(jdict)
{
  "@context": {
    "hdf5": "http://purl.allotrope.org/ontologies/hdf5/1.8#",
    "skos": "http://www.w3.org/2004/02/skos/core#"
  },
  "@graph": [
    {
      "@id": "_:N45",
      "@type": "hdf5:File",
      "hdf5:rootGroup": {
        "@id": "_:N44",
        "@type": "hdf5:Group",
        "hdf5:member": {
          "@id": "_:N46",
          "@type": "hdf5:Group",
          "hdf5:attribute": [
            {
              "@id": "_:N47",
              "@type": "hdf5:Attribute",
              "hdf5:name": "fname",
              "hdf5:value": "Matthias",
              "skos:definition": "The first name of the contact"
            },
            {
              "@id": "_:N48",
              "@type": "hdf5:Attribute",
              "hdf5:name": "type",
              "hdf5:value": "Contact",
              "skos:definition": "The role of the Person"
            }
          ],
          "hdf5:name": "/contact"
        },
        "hdf5:name": "/"
      }
    }
  ]
}