-
Notifications
You must be signed in to change notification settings - Fork 316
Description
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:
- additionalProperties: false is ignored: Map can contain any arbitrary fields
- oneOf constraints are never checked: Invalid combinations pass validation
- Type validation is skipped: Map values can be any object structure
- 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_schemaAdditional 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
-
Create TypeSpec model with
Map<string, ComplexType>where ComplexType has:additionalProperties: falseoneOfconstraints- Other validation rules
-
Compile with
@typespec/json-schemaemitter:tsp compile . --emit @typespec/json-schema -
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)
-
Expected: ValidationError raised
-
Actual: Validation passes ✓ (incorrectly)
Checklist
- Follow our Code of Conduct
- Check that there isn't already an issue that request the same bug to avoid creating a duplicate.
- Check that this is a concrete bug. For Q&A open a GitHub Discussion.
- The provided reproduction is a minimal reproducible example of the bug.