Detecting circular references in JSON Schema—especially ones like {"$ref": "#"} which refer back to the root—is important to prevent infinite loops when validating, parsing, or traversing the schema. In your case, the schema references itself directly via "$ref": "#", creating a circular dependency.
Here’s how you can detect such circular references in a Next.js TypeScript application, especially when processing JSON Schemas.
✅ Problem Recap
Your schema:
{
"name": "Circular Json Schema",
"strict": true,
"schema": {
"type": "object",
"properties": {
"self": { "$ref": "#" }
}
}
}
"$ref": "#"points to the root schema, which includes itself — this is intentionally circular.- If you recursively traverse this schema without cycle detection, you’ll get infinite recursion.
✅ Goal
Detect whether a JSON Schema contains circular $ref references during runtime.
🛠️ Solution: Detect Circular $refs Programmatically
You can write a cycle detection algorithm that traverses the schema while tracking already-visited pointers (using a Set or a Map).
Below is a TypeScript function to detect circular references in a JSON Schema:
type JSONSchema = {
$ref?: string;
type?: string;
properties?: { [key: string]: JSONSchema };
items?: JSONSchema | JSONSchema[];
anyOf?: JSONSchema[];
allOf?: JSONSchema[];
oneOf?: JSONSchema[];
[key: string]: any;
};
function hasCircularRef(
schema: JSONSchema,
refPath: string = '',
seenRefs: Set<string> = new Set()
): boolean {
// If we encounter a $ref, resolve what it points to (simplified for #)
if (schema.$ref) {
// We're simplifying: assuming only `#` (root) is used
const target = schema.$ref;
if (seenRefs.has(target)) {
return true; // Circular reference detected
}
// We simulate "resolving" the reference
// For `#`, we go to the root — in real use, you'd use a schema resolver
const newSeen = new Set(seenRefs);
newSeen.add(target);
// Now traverse the target — for `#`, it's the root schema
if (target === '#') {
return traverseSchema(schema, newSeen); // re-use current schema
}
// In a full implementation, fetch external/internal refs here
return false;
}
return traverseSchema(schema, seenRefs);
}
function traverseSchema(schema: JSONSchema, seenRefs: Set<string>): boolean {
if (typeof schema !== 'object' || schema === null) return false;
for (const key in schema) {
if (key === '$ref') continue; // handled above
const value = schema[key];
if (Array.isArray(value)) {
for (const item of value) {
if (typeof item === 'object' && item !== null) {
if (hasCircularRef(item, '', seenRefs)) return true;
}
}
} else if (typeof value === 'object' && value !== null) {
if (hasCircularRef(value, '', seenRefs)) return true;
}
}
return false;
}
🧪 Example Usage
const mySchema: JSONSchema = {
type: 'object',
properties: {
self: { $ref: '#' }, // circular
},
};
console.log(hasCircularRef(mySchema)); // true
🔄 How It Works
seenRefstracks$reftargets we’ve already visited.- When a
$refis encountered, we check if we’ve seen it before. - Since
"#"refers to the root, and the root contains the$ref, it will detect the cycle. - This avoids infinite recursion by short-circuiting.
🧩 Enhancements You Can Add
- Support for nested refs like
#/properties/name - External references like
http://example.com/schema.json#/defs/person - Use a schema resolver like
json-schema-ref-parser
📦 Recommended: Use json-schema-ref-parser
This is a production-ready tool that resolves $refs in JSON Schema and detects circular references automatically.
Install it:
npm install json-schema-ref-parser
Use it in your Next.js API route (Node.js environment):
import { bundle } from 'json-schema-ref-parser';
async function checkForCircular(schema: any) {
try {
await bundle(schema); // This resolves all $refs; throws if circular
return false; // No circular refs
} catch (err: any) {
if (err.message.includes('circular')) {
return true; // Circular reference detected
}
throw err; // Other error
}
}
⚠️ Note: This only works in Node.js (server side). Not in browser unless using a polyfill.
So use it in a Next.js API route if you’re validating uploaded schemas.
✅ Best Practice for Next.js
- Client-side (browser): Use manual traversal with
seenRefs(shown first). - Server-side (API route): Use
json-schema-ref-parserfor full robustness.
🔍 Summary
| Method | Detects Circular | Best For |
|---|---|---|
Manual traversal with Set<string> |
✅ Yes (limited) | Client-side, simple cases |
json-schema-ref-parser |
✅ Yes (full support) | Server-side, production use |
✅ Recommendation
For a Next.js app, use:
- Frontend: Light manual check if you control schema structure.
- Backend (API route): Use
json-schema-ref-parser.bundle()to detect and resolve circular references safely.
Let me know if you'd like a complete example with an API route!