RDF Parser/Serializer Implementation for Round-Trip Support in Ontology-Playground

The RDF parser/serializer achieves round-trip support through coupled modules in src/lib/rdf/serializer.ts and src/lib/rdf/parser.ts that use custom OWL annotations to preserve type metadata, identifiers, and relationship attributes during conversion between TypeScript interfaces and RDF/XML.

Ontology-Playground stores ontologies as TypeScript interfaces (Ontology, EntityType, Property, Relationship) and provides bidirectional conversion to standard RDF/XML (OWL) format. The implementation ensures lossless data transfer by embedding custom annotation properties that the parser reads back to reconstruct the exact internal model. This round-trip capability is verified by the test suite in src/lib/rdf/roundtrip.test.ts.

Core Architecture

The round-trip system relies on two tightly coupled modules that mirror each other’s logic:

  • src/lib/rdf/serializer.ts: Exports the internal TypeScript model to RDF/XML by generating OWL classes, datatype properties, and object properties with deterministic URIs and custom annotations.
  • src/lib/rdf/parser.ts: Imports RDF/XML by parsing the document with DOMParser, reconstructing EntityType instances from OWL classes, and restoring properties using stored metadata annotations.

These modules share a common URI derivation strategy via deriveBaseUri, which normalizes the ontology name into a URL-safe slug (http://example.org/ontology/<slug>/). This ensures that localNameFromUri can reliably map RDF resources back to original entities during import.

How Round-Trip Conversion Works

The implementation preserves model fidelity through five specific mechanisms:

  1. Stable Base URI Derivation: The deriveBaseUri function creates a consistent namespace from the ontology name, ensuring every exported resource uses a predictable URI that the parser can resolve back to the original entity ID.

  2. Custom Type Annotations: While standard RDF uses XSD datatypes, Ontology-Playground stores the original property type in ont:propertyType and enum values in ont:enumValues. The parser reads these custom elements first, falling back to XSD_TO_TYPE mappings only when annotations are absent.

  3. Identifier Flag Preservation: The serializer emits ont:isIdentifier for primary key properties. The parser detects this annotation and sets the corresponding isIdentifier boolean on the reconstructed Property object.

  4. Relationship Attribute Reassembly: Attributes attached to relationships are serialized as separate owl:DatatypeProperty elements with the annotation ont:relationshipAttributeOf. During parsing, these are grouped back into the Relationship.attributes array using the from and to entity mappings stored in ont:fromEntityId and ont:toEntityId.

  5. Data Binding Retention: ont:DataBinding elements capture external data-source mappings including source, table, and columnMapping. The parser detects these elements and reconstructs the DataBinding[] array alongside the ontology model.

Implementation Details by Module

Serializer (src/lib/rdf/serializer.ts)

The serializer builds an XML document following OWL/RDF syntax. It processes each EntityType to emit an owl:Class with annotations for ont:icon and ont:color. For properties, it generates owl:DatatypeProperty elements with:

  • ont:propertyType to store the original TypeScript type
  • ont:isIdentifier for primary key flags
  • ont:unit for measurement units
  • ont:enumValues for enumeration constraints

Relationships become owl:ObjectProperty elements with rdf:domain and rdf:range derived from entity IDs, or explicit ont:fromEntityId and ont:toEntityId annotations when domain/range are insufficient.

Parser (src/lib/rdf/parser.ts)

The parser validates the XML structure using DOMParser, then walks the OWL elements to reconstruct the model:

  • Reads rdfs:label and rdfs:comment for ontology metadata
  • Extracts EntityType details from <Class> elements, restoring visual customizations via ont:icon and ont:color
  • Processes <DatatypeProperty> elements to rebuild Property objects, respecting ont:propertyType, ont:isIdentifier, ont:unit, and ont:enumValues
  • Handles <ObjectProperty> elements, resolving entity references through explicit annotations or rdf:domain/rdf:range URIs
  • Reassembles relationship attributes using ont:relationshipAttributeOf to map them back to their parent relationships

Round-Trip Verification

The test suite in src/lib/rdf/roundtrip.test.ts validates that no information is lost during conversion. The test serializes a sample ontology to RDF/XML, parses it back, and asserts deep equality between the original and reconstructed objects.

import { serializeToRDF } from './serializer';
import { parseRDF } from './parser';
import { sampleOntology } from '../../data/sample';

const rdf = serializeToRDF(sampleOntology);
const { ontology: roundTripped } = parseRDF(rdf);

// Jest assertion used in roundtrip.test.ts
expect(roundTripped).toStrictEqual(sampleOntology);

This verification ensures that custom annotations, identifiers, enum values, and data bindings survive the export/import cycle intact.

Practical Code Examples

Serializing an Ontology to RDF

import { serializeToRDF } from './src/lib/rdf/serializer';
import type { Ontology } from './src/data/ontology';

const myOntology: Ontology = {
  name: 'Coffee Shop',
  description: 'A simple coffee-shop model',
  entityTypes: [
    {
      id: 'customer',
      name: 'Customer',
      icon: '👤',
      color: '#ff6600',
      properties: [{ 
        name: 'email', 
        type: 'string', 
        isIdentifier: true 
      }],
    },
  ],
  relationships: [],
};

const rdf = serializeToRDF(myOntology);
// rdf contains an OWL/RDF/XML document with ont:isIdentifier preserved

Parsing RDF Back to the Model

import { parseRDF } from './src/lib/rdf/parser';

const { ontology, bindings } = parseRDF(rdfString);

// ontology matches the original structure with full type fidelity
// bindings contains any DataBinding definitions from ont:DataBinding elements

Handling Enum Types

// The parser automatically restores enum values from ont:enumValues annotations
const property = {
  name: 'status',
  type: 'enum',
  enumValues: ['active', 'inactive'],
};

// When serialized, this stores:
// <ont:propertyType>enum</ont:propertyType>
// <ont:enumValues>["active","inactive"]</ont:enumValues>

Summary

  • Lossless conversion relies on custom annotations (ont:propertyType, ont:isIdentifier, ont:enumValues) stored alongside standard OWL properties
  • Deterministic URIs via deriveBaseUri ensure reliable resource mapping between serializer and parser
  • Relationship attributes are preserved through ont:relationshipAttributeOf and reassembled during parsing
  • Data bindings survive round-trips via dedicated ont:DataBinding elements
  • Validation occurs in src/lib/rdf/roundtrip.test.ts using deep equality assertions on reconstructed ontologies

Frequently Asked Questions

How does the parser handle custom property types without standard XSD equivalents?

The parser prioritizes the ont:propertyType annotation over XSD datatype inference. It reads this custom element first to restore the original TypeScript type; only if the annotation is missing does it fall back to the XSD_TO_TYPE mapping. This ensures that custom types like internal enums or specialized identifiers are reconstructed accurately even when no direct XSD equivalent exists.

What happens to relationship attributes during round-trip conversion?

Relationship attributes are emitted as separate owl:DatatypeProperty elements marked with ont:relationshipAttributeOf during serialization. The parser detects these markers and groups the properties back into the parent Relationship.attributes array, using ont:fromEntityId and ont:toEntityId to ensure they attach to the correct relationship endpoints.

Why does the serializer use a deterministic base URI?

The deriveBaseUri function generates a stable namespace from the ontology name, creating URIs like http://example.org/ontology/<slug>/. This determinism ensures that when the parser extracts localNameFromUri, it can reliably map RDF resources back to the original entity IDs without requiring additional lookup tables or UUID management.

How is data-binding information preserved in the RDF output?

Data bindings are serialized as ont:DataBinding elements containing attributes for source, table, and serialized columnMapping data. The parser collects all such elements during the XML walk and reconstructs the DataBinding[] array, enabling external data-source configurations to survive export and re-import operations.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →