A powerful, browser-based JSON-LD editor and SHACL validator for any RDF vocabulary
Interactive viewer and editor for JSON-LD metadata with real-time SHACL validation. Originally developed for DDI-CDI (Data Documentation Initiative - Cross Domain Integration) and CDIF (CDI Foundation), but works with any JSON-LD vocabulary and SHACL shapes including schema.org, DCAT, DataCube, SKOS, and custom ontologies.
Provides real-time validation, property classification, complex object editing, and array management directly in the browser. Perfect for researchers, data curators, and developers working with semantic web standards.
- DDI Alliance = International membership organization (since 2003) including:
- Major statistical agencies (Eurostat, US Census Bureau, Statistics Canada, etc.)
- Research institutions (ICPSR, GESIS, UK Data Archive, etc.)
- Academic organizations worldwide
- DDI = Data Documentation Initiative - the metadata standard they maintain
- CDI = Cross Domain Integration - the newest DDI specification (Version 1.0 released January 2025)
- Designed for cross-domain interoperability (not just social sciences anymore)
- Built on semantic web technologies (RDF, JSON-LD) - machine-readable and linkable
- Enables data from different domains to be combined and compared
- Authoritative standard - backed by major institutions who actually use it
- FAIR data principles - helps make data Findable, Accessible, Interoperable, Reusable
- Future-proof - semantic web approach means data stays meaningful over time
- Growing adoption - increasingly required by funders and data repositories
- JSON-LD files are complex nested structures - hard to edit manually
- SHACL validation typically requires command-line tools or enterprise software
- No easy way to visually see if your metadata is valid
- Existing tools are either too simple (text editors) or too complex (enterprise RDF tools)
- Dataverse gap - While Dataverse supports the DDI Codebook metadata standard, there's no built-in support for the newer DDI-CDI semantic web format
- Browser-based JSON-LD editor with real-time SHACL validation
- Works with any vocabulary (DDI-CDI, schema.org, DCAT, SKOS, etc.)
- No installation required - runs entirely in the browser
- Can integrate with Dataverse or work standalone
The problem: Most JSON-LD editors are either too simple (plain text editors) or too complex (enterprise RDF tooling). Validating against SHACL shapes often requires command-line tools or separate validation services.
This solution:
- β¨ Visual editing - See and edit your JSON-LD structure as nested, collapsible cards
- β Instant validation - Real-time SHACL validation with color-coded feedback
- π¨ Smart UI - Input fields adapt to SHACL constraints (dates, numbers, enumerations)
- π Complex objects - Create nested objects and references with modal helpers
- π Array support - Convert between single/array values, manage reference lists
- π No installation - Runs entirely in the browser, no server needed
- π Extensible - Integrate with Dataverse or use as standalone tool
Perfect for:
- Data curators working with schema.org Dataset markup
- Researchers creating DDI-CDI metadata
- Developers testing SHACL validation rules
- Anyone editing complex JSON-LD with nested structures
- Any JSON-LD vocabulary - schema.org, DCAT, DataCube, SKOS, FOAF, Dublin Core, and more
- Custom SHACL shapes - Load validation shapes from any URL or local file
- Standard JSON-LD processing - Uses W3C JSON-LD algorithms (jsonld.js)
- Vocabulary-agnostic editing - Works with any RDF ontology
- Namespace flexibility - Handles prefixed and expanded forms
- Smart input types based on SHACL datatype constraints (text, number, date, URI, etc.)
- Complex object support with nested node creation and inline editing
- Reference management - Link to existing nodes or create new blank nodes
- Array operations:
- Convert single value β array
- Add/remove array items
- Support for both value arrays and reference arrays
- Property management with searchable SHACL-based dropdowns
- Cardinality enforcement respecting SHACL min/maxCount
- Delete protection for required fields (SHACL sh:minCount > 0)
- Custom properties - Add properties not defined in SHACL shapes
- Namespace management:
- View and add custom namespace prefixes
- Remove custom namespaces (built-in protected)
- Integration with property/node creation
- Document creation - Create new JSON-LD documents from scratch with shape-specific contexts
- Unified add component - Consistent UX for adding properties and root nodes
- Real-time SHACL validation using Core SHACL features
- Visual indicators showing validation status
- Detailed reports with actionable feedback
- Property suggestions for missing fields
- Enhanced search:
- Search counter ("X of Y matches")
- Case-sensitive and regex modes
- Previous/Next navigation
- Keyboard shortcuts (F3, Shift+F3, Enter)
- Current match highlighting
- Comprehensive filters:
- Node type filter
- Validation status filter
- Property status filter (SHACL/extra)
- Hide empty properties
- Search scope selection (names/values/IDs/types)
- State persistence - Filter settings saved across sessions
- Load local files for standalone editing
- Load from Dataverse with URL parser (6 formats supported)
- Save to Dataverse with smart visibility:
- Standalone mode: Always visible (can save to Dataverse anytime)
- Embedded mode: Visible only when changes exist (already viewing from Dataverse)
- Reactive updates via change tracking
- Export JSON-LD with all modifications
- Change tracking with visual indicators
Live demo: https://libis.github.io/cdi-viewer/
Quick workflow (DDI-CDI mode - default):
- Click "Load Local File" β select any JSON-LD file
- DDI-CDI shapes are preloaded automatically
- Click "Enable Edit Mode" to start editing
- Add/edit/delete properties with visual feedback
- Click "Export JSON-LD" to download your changes
For other vocabularies:
- Visit https://libis.github.io/cdi-viewer/?shacl=generic
- Select your vocabulary's SHACL shapes from the dropdown
- Or enter a custom SHACL URL
| Vocabulary | Use Case | SHACL Shapes |
|---|---|---|
| schema.org | Dataset markup for Google Dataset Search | Built-in or custom |
| DDI-CDI | Social science data documentation | ddi-cdi-official (built-in) |
| DCAT-AP | EU open data catalog metadata | dcat-ap-3.0 (built-in) |
| DataCube | Statistical data cubes | w3c-datacube (built-in) |
| SKOS | Thesauri and taxonomies | skos (built-in) |
| Custom | Your own ontology | Provide SHACL URL |
# 1. Start with minimal JSON-LD
{
"@context": "https://schema.org/",
"@type": "Dataset",
"@id": "http://example.org/dataset/1",
"name": "My Dataset"
}
# 2. Load in the viewer
# 3. Add properties via the dropdown: description, keywords, creator
# 4. Create nested objects: creator β Person with name, affiliation
# 5. Validate against schema.org SHACL shapes
# 6. Export complete, validated JSON-LDThe demo includes DDI-CDI examples in the examples/cdi/ directory:
SimpleSample.jsonld- Minimal DDI-CDI examplese_na2so4-XDI-CDI-CDIF.jsonld- X-ray spectroscopy dataESS11-subset_DDICDI.jsonld- Comprehensive example
The viewer automatically detects DDI-CDI shapes and enables DDI-CDI specific features:
- When you load SHACL shapes containing
ddialliance.org/Specification/DDI-CDInamespace, DDI-CDI mode is enabled - This activates legacy context handling, DDICDIModels normalization, and default namespace resolution
- For other vocabularies (schema.org, DCAT, etc.), the tool operates in generic JSON-LD mode
Detection is version-agnostic and protocol-agnostic - works with any DDI-CDI version (1.0, 2.0, etc.) over http or https.
You can manually override the default namespace in js/core.js if needed:
// For DDI-CDI:
window.defaultTypeNamespace =
"http://ddialliance.org/Specification/DDI-CDI/1.0/RDF/";
// For schema.org:
window.defaultTypeNamespace = "http://schema.org/";
// For DCAT:
window.defaultTypeNamespace = "http://www.w3.org/ns/dcat#";This allows type names without prefixes to be resolved automatically.
If you need to handle legacy context URLs (redirecting old URLs to local copies), edit LEGACY_CONTEXT_URLS in js/cdi-json-ld-helpers.js:
const LEGACY_CONTEXT_URLS = {
"https://old-url.org/context.jsonld": "shapes/local-context.jsonld",
// Add more mappings as needed
};# Clone the repository
git clone https://github.com/libis/cdi-viewer.git
cd cdi-viewer
# Install dependencies
npm install
# Run tests
npm test
# Check code quality
npm run lint
# Build for production
npm run build
# Start development server
npm run dev
# Open http://localhost:8000Load test files via URL parameter:
# Default: DDI-CDI mode (official shapes preloaded automatically)
http://localhost:8000/
# Generic mode (no shapes preloaded - for any JSON-LD vocabulary)
http://localhost:8000/?shacl=generic
# CDIF mode (CDIF Discovery shapes preloaded)
http://localhost:8000/?shacl=cdif-core
# DCAT-AP mode (EU DCAT Application Profile)
http://localhost:8000/?shacl=dcat-ap-3.0
# DataCube mode (W3C RDF Data Cube)
http://localhost:8000/?shacl=w3c-datacube
# SKOS mode (Simple Knowledge Organization System)
http://localhost:8000/?shacl=skos
# Local fallback (built-in DDI-CDI shapes for offline use)
http://localhost:8000/?shacl=local-fallback
Note: Since version 1.0, the viewer defaults to DDI-CDI mode (matching the cdi-viewer name). Use ?shacl=generic for a clean start with any vocabulary.
The project includes a test page for the bundled version:
http://localhost:8000/test-bundle.html
This loads dist/cdi-viewer.min.js (44KB minified) instead of individual JS files.
Comprehensive documentation is available in the docs/ directory:
- GENERIC_USAGE.md - Complete guide for using with any JSON-LD vocabulary (schema.org, DCAT, etc.)
- CDI_PREVIEWER.md - DDI-CDI specific features, usage instructions, and customization
- CDIF_DISCOVERY_SHAPES_FIX.md - SHACL shapes implementation and Core SHACL conversion patterns
- ARCHITECTURE.md - Technical architecture and design decisions
- CONTRIBUTING.md - Development workflow and contribution guidelines
- jQuery 3.7.1 - DOM manipulation
- Bootstrap 3.3.7 - UI components
- N3.js v1.16.x (~150KB) - RDF/Turtle parsing
- jsonld.js (~130KB) - JSON-LD processing
- shacl-engine (~1.1MB) - Core SHACL + SPARQL validation
- Jest - Testing framework with JSDOM
- Rollup - Module bundler (44KB output)
- ESLint + Prettier - Code quality and formatting
- npm scripts - Build automation
npm run dev # Start development server (port 8000)
npm run build # Build production bundle (dist/cdi-viewer.min.js)
npm test # Run all tests (26 tests)
npm run test:watch # Run tests in watch mode
npm run test:coverage # Generate coverage report
npm run lint # Check code quality (ESLint + Prettier)- Core SHACL + SPARQL - Full SHACL validation with SPARQL constraints (~1.1MB)
- ES6 modules - Modern code with proper imports (like shacl-engine examples)
- Dual bundles - Validation (1.1MB) + App logic (38KB)
- Modular structure - Separated concerns, incremental ES6 migration
- Production ready - Configurable logging, clean code
- Dual deployment - Standalone (GitHub Pages) + Dataverse integration
- Test coverage - 26 tests preventing regressions
The editor uses a manual synchronization pattern without automatic data binding frameworks. This design prioritizes simplicity, transparency, and control over edit operations.
State Management (state.js):
{
jsonData: Object|null, // Current working copy of JSON-LD document
originalData: Object|null, // Immutable snapshot for reset functionality
changedElements: Set, // Tracks modified properties (composite IDs)
// ... 12 other state properties
}jsonData- The live working copy that gets modified as users editoriginalData- Immutable backup created when loading/creating documents (never modified)changedElements- Persistent Set storing composite IDs like"nodeId.propertyKey"
The editor follows a unidirectional data flow pattern:
βββββββββββββββ
β jsonData β (Source of truth in state.js)
ββββββββ¬βββββββ
β
βΌ render.js: createTree()
βββββββββββββββ
β DOM Tree β (Visual representation)
ββββββββ¬βββββββ
β
βΌ User edits inputs
βββββββββββββββ
β Modified β (Changed properties tracked in Set)
β DOM State β
ββββββββ¬βββββββ
β
βΌ data-extraction.js: collectChangesFromDOM()
βββββββββββββββ
β jsonData β (Synchronized back before save/export)
βββββββββββββββ
Key Points:
- No automatic binding - Changes in DOM don't instantly update
jsonData - Explicit sync - Must call
collectChangesFromDOM()to sync DOM βjsonData - Change tracking - Modified properties are tracked in a persistent Set
- Sync timing - Synchronization happens before save, export, or mode toggle
When a property is edited:
// event-handlers.js: Property change handler
$(document).on("change", ".property-input", function () {
const $propertyRow = $(this).closest(".property-row");
const nodeId = $propertyRow.attr("data-node-id");
const propertyKey = $propertyRow.attr("data-property");
// Mark as changed in UI
$propertyRow.addClass("changed");
// Track in persistent Set with composite ID
addChangedElement(`${nodeId}.${propertyKey}`);
updateChangedIndicator(); // Show "X unsaved changes"
});Composite ID structure: "nodeId.propertyKey"
- Example:
"_:b0.name","http://example.org/dataset/1.description" - Allows precise tracking of which properties on which nodes were modified
- Prevents duplicate tracking (Set automatically handles uniqueness)
When to sync - collectChangesFromDOM() is called before:
- Save to Dataverse (
saveToDataverse()) - Export JSON-LD (
exportData()) - Toggle Edit Mode (from edit β view)
Sync implementation:
// data-extraction.js: collectChangesFromDOM()
function collectChangesFromDOM() {
// 1. Check if any changes exist
if (getChangedElementsCount() === 0) return;
// 2. Parse composite IDs from Set
const changedIds = getAllChangedElements(); // Returns Set<string>
// ["_:b0.name", "http://example.org/1.description"]
// 3. Group changes by nodeId
const changesByNode = new Map();
// Map { "_:b0" => Set{"name", "age"}, ... }
// 4. For each changed property:
for (const [nodeId, propertyKeys] of changesByNode) {
const node = getNodeById(nodeId); // Find node in jsonData
const $card = $(`.node-card[data-node-id="${nodeId}"]`);
for (const key of propertyKeys) {
const $input = $card.find(`[data-property="${key}"] input`);
const value = $input.val(); // Read current DOM value
// 5. Mutate jsonData directly
node[key] = value; // In-place update
}
}
// jsonData is now synchronized with DOM edits
// Note: Changed tracking persists until save/export completes
}After successful save/export:
// Clear visual indicators and tracking
$(".property-row.changed").removeClass("changed");
clearChangedElements(); // Empty the SetAdvantages:
- β Explicit control - Clear when data flows between DOM and state
- β Performance - No overhead of automatic observers/dirty checking
- β Transparency - Easy to debug (single sync point)
- β Simple architecture - No framework complexity, minimal dependencies
- β Batched updates - Collect all changes at once before sync
Trade-offs:
β οΈ Must remember to callcollectChangesFromDOM()before operations that need current dataβ οΈ DOM state can diverge fromjsonDatauntil explicit syncβ οΈ Requires careful tracking to know when sync is needed
This pattern is ideal for document editing workflows where users make multiple changes before saving, rather than real-time collaborative editing scenarios.
jsonData during editing - the DOM is the source of truth for current values until collectChangesFromDOM() is called.
Correct patterns:
// β
Before save/export: sync first
collectChangesFromDOM();
const currentData = getJsonData(); // Now accurate
// β
During rendering: read from jsonData (render creates DOM from data)
function renderNode(node) {
// node comes from jsonData
createInputForValue(node.propertyKey); // Creates DOM from data
}Incorrect patterns:
// β WRONG: Reading jsonData during editing without sync
function validateCurrentData() {
const data = getJsonData(); // Outdated! DOM has unsaved changes
validateData(data); // Will validate old values
}
// β
CORRECT: Sync first, then read
function validateCurrentData() {
collectChangesFromDOM(); // Sync DOM β jsonData
const data = getJsonData(); // Now current
validateData(data);
}cdi-viewer/
βββ index.html # Main entry point (standalone mode)
βββ css/
β βββ cdi-preview.css # Styles (includes search/filter styles)
βββ src/
β βββ jsonld-editor/
β βββ advanced-search.js # Enhanced search (~240 lines)
β βββ advanced-filter.js # Filter system (~340 lines)
β βββ unified-add-component.js # Add UI component
β βββ namespace-manager.js # Namespace management
β βββ state.js # State management
β βββ core.js # Initialization
β βββ validation.js # SHACL validation
β βββ cdi-shacl-loader.js # Shape loading
β βββ cdi-shacl-helpers.js # Property classification
β βββ cdi-json-ld-helpers.js # JSON-LD processing
β βββ cdi-graph-helpers.js # Graph manipulation
β βββ render.js # UI rendering
β βββ property-suggestions.js # Property suggestions
β βββ data-extraction.js # Export pipeline
β βββ event-handlers.js # Event wiring
βββ dist/
β βββ cdi-app.bundle.js # App logic (38KB)
β βββ cdi-validation.bundle.js # SHACL validation (1.1MB)
βββ shapes/
β βββ ddi-cdi-official.ttl # DDI-CDI 1.0 shapes
β βββ cdif-core.ttl # CDIF Discovery shapes
βββ examples/cdi/
β βββ *.jsonld # Sample files
βββ docs/
βββ CDI_PREVIEWER.md
βββ CDIF_DISCOVERY_SHAPES_FIX.md
Perfect for:
- Research data metadata - DDI-CDI, DCAT, DataCite
- Schema.org datasets - Validate and edit Dataset markup
- Library metadata - BIBFRAME, Dublin Core
- Domain-specific ontologies - Any RDF vocabulary with SHACL shapes
Perfect for:
- Exploring any JSON-LD metadata files offline
- Testing SHACL validation with custom shapes
- Educational purposes (learning JSON-LD, SHACL)
- Quick metadata inspection and editing
Ideal for:
- Direct editing within Dataverse installations
- API-based metadata updates for any vocabulary
- Production metadata management
- Institutional repositories
The viewer can be registered as an external tool in Dataverse for any JSON-LD content type:
curl -X POST -H 'Content-Type: application/json' \
http://localhost:8080/api/admin/externalTools \
-d '{
"displayName": "JSON-LD Viewer",
"description": "View and edit JSON-LD metadata with SHACL validation",
"toolName": "jsonldViewer",
"scope": "file",
"type": "explore",
"hasPreviewMode": true,
"toolUrl": "https://libis.github.io/cdi-viewer/",
"toolParameters": {
"queryParameters": [
{"fileid": "{fileId}"},
{"siteUrl": "{siteUrl}"},
{"key": "{apiToken}"},
{"datasetid": "{datasetId}"},
{"datasetversion": "{datasetVersion}"}
]
},
"contentType": "application/ld+json"
}'Note: Originally developed for DDI-CDI, but works with any JSON-LD vocabulary.
The viewer supports multiple shape sources via the "Select SHACL shapes" dropdown:
- DDI-CDI 1.0 Official - Full DDI-CDI shapes from ddi-cdi.github.io
- CDIF Discovery Core - Browser-compatible schema.org Dataset validation
- DCAT-AP 3.0 - EU DCAT Application Profile for open data catalogs
- W3C DataCube - RDF Data Cube vocabulary for statistical data
- SKOS - Simple Knowledge Organization System for thesauri and taxonomies
- Local Fallback - Built-in DDI-CDI shapes for offline use
- Custom URL - Load any Core SHACL shapes file (Turtle format) from a URL you provide
The Custom URL option allows you to load SHACL shapes for any vocabulary:
- Select "Custom URL" from the dropdown
- Enter the full URL to your Turtle (.ttl) file
- Click "Load Custom Shapes"
Popular standards with published SHACL shapes:
- DCAT-AP 3.0: https://semiceu.github.io/DCAT-AP/releases/3.0.0/html/shacl/shapes.ttl (built-in)
- DataCube: https://raw.githubusercontent.com/w3c/shacl/master/shapes/datacube.shapes.ttl (built-in)
- SKOS: https://raw.githubusercontent.com/skohub-io/skohub-shapes/main/skos.shacl.ttl (built-in)
- FOAF: SHACL-Catalog (browse for specific shapes)
- GeoSPARQL: SHACL Play! Catalog (browse for specific shapes)
- RO-Crate: rocrate-validator profiles (profile-specific shapes)
Note: Only Core SHACL features are supported (no SPARQL-based constraints).
If you need to create your own SHACL shapes for validation:
Learn SHACL:
- W3C SHACL Specification - Official specification
- SHACL Playground - Interactive editor and validator
- SHACL Tutorial - Step-by-step guide
Generate SHACL from Ontologies:
- Astrea - Generate SHACL shapes from OWL ontologies
- SHACL Shape Generator - Automated shape generation
Validate Your Shapes:
- SHACL Play! - Online SHACL validator
- pySHACL - Python-based validator
Publishing Your Shapes:
- Host the
.ttlfile on GitHub Pages (free, static hosting) - Use raw GitHub URLs:
https://raw.githubusercontent.com/username/repo/main/shapes.ttl - Ensure CORS is enabled for browser access
- Use permanent URLs when possible (DOI, w3id.org, etc.)
Requirements:
- Save as Turtle format (
.ttl) - Use only Core SHACL features (no SPARQL constraints)
- Test with pySHACL or SHACL Play! before publishing
- Include namespace declarations and shape definitions
Enable detailed logging with ?debug=true:
https://libis.github.io/cdi-viewer/?debug=true
Contributions are welcome! This project is maintained by LIBIS @ KU Leuven.
Please read CONTRIBUTING.md for details on our development process, testing requirements, and code style guidelines.
- Fork and clone the repository
- Install dependencies:
npm install - Make your changes in
src/(ES6 modules) orjs/(legacy scripts) - Build:
npm run build(ornpm run build:watchfor development) - Run tests:
npm test - Check linting:
npm run lint - Test locally:
npm run dev(starts server on http://localhost:8000) - Submit a pull request
The project uses a hybrid architecture during migration to ES6 modules:
src/ # Modern ES6 modules (new code)
βββ validation.js # β
SHACL validation (proper imports)
βββ index.js # Entry point (future: all modules)
βββ README.md # Module documentation
js/ # Legacy plain scripts (being migrated)
βββ core.js # Initialization and config
βββ render.js # UI rendering
βββ ... # Other modules
dist/ # Built bundles for browser
βββ cdi-validation.bundle.js # 1.1MB - SHACL with SPARQL
βββ cdi-app.bundle.js # 38KB - App logic
Build Process:
# Write code with ES6 imports (like validation.js)
import Validator from 'shacl-engine/Validator.js';
# Rollup bundles all dependencies into browser files
npm run build
# Two bundles created automatically:
# - dist/cdi-validation.bundle.js (ES6 module + dependencies)
# - dist/cdi-app.bundle.js (legacy scripts concatenated)Why ES6 Modules?
- β
Write clean Node.js-style code with
import/export - β Same pattern as shacl-engine, jsonld.js, n3.js
- β Better IDE support, type checking, debugging
- β Single minimized bundle for production
- β Incremental migration (legacy + modern code coexist)
See src/README.md for details on the ES6 module structure and migration plan.
For detailed technical documentation, see:
- ARCHITECTURE.md - Comprehensive technical guide
- CONTRIBUTING.md - Development workflow and guidelines
- docs/CDI_PREVIEWER.md - Feature documentation
This project is licensed under the Apache License 2.0 - see the LICENSE file for details.
Developed by LIBIS @ KU Leuven (Katholieke Universiteit Leuven)
Originally created for DDI-CDI metadata, but designed as a generic JSON-LD viewer and editor that works with any vocabulary and SHACL shapes.
Part of the broader Dataverse ecosystem:
- Issues: GitHub Issues
- Documentation: See
docs/directory - Dataverse Community: dataverse-dev@googlegroups.com
- Live Demo: https://libis.github.io/cdi-viewer/
- GitHub Repository: https://github.com/libis/cdi-viewer
- Documentation: Generic Usage Guide | DDI-CDI Guide
- JSON-LD Specification: https://json-ld.org/
- SHACL Specification: https://www.w3.org/TR/shacl/
- Semantic Web Resources: JSON-LD Playground | SHACL Playground
- DDI-CDI Specification: https://ddialliance.org/ddi-cdi
- LIBIS: https://www.libis.be/
Made with β€οΈ by LIBIS @ KU Leuven
Originally built for DDI-CDI metadata, evolved into a powerful general-purpose JSON-LD editor. Works with any RDF vocabulary!