Skip to content

[Bug]: @typespec/json-schema Map<K, V> emits incomplete JSON Schema (missing additionalProperties) #8891

@DaveB93

Description

@DaveB93

Describe the bug

TypeSpec Bug Report: Map<K, V> emits incomplete JSON Schema

Environment

  • TypeSpec Version: 0.64.0
  • Emitter: @typespec/json-schema 0.64.0
  • OS: macOS (Darwin 25.2.0)
  • Node.js: (check with node --version)

Description

When emitting JSON Schema from TypeSpec models that use Map<string, T>, the generated schema is missing the additionalProperties constraint that properly validates map values against type T. This causes JSON Schema validation to skip all constraints defined on the value type, including additionalProperties: false, oneOf, and other validation rules.

TypeSpec Source

import "@typespec/json-schema";

namespace MyApp;

/**
 * Configuration for a server instance.
 */
@jsonSchema
@extension("additionalProperties", false)
model ServerConfig {
  endpoint?: Endpoint;
  fallback_endpoint?: Endpoint;
}

/**
 * Application configuration with server instances.
 */
model AppConfig {
  /** Map of server names to their configurations */
  servers?: Map<string, ServerConfig>;
}

/**
 * Endpoint configuration with oneOf constraint.
 * Must specify EITHER url OR (host + port).
 */
model Endpoint {
  url?: string;
  host?: string;
  port?: int32;
  timeout?: int32;
}

With oneOf constraint applied to Endpoint:

# Must have either 'url' OR both 'host' and 'port'
constraints:
  - path: "Endpoint"
    type: one_of
    options:
      - required: ["url"]
      - required: ["host", "port"]

Expected JSON Schema Output

{
  "$defs": {
    "MapStringServerConfig": {
      "type": "object",
      "additionalProperties": {
        "$ref": "#/$defs/ServerConfig"
      },
      "description": "A type representing a map..."
    },
    "ServerConfig": {
      "type": "object",
      "properties": {
        "endpoint": { "$ref": "#/$defs/Endpoint" },
        "fallback_endpoint": { "$ref": "#/$defs/Endpoint" }
      },
      "additionalProperties": false
    },
    "Endpoint": {
      "type": "object",
      "properties": {
        "url": { "type": "string" },
        "host": { "type": "string" },
        "port": { "type": "integer" },
        "timeout": { "type": "integer" }
      },
      "oneOf": [
        { "required": ["url"] },
        { "required": ["host", "port"] }
      ]
    }
  }
}

Actual JSON Schema Output

Generated with tsp compile . --emit @typespec/json-schema:

{
  "$defs": {
    "MapStringServerConfig": {
      "type": "object",
      "properties": {},
      "description": "A type representing a map..."
      // ❌ MISSING: additionalProperties constraint!
    },
    "ServerConfig": {
      "type": "object",
      "properties": {
        "endpoint": { "$ref": "#/$defs/Endpoint" },
        "fallback_endpoint": { "$ref": "#/$defs/Endpoint" }
      },
      "additionalProperties": false
    },
    "Endpoint": {
      "type": "object",
      "properties": {
        "url": { "type": "string" },
        "host": { "type": "string" },
        "port": { "type": "integer" },
        "timeout": { "type": "integer" }
      },
      "oneOf": [
        { "required": ["url"] },
        { "required": ["host", "port"] }
      ]
    }
  }
}

Impact

Without additionalProperties, JSON Schema validation does NOT enforce constraints on map values:

  1. additionalProperties: false is ignored: Map can contain any arbitrary fields
  2. oneOf constraints are never checked: Invalid combinations pass validation
  3. Type validation is skipped: Map values can be any object structure
  4. Runtime validation failures: Invalid configurations accepted during schema validation but fail at runtime

Example: Invalid Data Passes Validation

This invalid JSON incorrectly passes validation:

{
  "servers": {
    "primary": {
      "endpoint": {
        "url": "https://api.example.com",
        "host": "api.example.com",
        "port": 443
      },
      "database_config": {
        "this_field_should_not_be_allowed": true
      }
    }
  }
}

Expected: Validation fails with:

  • "Additional properties are not allowed ('database_config' was unexpected)"
  • "Must match exactly one of the required patterns (oneOf violation)" (endpoint has both url AND host+port)

Actual: Validation passes ✓ (incorrectly)

Root Cause

The @typespec/json-schema emitter generates a type definition for Map<string, T> but does not translate the map's value type constraint into the additionalProperties field required by JSON Schema.

Workaround

Post-process emitted JSON Schema to inject missing additionalProperties:

def fix_map_types(bundled_schema: dict) -> dict:
    """Fix Map<string, T> types by injecting additionalProperties constraint."""
    for def_name, def_schema in bundled_schema.get("$defs", {}).items():
        # Pattern: MapString{TypeName}
        if def_name.startswith("MapString") and isinstance(def_schema, dict):
            type_name = def_name.replace("MapString", "")
            if type_name and type_name in bundled_schema["$defs"]:
                def_schema["additionalProperties"] = {"$ref": f"#/$defs/{type_name}"}

    return bundled_schema

Additional Context

  • Using TypeSpec for dual emission: JSON Schema (for configuration validation) and Protobuf (for APIs)
  • Map types are critical for dictionary-like structures with constrained values
  • Without proper validation, runtime errors occur instead of catching issues at schema validation time
  • Affects any TypeSpec project using Maps with complex value types that have validation constraints

Reproduction

  1. Create TypeSpec model with Map<string, ComplexType> where ComplexType has:

    • additionalProperties: false
    • oneOf constraints
    • Other validation rules
  2. Compile with @typespec/json-schema emitter:

    tsp compile . --emit @typespec/json-schema
  3. Validate JSON against schema:

    import json
    import jsonschema
    
    schema = json.load(open("schema.json"))
    invalid_data = {
        "servers": {
            "primary": {
                "endpoint": {"url": "https://api.example.com", "host": "api.example.com"},
                "database_config": {"should_not_exist": True}
            }
        }
    }
    
    # This should raise ValidationError but doesn't
    jsonschema.validate(invalid_data, schema)
  4. Expected: ValidationError raised

  5. Actual: Validation passes ✓ (incorrectly)

Checklist

Metadata

Metadata

Assignees

Labels

needs-areaneeds-infoMark an issue that needs reply from the author or it will be closed automatically

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions