This tutorial examines the keyword syntax tokens of JSON-LD and introduces custom property types which extend NGSI-LD properties to cover multilingual capabilities and preferred enumeration names reusing the data from the Smart Farm example. The tutorial uses cUrl commands throughout.
Details
- NGSI-LD Rules
- Prerequisites
- Docker Engine
- jq
- Postman
- GitPod
- Architecture
- Start Up
- Checking the system
- Working with multilanguage properties
- Creating a new data entity
- Reading multilingual data in normalized format
- Reading multilingual data in key-value format
- Querying for Multilingual Data
- Using an alternative
@context
- License
The JSON-LD syntax defines a series of keywords to describe the structure of the JSON displayed. Since NGSI-LD is just a formally structured extended subset of JSON-LD, NGSI-LD should be directly or indirectly capable of offering an equivalent for all the functions defined by JSON-LD.
As an example, JSON-LD defines @id
to indicate the unique identifier of an Entity, and @type
to define the type of an Entity.
The NGSI-LD core @context
further refines this, so that id
/@id
and type
/@type
are considered as interchangable.
Both of the following syntaxes are acceptable in NGSI-LD:
{
"id": "urn:ngsi-ld:Building:farm001",
"type": "Building",
"@context" : "https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context-v1.7.jsonld"
}
{
"@id": "urn:ngsi-ld:Building:farm001",
"@type": "Building",
"@context" : "https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context-v1.7.jsonld"
}
Among the keywords defined in JSON-LD, the following terms are used or mapped within the NGSI-LD core @context
to maintain their meaning when data is supplied.
@list
- Used to express an ordered set of data.@json
- Used in association with unexpandable JSON objects@language
- Used to specify the language for a particular string value.@none
- Used as a default index value when an attribute does not have the feature being indexed.@value
- Used to specify the data that is associated with a particular property@vocab
- Used to expand properties and values
Certain other keywords such as @graph
, which describe statements about relationships are accepted in NGSI-LD, but are never processed directly by NGSI-LD Context brokers
For example. Looking at the core @context
, the GeoProperty attribute
coordinates
is fully defined as:
"coordinates": {
"@container": "@list",
"@id": "geojson:coordinates"
}
This ensure that the ordering of the values in its array (longitude, latitude) is always maintained.
All ordinary NGSI-LD Properties (and GeoProperties) have a value
, which is the equivalent of a JSON-LD @value
- this mean that the value
of a Property is just the data that is associated with a particular property.
However, there are recent updates to the NGSI-LD specification which have introduced various extensions or sub-classes to this principle, allowing the creation of NGSI-LD properties which directly conform to
JSON-LD keywords other than @value
.
- An NGSI-LD LanguageProperty holds a set of internationalized strings and is defined using the JSON-LD
@language
keyword. - An NGSI-LD VocabularyProperty holds is a mapping of a URI to a value within the user'
@context
and is defined using the JSON-LD@vocab
keyword.
In each case, the resultant payload will be altered according to the standard JSON-LD definitions, so the output NGSI-LD remains fully valid JSON-LD.
NGSI-LD is a formally structured extended subset of JSON-LD. Therefore, NGSI-LD offers all the
interoperability and flexibility of JSON-LD itself. It also defines its own core @context
which cannot be
overridden for NGSI-LD operations. This means that NGSI-LD users agree to a common well-defined set of rules for
structuring their data, and then supplement this with the rest of the JSON-LD specification.
Whilst interacting directly with the NGSI-LD interface of the context broker the additional NGSI-LD rules must be respected. However after the data has been extracted it is possible to loosen this requirement and pass the results to third parties as JSON-LD.
This tutorial is a simple introduction to the rules and restrictions behind NGSI-LD and will create some NGSI-LD entities and then extract the data in different formats. The two main data formats are normalised and key-value-pairs. Data returned in the normalised format respects the NGSI-LD rules and may be used directly by another context broker (or any other component offering an NGSI-LD interface). Data returned in the key-value-pairs format is by definition not NGSI-LD.
To keep things simple all components will be run using Docker. Docker is a container technology which allows different components to be isolated into their respective environments.
- To install Docker on Windows follow the instructions here
- To install Docker on Mac follow the instructions here
- To install Docker on Linux follow the instructions here
Docker Compose is a tool for defining and running multi-container Docker applications. A YAML file is used to configure the required services for the application. This means all container services can be brought up in a single command.
Compose V1 is discontinued, nevertheless Compose V2 has replaced it and is now integrated into all current Docker Desktop versions. Therefore, it is not needed to install the extension to the docker engine to execute the docker compose commands.
jq is a lightweight and flexible command-line JSON processor which can be used to format
the JSON responses received from the context broker and other FIWARE components. More information about how to use jq
can be found here. jq-1.6
is
recommended.
The tutorials which use HTTP requests supply a collection for use with the Postman utility. Postman is a testing framework for REST APIs. The tool can be downloaded from www.getpostman.com. All the FIWARE Postman collections can be downloaded directly from the Postman API network.
Gitpod is an open-source Kubernetes application for ready-to-code cloud
development environments that spins up an automated dev environment for each task, in the cloud. It enables you to run
the tutorials in a cloud development environment directly from your browser or your Desktop IDE. The default environment
is based on Ubuntu and includes Java 11.0.16
and Maven 3.8.6
.
The demo application will send and receive NGSI-LD calls to a compliant context broker. Since the standardised NGSI-LD interface is available across multiple context brokers, we only need to pick one - for example the Orion Context Broker. The application will therefore only make use of one FIWARE component.
Currently, the Orion Context Broker relies on open source MongoDB technology to hold the current state of the context data it contains and persistent information relevant to subscriptions and registrations. Other Context Brokers such as Scorpio or Stellio are using PostgreSQL for state information.
To promote interoperability of data exchange, NGSI-LD context brokers explicitly expose a
JSON-LD @context
file to define the data held within the
context entities. This defines a unique URI for every entity type and every attribute so that other services outside of
the NGSI domain are able to pick and choose the names of their data structures. Every @context
file must be available
on the network. In our case the tutorial application will be used to host a series of static files.
Therefore, the architecture will consist of three elements:
- The Scorpio Context Broker which will receive requests using NGSI-LD
- The underlying Postgres database:
- Used by the OrionLD Context Broker to hold context data information such as data entities, subscriptions and registrations.
- An HTTP Web-Server which offers static
@context
files defining the context entities within the system.
Since all interactions between the three elements are initiated by HTTP requests, the elements can be containerized and run from exposed ports.
The necessary configuration information can be seen in the services section of the associated scorpio.yml
file:
orionld:
labels:
org.fiware: 'tutorial'
platform: linux/amd64
image: quay.io/fiware/orion-ld:1.4.0
hostname: orionld
container_name: fiware-orionld
depends_on:
- mongo-db
networks:
- default
ports:
- 1026:1026
command: -dbhost mongo-db -logLevel DEBUG -forwarding -experimental
healthcheck:
test: curl --fail -s http://orionld:1026/version || exit 1
interval: 5s
mongo-db:
labels:
org.fiware: 'tutorial'
image: mongo:4.4
hostname: mongo-db
container_name: db-mongo
expose:
- "27017"
ports:
- "27017:27017"
networks:
- default
volumes:
- mongo-db:/data/db
- mongo-config:/data/configdb
healthcheck:
test: [ "CMD", "mongo", "--quiet", "127.0.0.1/test", "--eval", "'quit(db.runCommand({ ping: 1 }).ok ? 0 : 2)'"]
interval: 5s
ld-context:
labels:
org.fiware: 'tutorial'
image: httpd:alpine
hostname: context
container_name: fiware-ld-context
ports:
- "3004:80"
volumes:
- data-models:/usr/local/apache2/htdocs/
healthcheck:
test: (wget --server-response --spider --quiet http://ld-context/ngsi-context.jsonld 2>&1 | awk 'NR==1{print $$2}'| grep -q -e "200") || exit 1
All containers reside on the same network - the OrionLD Context Broker is listening on Port 1026
and MongoDB is
listening on the default port 27017
and the httpd web server is offering @context
files on port 80
. All containers
are also exposing ports externally - this is purely for the tutorial access - so that cUrl or Postman can access them
without being part of the same network. The command-line initialization should be self-explanatory.
All services can be initialised from the command-line by running the services Bash script provided within the repository. Please clone the repository and create the necessary images by running the commands as shown:
git clone git@github.com:flopezag/tutorials.Multilanguage.git
cd tutorials.Multilanguage
./services [start]
Note: If you want to clean up and start over again you can do so with the following command:
./services stop
Once the services have started up, and before interacting with the context broker itself, it is useful to check that the necessary prerequisites are in place.
Three @context
files have been generated and hosted on the tutorial application. They serve different roles.
-
ngsi-context.jsonld
-The NGSI-LD@context
serves to define all attributes when sending data to the context broker or retrieving data in normalised format. This@context
must be used for all NGSI-LD to NGSI-LD interactions. -
alternate-context.jsonld
is an alternative JSON-LD definition of the attributes of the data models used by a third-party. Internally their billing application used different short names for attributes depending on the language. Their@context
file reflects the agreed mapping between attribute names.
The full data model description for a Building entity as used in this tutorial is based on the standard Smart Data Models definition. A Swagger Specification of the same model is also available, and would be used to generate code stubs in a full application.
Sometimes, it is required to use a different language in the creation and consumption of Entity data. In order to
proceed, we need to create initially a new entity data that defines the new data type LanguageProperty
and use the
sub-attribute LanguageMap
(and not value) to keep the representation of the values of this attribute in different
languages.
This LanguageMap
corresponds to a JSON object consisting of a series of key-value pairs where the keys shall be JSON
strings representing IETF RFC 5646 language codes.
Let's create a Point of Interest data in which we want to keep the detailed information about the Helsinki Cathedral, but for the value of the name, we use three different languages, English, Finnish, and Italian. The process will be to send a request to the Broker with the following information:
curl -iX POST 'http://localhost:1026/ngsi-ld/v1/entities/' \
-H 'Content-Type: application/ld+json' \
--data-raw '{
"id": "urn:ngsi-ld:Building:farm001",
"type": "Building",
"category": {
"type": "VocabularyProperty",
"vocab": ["farm"]
},
"address": {
"type": "Property",
"value": {
"streetAddress": "Großer Stern 1",
"addressRegion": "Berlin",
"addressLocality": "Tiergarten",
"postalCode": "10557"
},
"verified": {
"type": "Property",
"value": true
}
},
"location": {
"type": "GeoProperty",
"value": {
"type": "Point",
"coordinates": [13.3505, 52.5144]
}
},
"name": {
"type": "LanguageProperty",
"languageMap": {
"en": "Victory Farm",
"de": "Bauernhof von Sieg",
"ja": "ビクトリーファーム"
}
},
"@context": "http://context/ngsi-context.jsonld"
}'
The response that we obtain will be something similar (except the Date
value) to the following content:
HTTP/1.1 201 Created
Date: Sat, 16 Dec 2023 08:39:32 GMT
Location: /ngsi-ld/v1/entities/urn:ngsi-ld:Building:poi123456
Content-Length: 0
Each subsequent entity must have a unique id
for the given type
curl -iX POST 'http://localhost:1026/ngsi-ld/v1/entities/' \
-H 'Content-Type: application/json' \
-H 'Link: <http://context/ngsi-context.jsonld>; rel="http://www.w3.org/ns/json-ld#context"; type="application/ld+json"' \
-d '{
"id": "urn:ngsi-ld:Building:barn002",
"type": "Building",
"category": {
"type": "VocabProperty",
"vocab": ["barn"]
},
"address": {
"type": "Property",
"value": {
"streetAddress": "Straße des 17. Juni",
"addressRegion": "Berlin",
"addressLocality": "Tiergarten",
"postalCode": "10557"
},
"verified": {
"type": "Property",
"value": true
}
},
"location": {
"type": "GeoProperty",
"value": {
"type": "Point",
"coordinates": [13.3698, 52.5163]
}
},
"name": {
"type": "LanguageProperty",
"languageMap": {
"en": "Big Red Barn",
"de": "Große Rote Scheune",
"ja": "大きな赤い納屋"
}
}
}'
Imagining that we want to get details of a specific entity (urn:ngsi-ld:Building:poi123456
) in normalised
format and without any reference to the language that we want to obtain the data. We should execute the following
command:
curl -X GET 'http://localhost:1026/ngsi-ld/v1/entities/urn:ngsi-ld:Building:farm001?attrs=name' \
-H 'Link: <http://context/ngsi-context.jsonld>; rel="http://www.w3.org/ns/json-ld#context"; type="application/ld+json"'
And the response that we obtain include all the string values defined for the different languages:
{
"id": "urn:ngsi-ld:Building:farm001",
"type": "Building",
"name": {
"type": "LanguageProperty",
"languageMap": {
"en": "Victory Farm",
"de": "Bauernhof von Sieg",
"ja": "ビクトリーファーム"
}
}
}
On the other hand, if we decided to specify that we wanted to receive the value (or values) but only in German
language, we should specify the corresponding query parameter lang
equal to de
.
curl -X GET 'http://localhost:1026/ngsi-ld/v1/entities/urn:ngsi-ld:Building:farm001?attrs=name&lang=de' \
-H 'Link: <http://context/ngsi-context.jsonld>; rel="http://www.w3.org/ns/json-ld#context"; type="application/ld+json"'
In this case, the response provides a new sub-attribute lang
with the details of the language that was selected
together with the sub-attribute value
with the content of the string in the corresponding German language. It is
important to notice that in this response the value of type
is Property and there is no LanguageMap
but value
sub-attribute.
{
"id": "urn:ngsi-ld:Building:farm001",
"type": "Building",
"name": {
"type": "Property",
"lang": "de",
"value": "Bauernhof von Sieg"
}
}
If we wanted to get the response in key-value format, we need to send the corresponding request parameter options
equal to keyValues
:
curl -X GET 'http://localhost:1026/ngsi-ld/v1/entities/urn:ngsi-ld:Building:farm001?attrs=name&options=keyValues' \
-H 'Link: <http://context/ngsi-context.jsonld>; rel="http://www.w3.org/ns/json-ld#context"; type="application/ld+json"'
{
"id": "urn:ngsi-ld:Building:farm001",
"name": {
"languageMap": {
"en": "Victory Farm",
"de": "Bauernhof von Sieg",
"ja": "ビクトリーファーム"
}
}
}
and if we wanted to get only the corresponding value of the name
in English language:
curl -X GET 'http://localhost:1026/ngsi-ld/v1/entities/urn:ngsi-ld:Building:farm001?attrs=name&options=keyValues&lang=en' \
-H 'Link: <http://context/ngsi-context.jsonld>; rel="http://www.w3.org/ns/json-ld#context"; type="application/ld+json"'
{
"id": "urn:ngsi-ld:Building:farm001",
"name": "Victory Farm"
}
Use the standard Object attribute bracket [ ]
notation when querying LanguageProperties
. For example, if we want to
obtain the Building whose name is equal to Big Red Barn
in English.
curl -L -g 'http://localhost:1026/ngsi-ld/v1/entities/?type=Building&attrs=name&q=name[en]%3D%3D%22Big%20Red%20Barn%22' \
-H 'Link: <http://context/ngsi-context.jsonld>; rel="http://www.w3.org/ns/json-ld#context"; type="application/ld+json"' \
-H 'Accept: application/ld+json'
[
{
"id" : "urn:ngsi-ld:Building:barn002",
"type" : "Building",
"name" : {
"type" : "LanguageProperty",
"languageMap" : {
"en" : "Big Red Barn",
"de" : "Große Rote Scheune",
"ja" : "大きな赤い納屋"
}
},
"@context" : [ "http://context/ngsi-context.jsonld", "https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context-v1.7.jsonld" ]
}
]
Now, I wanted to receive the response but corresponding to Große Rote Scheune
in Any language:
curl -L -g 'http://localhost:1026/ngsi-ld/v1/entities/?type=Building&attrs=name&q=name[*]%3D%3D%22Gro%C3%9Fe%20Rote%20Scheune%22' \
-H 'Link: <http://context/ngsi-context.jsonld>; rel="http://www.w3.org/ns/json-ld#context"; type="application/ld+json"' \
-H 'Accept: application/ld+json'
Using the Asterisk Syntax *
checks for all available languages.
[
{
"id": "urn:ngsi-ld:Building:barn002",
"type": "Building",
"name": {
"type": "LanguageProperty",
"languageMap": {
"en": "Big Red Barn",
"de": "Große Rote Scheune",
"ja": "大きな赤い納屋"
}
},
"@context": [
"http://context/ngsi-context.jsonld",
"https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context-v1.7.jsonld"
]
}
]
The simple NGSI-LD @context
is merely a mechanism for mapping URNs. It is therefore possible to retrieve the same
data using a different set of short names, in different languages.
The alternate-context-fi.jsonld
maps the names of various attributes to their equivalents in Finnish. The
alternate-context-it.jsonld
provides their equivalent in Italian. If it is supplied in the request, a query can be
made using alternate short names (e.g., type=Building
becomes type=PuntoDiInteresse
or
type=KiinnostuksenKohde
).
There is a limitation in this mapping, it is not possible to change the core context attribute names, like id
, type
,
or @context
for example.
Let's try to recover the information about the farm using the alternate context file for the German language.
curl -L -g 'http://localhost:1026/ngsi-ld/v1/entities/?type=Geb%C3%A4ude&q=name[*]%3D%3D%22Gro%C3%9Fe%20Rote%20Scheune%22&lang=en&attrs=name%2Ckategorie' \
-H 'Link: <http://context/alternate-context.jsonld>; rel="http://www.w3.org/ns/json-ld#context"; type="application/ld+json"' \
-H 'Accept: application/ld+json'
The response is returned in JSON-LD format with short form attribute names (kategorie
, name
, PuntoDiInteresse
)
which correspond to the short names provided in the alternate context. Note that core context terms (id
, type
,
value
, etc.) cannot be overridden directly but would require an additional JSON-LD expansion/compaction operation
(programmatically).
[
{
"id": "urn:ngsi-ld:Building:barn002",
"type": "Gebäude",
"name": {
"type": "LanguageProperty",
"languageMap": {
"en": "Big Red Barn",
"de": "Große Rote Scheune",
"ja": "大きな赤い納屋"
}
},
"kategorie": {
"type": "VocabularyProperty",
"vocab": "scheune"
},
"@context": [
"http://context/alternate-context.jsonld",
"https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context-v1.7.jsonld"
]
}
]
If we change the context to a Finnish language:
curl -L 'http://localhost:1026/ngsi-ld/v1/entities/?type=Geb%C3%A4ude&q=kategorie%3D%3Dbauernhof&attrs=name%2Ckategorie' \
-H 'Link: <http://context/alternate-context.jsonld>; rel="http://www.w3.org/ns/json-ld#context"; type="application/ld+json"' \
-H 'Accept: application/ld+json'
The response is returned in JSON-LD format with short form attribute names (luokka
, nimi
, KiinnostuksenKohde
).
[
{
"@context": "http://context/alternate-context-fi.jsonld",
"id": "urn:ngsi-ld:Building:poi123456",
"type": "KiinnostuksenKohde",
"luokka": "107",
"location": {
"type": "Point",
"coordinates": [
60.17021,
24.95212
]
},
"nimi": {
"languageMap": {
"fi": "Helsingin tuomiokirkko",
"en": "Helsinki Cathedral",
"it": "Duomo di Helsinki"
}
}
}
]
MIT © 2020-2023 FIWARE Foundation e.V.