THE FOUNDATION

fwJsonld — JSON-LD @context Engine

Download, parse, cache, expand, and compact JSON-LD @context documents. The semantic layer that makes NGSI-LD interoperable across systems and organizations.

What It Does

fwJsonld is the JSON-LD @context processing engine for the FiWorks stack. It downloads remote @context documents using the fwHttp client, parses them into dual fwHash tables (name→URI and URI→name), caches the result in a thread-safe context cache, and performs full expansion and compaction of NGSI-LD entities.

This is the library that turns short attribute names like "temperature" into fully qualified URIs like "https://uri.fiware.org/ns/data-models#temperature" and back again — the core operation that makes NGSI-LD a linked-data protocol rather than just another JSON API.

Trace Levels

fwJsonld uses trace levels 150–199. See the full trace level table for all assignments across the fw-lib stack.

Types

// Single term definition in a context
typedef struct FwJsonldItem {
    char*  name;  // Short name (e.g., "temperature")
    char*  id;    // Expanded IRI (e.g., "https://uri.etsi.org/ngsi-ld/temperature")
    char*  type;  // Type annotation ("@id", "@vocab", "DateTime", etc.) or NULL
} FwJsonldItem;

// Parsed context with dual hash tables
typedef struct FwJsonldContext {
    char*        url;      // URL of this context (NULL for inline)
    KHashTable*  nameHT;   // name -> FwJsonldItem (for expansion)
    KHashTable*  valueHT;  // IRI  -> FwJsonldItem (for compaction)
    char*        vocab;    // @vocab value or NULL
    bool        isArray;  // true = array of child contexts
} FwJsonldContext;

Each context maintains two hash tables: one for expansion (name → IRI) and one for compaction (IRI → name), enabling O(1) lookup in both directions.

Functions

Lifecycle

int  fwJsonldInit(FaAlloc* fwAllocP, const char* coreContextUrl);
void fwJsonldCleanup(void);

Initialize the library: creates the context cache and downloads the NGSI-LD core context. Pass NULL for coreContextUrl to use the default (v1.8). Returns 0 on success, -1 on failure.

Context Parsing

FunctionDescription
fwJsonldContextFromTree(contextNode, fwAllocP)Parse an @context from a FtNode tree — handles string (URL), object (inline), and array forms
fwJsonldContextFromObject(objectNode, fwAllocP, url)Parse an inline context object; two-pass algorithm to resolve prefix values
fwJsonldContextFromUrl(url, fwAllocP)Download and parse a context URL; checks cache first, deduplicates concurrent downloads

Expansion & Compaction

char*       fwJsonldExpand(FwJsonldContext* contextP, const char* name,
                          FaAlloc* fwAllocP, FwJsonldItem** itemPP);
const char* fwJsonldCompact(FwJsonldContext* contextP, const char* iri);
char*       fwJsonldPrefixExpand(FwJsonldContext* contextP, const char* name,
                                FaAlloc* fwAllocP);

Expansion lookup chain (first match wins):

  1. Already a full IRI (urn:, http://, https://) — return as-is
  2. Contains : — attempt prefix expansion
  3. Core context lookup
  4. User context lookup (last-wins for context arrays)
  5. @vocab fallback
  6. Return unchanged if no mapping found

Cache Operations

FwJsonldContext* fwJsonldCacheLookup(const char* url);
void            fwJsonldCacheInsert(FwJsonldContext* contextP);
FwJsonldContext* fwJsonldCoreContext(void);
bool           fwJsonldAlreadyExpanded(const char* value);

Context Array Priority

Per the JSON-LD specification, when a context is an array the last context has highest priority. This means inline context definitions can override definitions from a downloaded core context:

{
  "@context": [
    "https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context-v1.8.jsonld",
    { "temperature": "https://my-override.example.com/temp" }
  ]
}

Here the inline context overrides any definition of "temperature" from the core context.

Usage Example

#include "fwJsonld/fwJsonld.h"
#include "fwAlloc/FaAlloc.h"
#include "fwJson/fwJson.h"

int main(void)
{
    FaAlloc  fwAlloc;
    char    buf[64 * 1024];

    faBufferInit(&fwAlloc, buf, sizeof(buf), 32 * 1024, NULL, "main");

    // Initialize library (downloads + caches core context)
    if (fwJsonldInit(&fwAlloc, NULL) != 0)
        return 1;

    // Parse an inline @context
    char* json = faStrdup(&fwAlloc, "{\"temperature\": \"https://example.com/temp\"}");
    FjParser* fwJsonP = fjBufferCreate(NULL, &fwAlloc);
    FtNode* tree = kjParse(fwJsonP, json);

    FwJsonldContext* ctx = fwJsonldContextFromObject(tree, &fwAlloc, NULL);

    // Expand: short name -> full IRI
    char* expanded = fwJsonldExpand(ctx, "temperature", &fwAlloc, NULL);
    // expanded == "https://example.com/temp"

    // Compact: full IRI -> short name
    const char* compacted = fwJsonldCompact(ctx, "https://example.com/temp");
    // compacted == "temperature"

    // Prefix expansion: given context {"schema": "https://schema.org/"}
    // fwJsonldExpand(ctx, "schema:name", &fwAlloc, NULL) -> "https://schema.org/name"

    fwJsonldCleanup();
    return 0;
}