Skip to content

Instantly share code, notes, and snippets.

@jadsongmatos
Created June 5, 2025 03:16
Show Gist options
  • Save jadsongmatos/a3b84b41f37667888016cc86334f7aa4 to your computer and use it in GitHub Desktop.
Save jadsongmatos/a3b84b41f37667888016cc86334f7aa4 to your computer and use it in GitHub Desktop.
import copy
import json
from pathlib import Path
from typing import Any, Dict, Optional
def openapi_to_json_schema(
openapi_path: str,
output_path: str,
root_schema_name: Optional[str] = None
) -> None:
"""
Converte os componentes/schemas de um OpenAPI 3.x para um JSON Schema
2020-12 pronto para uso no react-jsonschema-form.
"""
# 1) Carrega OpenAPI
raw = Path(openapi_path).read_text(encoding="utf-8")
openapi: Dict[str, Any] = json.loads(raw)
schemas: Dict[str, Any] = (
openapi.get("components", {})
.get("schemas", {})
)
if not schemas:
raise ValueError("O arquivo OpenAPI não contém components.schemas")
# 2) Funções auxiliares
def normalize_ref(node: Dict[str, Any]) -> None:
"""
Se houver "$ref": "#/components/schemas/X", converte → "#/$defs/X".
Deve ser chamada antes de qualquer outro processamento desse node.
"""
ref = node.get("$ref")
if isinstance(ref, str) and ref.startswith("#/components/schemas/"):
target = ref.split("/")[-1] # pega "X" em "#/components/schemas/X"
node["$ref"] = f"#/$defs/{target}"
def ensure_type(schema: Dict[str, Any]) -> None:
"""
Garante que exista a chave 'type' em schema.
Se não houver type, tenta inferir:
- se tiver "properties" ou "additionalProperties", assume "object"
- se tiver "items", assume "array"
- caso contrário, coloca "object" como fallback seguro
"""
if "type" in schema:
return
if "properties" in schema or "additionalProperties" in schema:
schema["type"] = "object"
elif "items" in schema:
schema["type"] = "array"
else:
schema["type"] = "object"
def transform(node: Any) -> Any:
"""
Aplica recursivamente as transformações a um sub-schema:
1) Se for lista, processa cada item
2) Se for primitivo, retorna direto
3) Se for dict:
a) normaliza "$ref"
b) converte "anyOf" → "oneOf"
c) percorre "properties", "patternProperties", "definitions", "$defs"
d) percorre "items"
e) percorre "oneOf"
f) garante "type"
"""
if isinstance(node, list):
return [transform(item) for item in node]
if not isinstance(node, dict):
return node
# 3) Normaliza ref antes de criar nodes aninhados
normalize_ref(node)
# 4) Converte anyOf → oneOf
if "anyOf" in node:
node["oneOf"] = node.pop("anyOf")
# 5) Percorre objetos aninhados
for key in ("properties", "patternProperties", "definitions", "$defs"):
if key in node and isinstance(node[key], dict):
node[key] = {k: transform(v) for k, v in node[key].items()}
# 6) Percorre items se existir
if "items" in node:
node["items"] = transform(node["items"])
# 7) Percorre oneOf
if "oneOf" in node and isinstance(node["oneOf"], list):
node["oneOf"] = [transform(sub) for sub in node["oneOf"]]
# 8) Garante type
ensure_type(node)
return node
# 3) Copia e converte cada schema em components.schemas
converted: Dict[str, Any] = {}
for name, schema in schemas.items():
# usa deepcopy para não modificar direto o original
converted[name] = transform(copy.deepcopy(schema))
# 4) Escolhe o schema raiz
if root_schema_name is None:
# primeiramente, tenta tirar o "firstKey" de converted
root_schema_name = next(iter(converted))
if root_schema_name not in converted:
raise KeyError(f"Schema '{root_schema_name}' não encontrado em components.schemas")
# 5) Extrai o schema raiz e coloca os demais em "$defs"
root_schema = converted.pop(root_schema_name)
root_schema.setdefault("$schema", "https://json-schema.org/draft/2020-12/schema")
root_schema.setdefault("$id", f"https://example.com/schemas/{root_schema_name}")
if converted:
# somente adiciona `$defs` se existir algum schema extra
root_schema["$defs"] = converted
# 6) Escreve em disco
Path(output_path).write_text(
json.dumps(root_schema, indent=2, ensure_ascii=False),
encoding="utf-8"
)
print(f"✅ Esquema convertido salvo em: {output_path}")
# gera JSON Schema
openapi_to_json_schema("openapi.json", "schema.json", root_schema_name=None)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment