Map Language Types to JSON Schema
Function schemas live at the intersection of your language's type system and JSON Schema. A Python list[int] must become {"type": "array", "items": {"type": "integer"}} in JSON Schema. A TypeScript Date becomes {"type": "string", "format": "date-time"}. Getting this mapping right is critical: a mismatched type causes the LLM to send the wrong data type, your validator rejects it, or worse, your function crashes at runtime. Most production errors in tool calling come from type mismatches—the model guesses what "date" means and sends "2026-01-15T10:00:00Z" when your code expected 1609459200 (Unix seconds). This article maps types from four major languages to JSON Schema equivalents.
Core Type Mappings
| Language | Python | TypeScript | Go | Rust | JSON Schema |
|---|---|---|---|---|---|
| String | str | string | string | String | {"type": "string"} |
| Integer | int | number | bigint | int, int64 | i32, i64 | {"type": "integer"} |
| Float | float | number | float64 | f64 | {"type": "number"} |
| Boolean | bool | boolean | bool | bool | {"type": "boolean"} |
| Array | list[T] | T[] | []T | Vec<T> | {"type": "array", "items": {...}} |
| Object | dict[K, V] | Record<K, V> | map[K]V | HashMap<K, V> | {"type": "object", "properties": {...}} |
| Null | None | null | nil | None | {"type": "null"} |
| Date-Time | datetime | Date | time.Time | DateTime<Utc> | {"type": "string", "format": "date-time"} |
| Enum | Enum | Union type | Custom type | enum | {"enum": [list]} |
Primitive Types
Strings
All languages map string types straightforwardly to JSON Schema string. Add constraints as needed:
Python:
# Simple string
name: str → {"type": "string", "description": "..."}
# String with length constraints
email: str → {"type": "string", "minLength": 5, "maxLength": 254, "pattern": "^.+@.+\\..+$"}
# Literal string (only one value allowed)
version: Literal["v2"] → {"const": "v2"}
TypeScript:
// Simple string
name: string → {"type": "string"}
// String literal (only one value)
role: "admin" | "user" → {"enum": ["admin", "user"]}
// Template literal (pattern)
uuid: `${string}-${string}` → {"type": "string", "pattern": "^.+-.*"}
Numbers and Integers
JSON Schema distinguishes integer and number. Use integer for whole numbers and number for floats. Python's int maps to integer, but Python's float maps to number:
Python:
count: int → {"type": "integer"}
price: float → {"type": "number"}
percentage: int → {"type": "integer", "minimum": 0, "maximum": 100}
TypeScript (careful: no separate integer type):
count: number → {"type": "integer"} // At runtime, you must validate it's whole
price: number → {"type": "number"}
// Use a branded type for clarity:
type WholeNumber = number & { readonly __wholeNumber: unique symbol }
Go:
count := 42 // int → {"type": "integer"}
price := 19.99 // float64 → {"type": "number"}
Rust:
count: i32 → {"type": "integer"}
price: f64 → {"type": "number"}
Booleans
All languages have a direct boolean type:
# Python
is_active: bool → {"type": "boolean"}
# TypeScript
is_active: boolean → {"type": "boolean"}
# Go
isActive := true // bool → {"type": "boolean"}
# Rust
is_active: bool → {"type": "boolean"}
Collections: Arrays and Objects
Arrays
A list or array becomes {"type": "array", "items": <schema of element>}. Always declare what type of elements the array holds:
Python:
tags: list[str] → {
"type": "array",
"items": {"type": "string"},
"description": "List of tags"
}
scores: list[int] → {
"type": "array",
"items": {"type": "integer", "minimum": 0, "maximum": 100}
}
items: list[dict[str, any]] → {
"type": "array",
"items": {
"type": "object",
"properties": {...}
}
}
TypeScript:
tags: string[] → {
"type": "array",
"items": {"type": "string"}
}
scores: number[] → {
"type": "array",
"items": {"type": "integer"}
}
Go:
tags := []string{...} → {
"type": "array",
"items": {"type": "string"}
}
scores := []int{...} → {
"type": "array",
"items": {"type": "integer"}
}
Objects and Structs
Classes, structs, and dataclasses become {"type": "object"} with a properties map:
Python (using dataclass):
@dataclass
class User:
name: str
age: int
email: str
# Becomes:
{
"type": "object",
"properties": {
"name": {"type": "string"},
"age": {"type": "integer", "minimum": 0},
"email": {"type": "string", "pattern": "^.+@.+$"}
},
"required": ["name", "age", "email"]
}
TypeScript:
interface User {
name: string;
age: number;
email: string;
}
// Becomes:
{
"type": "object",
"properties": {
"name": {"type": "string"},
"age": {"type": "integer"},
"email": {"type": "string"}
},
"required": ["name", "age", "email"]
}
Optional and Nullable Types
When a field is optional or can be null, it should not appear in the required array. If a field can be either a value or null, some frameworks use type: ["string", "null"], but support varies—it's safer to omit the field from required and document the behavior:
Python:
# Optional (field can be omitted)
age: int | None = None → Not in "required" array
# Required, but nullable (must be present, can be null)
status: str | None → In "required" array, schema says "can be null"
TypeScript:
// Optional
age?: number → Not in "required"
// Nullable
status: string | null → In "required", but schema allows null
Dates and Times
JSON doesn't have a native date type. By convention, dates are strings with a format hint:
- ISO 8601 datetime:
{"type": "string", "format": "date-time"} - Date only:
{"type": "string", "format": "date"} - Time only:
{"type": "string", "format": "time"} - Unix timestamp:
{"type": "integer", "description": "Unix seconds since 1970-01-01 00:00:00 UTC"}
Python:
from datetime import datetime
created_at: datetime → {
"type": "string",
"format": "date-time",
"description": "ISO 8601 timestamp"
}
# Or Unix timestamp:
created_at: int → {
"type": "integer",
"description": "Unix timestamp in seconds"
}
TypeScript:
createdAt: Date → {
"type": "string",
"format": "date-time"
}
// At runtime, parse the string to a Date:
const date = new Date(jsonData.createdAt);
Enums and Union Types
Enums become {"enum": [list of allowed values]}. Union types with distinct tags require special handling (covered in Article 5):
Python:
class Status(Enum):
PENDING = "pending"
ACTIVE = "active"
ARCHIVED = "archived"
# Becomes:
{"enum": ["pending", "active", "archived"]}
TypeScript:
type Status = "pending" | "active" | "archived";
// Becomes:
{"enum": ["pending", "active", "archived"]}
Go:
// Go doesn't have native enums, so define constants and validate:
const (
StatusPending = "pending"
StatusActive = "active"
)
// Schema:
{"enum": ["pending", "active"]}
Complex Example: Full Function Signature
Here's a Python function and its complete schema:
@dataclass
class Location:
latitude: float
longitude: float
def search_nearby(
location: Location,
radius_km: float,
limit: int = 10,
filter_type: Literal["restaurant", "shop", "service"] = "restaurant"
) -> list[dict]:
"""Search for places near a location."""
pass
# Schema:
{
"name": "search_nearby",
"description": "Search for places within a radius of a location",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "object",
"properties": {
"latitude": {"type": "number", "minimum": -90, "maximum": 90},
"longitude": {"type": "number", "minimum": -180, "maximum": 180}
},
"required": ["latitude", "longitude"]
},
"radius_km": {"type": "number", "minimum": 0.1, "maximum": 50},
"limit": {"type": "integer", "minimum": 1, "maximum": 100, "default": 10},
"filter_type": {"enum": ["restaurant", "shop", "service"], "default": "restaurant"}
},
"required": ["location", "radius_km"]
}
}
Key Takeaways
- Each language type maps to a specific JSON Schema
typeand constraints. - Primitive types (
str,int,float,bool) map directly; collections require nested schemas. - Enums become
{"enum": [...]}with a fixed list of allowed values. - Optional fields are not in the required array; nullable types should be documented.
- Dates have no native JSON type—use
"format": "date-time"or Unix timestamps.
Frequently Asked Questions
What if my language has a custom type (e.g., UserId branded type)?
Map it to its base JSON type (e.g., UserId is a string → {"type": "string"}), and add a description explaining its format (e.g., "Unique identifier for a user, e.g., user_12345").
Can JSON Schema represent a tuple (fixed-length array with different element types)?
Not clearly. JSON Schema's items applies to all elements. Some frameworks support prefixItems (newer spec), but most LLM tools don't. Represent tuples as objects instead: {"type": "object", "properties": {"first": {...}, "second": {...}}}.
Should I use format for string validation?
Yes, for standard formats like email, date-time, uri, and uuid. Most validators recognize these. Use pattern for custom formats.
What about generic types like list[T] where T is a type variable?
At schema-generation time, you must resolve T to a concrete type. If your function accepts list[T] generically, either constrain it (e.g., always list[str]) or document that the schema handles list of mixed types (generally avoid this for LLM tool calling).
How do I represent a union of different object types (e.g., Dog | Cat)?
This is complex in JSON Schema without oneOf (which most LLM tools don't support well). See Article 5 for the tagged union pattern using a discriminator field.