This document defines an algorithm for converting JSON-LD 1.1 Frames into JSON Schema documents. The algorithm provides a systematic way to translate JSON-LD framing concepts into JSON Schema validation constructs, enabling validation of JSON-LD documents based on their expected frame structure.

This is an unofficial specification documenting the JSON-LD Frame to JSON Schema conversion algorithm. Comments and feedback are welcome via the GitHub repository.

Introduction

JSON-LD 1.1 Framing [[JSON-LD11-FRAMING]] provides a mechanism to shape and structure JSON-LD data according to developer-specified patterns. JSON Schema [[JSON-SCHEMA]] provides a vocabulary for validating the structure, types, and constraints of JSON documents. This specification defines a formal algorithm for converting frames into schemas, bridging these two technologies.

Use Cases

The conversion algorithm enables several important use cases:

Design Goals

The algorithm is designed with the following goals:

Conformance

A conforming implementation MUST implement the algorithm as specified in the Algorithm Definition section.

Terminology

The following terms are used throughout this specification:

frame
A JSON-LD Frame object as defined in [[JSON-LD11-FRAMING]]
schema
A JSON Schema document as defined in [[JSON-SCHEMA]]
framing flag
A keyword that controls framing behavior, including @embed, @explicit, @requireAll, and @omitDefault
context
A JSON-LD context that defines mappings from terms to IRIs and type coercion rules
type coercion
The process of specifying how values should be interpreted using the @type keyword within a context definition
property frame
A value in a frame object that describes the expected structure for a property

Algorithm Overview

The conversion process consists of the following phases:

  1. Parse Frame: Parse and validate the JSON-LD Frame input
  2. Extract Context: Extract type information from @context if present
  3. Process Frame Structure: Recursively process the frame to build the schema
  4. Apply Framing Flags: Apply global framing flags to schema properties
  5. Generate Schema: Output the complete JSON Schema document

The algorithm has:

Algorithm Definition

Main Entry Point

The main conversion function takes a frame and returns a schema.

frameToSchema(frame, schemaVersion, graphOnly)

Arguments:
  frame: A JSON-LD Frame object
  schemaVersion: JSON Schema version URI (optional, defaults to Draft 2020-12)
  graphOnly: Boolean flag to output only the schema for @graph items (optional, defaults to false)

Returns:
  A JSON Schema document

Steps:
  1. Let frameContent be frame
  2. If frame["@graph"] exists:
     1. Let graphValue be frame["@graph"]
     2. If graphValue is an array and its length is greater than 0:
        1. Set frameContent to graphValue[0]
     3. Else if graphValue is an object:
        1. Set frameContent to graphValue
     4. If frameContent["@context"] does not exist and frame["@context"] exists:
        1. Create a new object merging frame["@context"] with frameContent
        2. Set frameContent to this merged object
  3. Let graphItemSchema be a new object
  4. Set graphItemSchema["type"] to "object"
  5. Let flags be the result of calling extractFramingFlags(frameContent)
  6. Let context be the result of calling extractContext(frameContent)
  7. Call processFrameObject(frameContent, graphItemSchema, flags, context)
  8. If graphOnly is true:
     1. Let schema be a new object
     2. Set schema["$schema"] to schemaVersion
     3. Merge all properties from graphItemSchema into schema
     4. Return schema
  9. Let schema be a new object
  10. Set schema["$schema"] to schemaVersion
  11. Set schema["type"] to "object"
  12. Let properties be a new object
  13. Set properties["@context"] to an empty object
  14. Let graphProperty be a new object
  15. Set graphProperty["type"] to "array"
  16. Set graphProperty["items"] to graphItemSchema
  17. Set properties["@graph"] to graphProperty
  18. Set schema["properties"] to properties
  19. Set schema["required"] to ["@context", "@graph"]
  20. Set schema["additionalProperties"] to true
  21. Return schema
      

Extract Framing Flags

This function extracts framing flags from a frame object.

extractFramingFlags(frame)

Arguments:
  frame: A JSON-LD Frame object

Returns:
  An object containing framing flag values

Steps:
  1. Let flags be a new object
  2. Set flags["embed"] to frame["@embed"] if present, otherwise true
  3. Set flags["explicit"] to frame["@explicit"] if present, otherwise false
  4. Set flags["requireAll"] to frame["@requireAll"] if present, otherwise false
  5. Set flags["omitDefault"] to frame["@omitDefault"] if present, otherwise false
  6. Return flags
      

Extract Context

This function extracts type information from the JSON-LD context.

extractContext(frame)

Arguments:
  frame: A JSON-LD Frame object

Returns:
  A map from property names to type URIs

Steps:
  1. If frame["@context"] does not exist, return an empty map
  2. Let context be frame["@context"]
  3. Return the result of calling parseContext(context)
      
parseContext(context)

Arguments:
  context: A JSON-LD context (string, object, or array)

Returns:
  A map from property names to type URIs

Steps:
  1. Let typeMap be a new empty map
  2. If context is not an object, return typeMap
  3. For each keyvalue in context:
     1. If key starts with "@", continue to next iteration
     2. If value is not an object, continue to next iteration
     3. If value["@type"] exists:
        1. Set typeMap[key] to value["@type"]
  4. Return typeMap
      

Process Frame Object

This function processes a frame object and populates the corresponding schema object.

processFrameObject(frame, schema, flags, context)

Arguments:
  frame: A JSON-LD Frame object
  schema: The schema object to populate
  flags: Framing flags
  context: Type context mapping

Steps:
  1. Let properties be a new empty object
  2. Let required be a new empty array
  3. If frame["@type"] exists:
     1. Let typeSchema be the result of calling processTypeConstraint(frame["@type"])
     2. Set properties["@type"] to typeSchema
     3. If frame["@type"] is not a wildcard, append "@type" to required
  4. If frame["@id"] exists:
     1. Let idSchema be the result of calling processIdConstraint(frame["@id"])
     2. Set properties["@id"] to idSchema
     3. If frame["@id"] is not empty, append "@id" to required
  5. For each keyvalue in frame:
     1. If key is a framing keyword, continue to next iteration
     2. If key is "@type" or "@id", continue to next iteration
     3. Let propSchema be the result of calling processProperty(key, value, flags, context)
     4. Set properties[key] to propSchema
     5. If shouldBeRequired(key, value, flags) is true, append key to required
  6. If properties is not empty, set schema["properties"] to properties
  7. If required is not empty, set schema["required"] to required
  8. If flags["explicit"] is true:
     1. Set schema["additionalProperties"] to false
  9. Otherwise:
     1. Set schema["additionalProperties"] to true
      

Process Type Constraint

processTypeConstraint(typeValue)

Arguments:
  typeValue: The @type value from the frame

Returns:
  A JSON Schema type constraint object

Steps:
  1. If typeValue is a string:
     1. Return { "const": typeValue }
  2. If typeValue is an array:
     1. If the length of typeValue is 0:
        1. Return { "type": "string" }
     2. If the length of typeValue is 1:
        1. Return { "const": typeValue[0] }
     3. Otherwise:
        1. Return { "enum": typeValue }
  3. If typeValue is an empty object:
     1. Return { "type": "string" }
  4. Return { "type": "string" }
      

Process ID Constraint

processIdConstraint(idValue)

Arguments:
  idValue: The @id value from the frame

Returns:
  A JSON Schema ID constraint object

Steps:
  1. If idValue is a string:
     1. Return { "const": idValue }
  2. If idValue is an empty object:
     1. Return { "type": "string", "format": "uri" }
  3. If idValue is an object and idValue["@id"] exists:
     1. Return the result of calling processIdConstraint(idValue["@id"])
  4. Return { "type": "string", "format": "uri" }
      

Process Property

processProperty(key, value, flags, context)

Arguments:
  key: Property name
  value: Property value from frame
  flags: Framing flags
  context: Type context mapping

Returns:
  A JSON Schema property definition

Steps:
  1. Let contextType be context[key] if it exists, otherwise null
  2. If value is an empty object:
     1. Return the result of calling inferTypeFromContext(key, contextType)
  3. If value is a string, number, or boolean:
     1. Let jsonType be the result of calling inferJsonType(value)
     2. Return { "type": jsonType, "default": value }
  4. If value is an array:
     1. Return the result of calling processArrayFrame(value, flags, context)
  5. If value is an object:
     1. Return the result of calling processNestedFrame(value, flags, context)
  6. Return an empty object
      

Infer Type from Context

inferTypeFromContext(key, contextType)

Arguments:
  key: Property name
  contextType: Type from context or null

Returns:
  A JSON Schema type definition

Steps:
  1. If contextType is null:
     1. Return { "type": "string" }
  2. Let mapping be the type mapping for contextType:
     - "@id" → { "type": "string", "format": "uri" }
     - "http://www.w3.org/2001/XMLSchema#string" → { "type": "string" }
     - "http://www.w3.org/2001/XMLSchema#integer" → { "type": "integer" }
     - "http://www.w3.org/2001/XMLSchema#int" → { "type": "integer" }
     - "http://www.w3.org/2001/XMLSchema#long" → { "type": "integer" }
     - "http://www.w3.org/2001/XMLSchema#boolean" → { "type": "boolean" }
     - "http://www.w3.org/2001/XMLSchema#double" → { "type": "number" }
     - "http://www.w3.org/2001/XMLSchema#float" → { "type": "number" }
     - "http://www.w3.org/2001/XMLSchema#decimal" → { "type": "number" }
     - "http://www.w3.org/2001/XMLSchema#dateTime" → { "type": "string", "format": "date-time" }
     - "http://www.w3.org/2001/XMLSchema#date" → { "type": "string", "format": "date" }
     - "http://www.w3.org/2001/XMLSchema#time" → { "type": "string", "format": "time" }
  3. If mapping exists for contextType, return mapping
  4. Return { "type": "string" }
      

Process Array Frame

processArrayFrame(arrayValue, flags, context)

Arguments:
  arrayValue: Array from frame
  flags: Framing flags
  context: Type context mapping

Returns:
  A JSON Schema array definition

Steps:
  1. If the length of arrayValue is 0:
     1. Return { "type": "array", "items": {} }
  2. Let itemFrame be arrayValue[0]
  3. Let itemSchema be determined as follows:
     1. If itemFrame is an object:
        1. Call processNestedFrame(itemFrame, flags, context)
     2. Otherwise:
        1. Let jsonType be the result of calling inferJsonType(itemFrame)
        2. Use { "type": jsonType }
  4. Return { "type": "array", "items": itemSchema }
      

Process Nested Frame

processNestedFrame(frameObj, flags, context)

Arguments:
  frameObj: Nested frame object
  flags: Parent framing flags
  context: Type context mapping

Returns:
  A JSON Schema nested object definition

Steps:
  1. Let nestedFlags be a new object with the following properties:
     - "embed": frameObj["@embed"] if present, otherwise flags["embed"]
     - "explicit": frameObj["@explicit"] if present, otherwise flags["explicit"]
     - "requireAll": frameObj["@requireAll"] if present, otherwise flags["requireAll"]
     - "omitDefault": frameObj["@omitDefault"] if present, otherwise flags["omitDefault"]
  2. Let embedValue be nestedFlags["embed"]
  3. If embedValue is false or "@never":
     1. Return {
          "oneOf": [
            { "type": "string", "format": "uri" },
            {
              "type": "object",
              "properties": {
                "@id": { "type": "string", "format": "uri" }
              },
              "required": ["@id"],
              "additionalProperties": false
            }
          ]
        }
  4. Let nestedSchema be { "type": "object" }
  5. Call processFrameObject(frameObj, nestedSchema, nestedFlags, context)
  6. Return nestedSchema
      

Determine Required Status

shouldBeRequired(key, value, flags)

Arguments:
  key: Property name
  value: Property value from frame
  flags: Framing flags

Returns:
  true if the property should be required, false otherwise

Steps:
  1. If flags["requireAll"] is true:
     1. Return true
  2. If flags["omitDefault"] is true:
     1. Return false
  3. If value is an empty object:
     1. Return true
  4. If value is an object or array:
     1. Return true
  5. Return false
      

Helper Functions

inferJsonType(value)

Arguments:
  value: A JSON value

Returns:
  A string representing the JSON type

Steps:
  1. If value is a boolean, return "boolean"
  2. If value is an integer, return "integer"
  3. If value is a number, return "number"
  4. If value is a string, return "string"
  5. If value is an array, return "array"
  6. If value is an object, return "object"
  7. If value is null, return "null"
  8. Return "string"
      

Examples

Basic Example

Given the following frame:

{
  "@context": {
    "name": "http://schema.org/name",
    "age": {
      "@id": "http://schema.org/age",
      "@type": "http://www.w3.org/2001/XMLSchema#integer"
    }
  },
  "@type": "Person",
  "name": {},
  "age": {}
}
      

The algorithm produces the following schema (default behavior with graphOnly=false):

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "@context": {},
    "@graph": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "@type": { "const": "Person" },
          "name": { "type": "string" },
          "age": { "type": "integer" }
        },
        "required": ["@type", "name", "age"],
        "additionalProperties": true
      }
    }
  },
  "required": ["@context", "@graph"],
  "additionalProperties": true
}
      

Nested Objects with @explicit Flag

Given the following frame with nested objects and the @explicit flag:

{
  "@type": "Person",
  "@explicit": true,
  "name": {},
  "address": {
    "@type": "PostalAddress",
    "streetAddress": {},
    "addressLocality": {}
  }
}
      

The algorithm produces (default behavior with graphOnly=false):

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "@context": {},
    "@graph": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "@type": { "const": "Person" },
          "name": { "type": "string" },
          "address": {
            "type": "object",
            "properties": {
              "@type": { "const": "PostalAddress" },
              "streetAddress": { "type": "string" },
              "addressLocality": { "type": "string" }
            },
            "required": ["@type", "streetAddress", "addressLocality"],
            "additionalProperties": true
          }
        },
        "required": ["@type", "name", "address"],
        "additionalProperties": false
      }
    }
  },
  "required": ["@context", "@graph"],
  "additionalProperties": true
}
      

Non-Embedded References

When @embed is set to false:

{
  "@type": "Article",
  "title": {},
  "author": {
    "@embed": false,
    "@type": "Person"
  }
}
      

The algorithm produces a schema that accepts either a URI string or an object with just an @id (default behavior with graphOnly=false):

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "@context": {},
    "@graph": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "@type": { "const": "Article" },
          "title": { "type": "string" },
          "author": {
            "oneOf": [
              { "type": "string", "format": "uri" },
              {
                "type": "object",
                "properties": {
                  "@id": { "type": "string", "format": "uri" }
                },
                "required": ["@id"],
                "additionalProperties": false
              }
            ]
          }
        },
        "required": ["@type", "title", "author"],
        "additionalProperties": true
      }
    }
  },
  "required": ["@context", "@graph"],
  "additionalProperties": true
}
      

Array Frames

Frame with array notation:

{
  "@type": "Person",
  "name": {},
  "knows": [{
    "@type": "Person",
    "name": {}
  }]
}
      

Generates an array schema (default behavior with graphOnly=false):

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "@context": {},
    "@graph": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "@type": { "const": "Person" },
          "name": { "type": "string" },
          "knows": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "@type": { "const": "Person" },
                "name": { "type": "string" }
              },
              "required": ["@type", "name"],
              "additionalProperties": true
            }
          }
        },
        "required": ["@type", "name", "knows"],
        "additionalProperties": true
      }
    }
  },
  "required": ["@context", "@graph"],
  "additionalProperties": true
}
      

Value Objects with Language Tags

Value objects allow properties to have both simple string values and structured objects with metadata:

{
  "@context": {
    "@vocab": "http://schema.org/"
  },
  "@type": "Article",
  "headline": {
    "@value": {},
    "@language": "en"
  }
}
      

The algorithm produces (with graphOnly=true):

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "@type": { "const": "Article" },
    "headline": {
      "oneOf": [
        { "type": "string" },
        {
          "type": "object",
          "properties": {
            "@value": {},
            "@language": { "const": "en" }
          },
          "required": ["@value", "@language"],
          "additionalProperties": false
        }
      ]
    }
  },
  "required": ["@type", "headline"],
  "additionalProperties": true
}
      

This schema accepts both:

Language Maps

Language maps allow properties to specify values in multiple languages using @container: "@language":

{
  "@context": {
    "@vocab": "http://schema.org/",
    "description": {
      "@id": "http://schema.org/description",
      "@container": "@language"
    }
  },
  "@type": "Product",
  "name": {},
  "description": {}
}
      

The algorithm produces (with graphOnly=true):

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "@type": { "const": "Product" },
    "name": { "type": "string" },
    "description": {
      "oneOf": [
        { "type": "string" },
        {
          "type": "object",
          "patternProperties": {
            "^[a-z]{2,3}(-[A-Z][a-z]{3})?(-[A-Z]{2}|-[0-9]{3})?(-[a-z0-9]+)*$": {
              "type": "string"
            }
          },
          "additionalProperties": false
        }
      ]
    }
  },
  "required": ["@type", "name", "description"],
  "additionalProperties": true
}
      

This schema accepts both:

The BCP 47 regex pattern supports language codes like: en, en-US, es-419, zh-Hans-CN.

Set and Index Containers

Different container types produce different schema structures:

{
  "@context": {
    "@vocab": "http://schema.org/",
    "keywords": {
      "@id": "http://schema.org/keywords",
      "@container": "@set"
    },
    "metadata": {
      "@id": "http://schema.org/metadata",
      "@container": "@index"
    }
  },
  "@type": "BlogPost",
  "keywords": {},
  "metadata": {}
}
      

The algorithm produces (with graphOnly=true):

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "@type": { "const": "BlogPost" },
    "keywords": {
      "type": "array",
      "uniqueItems": true
    },
    "metadata": {
      "type": "object",
      "additionalProperties": { "type": "string" }
    }
  },
  "required": ["@type", "keywords", "metadata"],
  "additionalProperties": true
}
      

This schema validates:

Mapping Reference

The following table summarizes the key mappings between JSON-LD Framing concepts and JSON Schema constructs:

Frame Concept JSON Schema Construct Notes
Frame Object type: "object" Basic structural mapping
@type (single) const Exact type match
@type (multiple) enum One of several types
@id (required) type: "string", format: "uri" URI validation
@embed: true Nested object schema Default behavior
@embed: false oneOf (URI or @id object) Reference-only
@explicit: true additionalProperties: false Strict validation
@explicit: false additionalProperties: true Default, allows extras
@requireAll: true All properties in required All must be present
@omitDefault: true Properties not in required All optional
Array notation [{...}] type: "array", items: {...} Array of objects
Empty object {} Required property with inferred type Property must be present
Context type coercion JSON Schema type inference XSD types → JSON types
@value with @language oneOf (string or value object) Language-tagged literals
@value with @type oneOf (string or typed value object) Typed literals (e.g., dates)
@container: "@language" oneOf (string or language map) Language map with BCP 47 pattern
@container: "@index" type: "object" with additionalProperties Index map with arbitrary keys
@container: "@set" type: "array", uniqueItems: true Set of unique items
@container: "@list" type: "array" Ordered list

Implementation Notes

Context Processing

Implementations SHOULD use existing JSON-LD libraries for context processing to ensure correct handling of context expansion, prefix resolution, and type coercion rules.

Error Handling

Implementations SHOULD provide clear error messages when:

Circular References

Implementations SHOULD detect and handle circular references in nested frames to avoid infinite recursion. This can be accomplished by tracking the frame processing stack.

Schema Validation

Implementations MAY validate generated schemas against the JSON Schema meta-schema to ensure correctness before returning them.

Limitations

The following JSON-LD Framing features have limited or no mapping to JSON Schema:

Security Considerations

Context Processing

When processing external contexts referenced by URL, implementations SHOULD implement appropriate security measures including:

Recursion Limits

Implementations SHOULD enforce limits on frame nesting depth to prevent stack overflow attacks from maliciously crafted frames.

Privacy Considerations

This specification defines an algorithm for converting frame structures to schemas. The algorithm itself does not introduce privacy concerns beyond those inherent in JSON-LD and JSON Schema usage. Implementations should follow the privacy considerations outlined in the JSON-LD 1.1 specification.