JSON-LD Playground
ActivityPub uses JSON-LD for semantic data representation. Understanding JSON-LD helps debug context issues and ensure interoperability.
Online Tools
W3C JSON-LD Playground
The official tool for testing JSON-LD:
Features:
- Expand, compact, flatten, frame operations
- Multiple input formats
- Real-time validation
- Share examples via URL
Using the Playground
- Paste your ActivityPub object in the input
- Select operation (usually "Expand")
- View the expanded form to see resolved terms
JSON-LD Operations
Expansion
Expands compact terms to full IRIs:
Input:
{
"@context": "https://www.w3.org/ns/activitystreams",
"type": "Note",
"content": "Hello, world!"
}
Expanded:
[
{
"@type": ["https://www.w3.org/ns/activitystreams#Note"],
"https://www.w3.org/ns/activitystreams#content": [
{"@value": "Hello, world!"}
]
}
]
Compaction
Shortens full IRIs using a context:
const jsonld = require('jsonld');
const expanded = [{
"@type": ["https://www.w3.org/ns/activitystreams#Note"],
"https://www.w3.org/ns/activitystreams#content": [
{"@value": "Hello!"}
]
}];
const context = {"@context": "https://www.w3.org/ns/activitystreams"};
const compacted = await jsonld.compact(expanded, context);
// Result: { "@context": "...", "type": "Note", "content": "Hello!" }
Common Contexts
ActivityStreams
{
"@context": "https://www.w3.org/ns/activitystreams"
}
Defines: type, id, actor, object, content, published, etc.
Security (for HTTP Signatures)
{
"@context": [
"https://www.w3.org/ns/activitystreams",
"https://w3id.org/security/v1"
]
}
Adds: publicKey, publicKeyPem, owner
Mastodon Extensions
{
"@context": [
"https://www.w3.org/ns/activitystreams",
"https://w3id.org/security/v1",
{
"toot": "http://joinmastodon.org/ns#",
"Emoji": "toot:Emoji",
"discoverable": "toot:discoverable",
"sensitive": "as:sensitive"
}
]
}
Validation Script
Check Context Resolution
const jsonld = require('jsonld');
async function validateContext(object) {
try {
// Try to expand - will fail if context is invalid
const expanded = await jsonld.expand(object);
console.log('✅ Context is valid');
console.log('Expanded form:', JSON.stringify(expanded, null, 2));
return true;
} catch (error) {
console.error('❌ Context error:', error.message);
return false;
}
}
// Test
const note = {
"@context": "https://www.w3.org/ns/activitystreams",
"type": "Note",
"content": "Hello!"
};
validateContext(note);
Verify Term Resolution
async function checkTerms(object, expectedTerms) {
const expanded = await jsonld.expand(object);
const flat = expanded[0] || {};
const results = {};
for (const term of expectedTerms) {
const asNs = `https://www.w3.org/ns/activitystreams#${term}`;
results[term] = asNs in flat || `@${term}` in flat;
}
return results;
}
// Check if common terms resolve
const terms = ['type', 'actor', 'object', 'content', 'published'];
const results = await checkTerms(activity, terms);
console.log(results);
// { type: true, actor: true, object: true, content: true, published: true }
Common Issues
Missing Context
// ❌ Bad - no context
{
"type": "Note",
"content": "Hello"
}
// ✅ Good - has context
{
"@context": "https://www.w3.org/ns/activitystreams",
"type": "Note",
"content": "Hello"
}
Wrong Context URL
// ❌ Bad - typo in URL
{
"@context": "https://www.w3.org/ns/activitystream",
"type": "Note"
}
// ✅ Good - correct URL
{
"@context": "https://www.w3.org/ns/activitystreams",
"type": "Note"
}
Context Order Matters
// Later contexts override earlier ones
{
"@context": [
"https://www.w3.org/ns/activitystreams",
{
"content": "http://example.com/custom#content"
}
],
"content": "This uses custom namespace"
}
Unknown Terms
// Terms not in context are dropped during compaction
const object = {
"@context": "https://www.w3.org/ns/activitystreams",
"type": "Note",
"customField": "This will be lost"
};
// After round-trip through JSON-LD processor, customField may be gone
Testing ActivityPub Objects
Validate a Note
async function validateNote(note) {
const errors = [];
// Check context
if (!note['@context']) {
errors.push('Missing @context');
}
// Expand and check required fields
try {
const expanded = await jsonld.expand(note);
const obj = expanded[0] || {};
const asNs = 'https://www.w3.org/ns/activitystreams#';
if (!obj['@type']?.includes(`${asNs}Note`)) {
errors.push('type must be Note');
}
if (!obj[`${asNs}content`]) {
errors.push('Missing content');
}
if (!obj[`${asNs}attributedTo`]) {
errors.push('Missing attributedTo');
}
} catch (e) {
errors.push(`JSON-LD error: ${e.message}`);
}
return errors;
}
Validate an Activity
async function validateActivity(activity) {
const errors = [];
const asNs = 'https://www.w3.org/ns/activitystreams#';
try {
const expanded = await jsonld.expand(activity);
const obj = expanded[0] || {};
// Check for actor
if (!obj[`${asNs}actor`]) {
errors.push('Activity must have actor');
}
// Check for object (most activities need it)
const type = obj['@type']?.[0];
const intransitive = [
`${asNs}Arrive`,
`${asNs}Travel`,
`${asNs}Question`
];
if (!obj[`${asNs}object`] && !intransitive.includes(type)) {
errors.push('Activity should have object');
}
} catch (e) {
errors.push(`JSON-LD error: ${e.message}`);
}
return errors;
}
Local Testing Setup
Node.js Setup
npm install jsonld
const jsonld = require('jsonld');
// Custom document loader (for offline testing)
const customLoader = async (url) => {
const contexts = {
'https://www.w3.org/ns/activitystreams': {
// ActivityStreams context (cached)
}
};
if (contexts[url]) {
return {
document: contexts[url],
documentUrl: url
};
}
// Fall back to default loader
return jsonld.documentLoaders.node()(url);
};
jsonld.documentLoader = customLoader;
Browser Setup
<script src="https://cdnjs.cloudflare.com/ajax/libs/jsonld/8.3.2/jsonld.min.js"></script>
<script>
async function test() {
const note = {
"@context": "https://www.w3.org/ns/activitystreams",
"type": "Note",
"content": "Hello!"
};
const expanded = await jsonld.expand(note);
console.log(expanded);
}
test();
</script>
Quick Reference
┌────────────────────────────────────────────────────────────┐
│ JSON-LD QUICK REFERENCE │
├────────────────────────────────────────────────────────────┤
│ │
│ @context - Defines term mappings │
│ @id - Unique identifier (same as 'id' in AS2) │
│ @type - Object type (same as 'type' in AS2) │
│ @value - Literal value in expanded form │
│ │
│ Operations: │
│ • Expand - Resolve all terms to full IRIs │
│ • Compact - Shorten IRIs using context │
│ • Flatten - Remove nesting │
│ • Frame - Reshape to match template │
│ │
└────────────────────────────────────────────────────────────┘
Related Tools
- Actor Inspector - Inspect actors
- Debugging Tips - General debugging
- Compliance Checklist - Full validation