Free & open source — no account required
Free & open source — no account required
This technical guide provides an in-depth analysis of the json to openapi 3 engine, best practices for implementation, and data security standards.
OpenAPI 3.1 (released 2021) is fully aligned with JSON Schema 2020-12, which changes how several important patterns work compared to 3.0: nullable is replaced with type: ['string', 'null'], exclusiveMinimum becomes a number rather than a boolean, and the $schema keyword is now supported for inline schema meta-references. Beyond the spec upgrade, production OpenAPI work involves: using discriminator for polymorphic responses, generating webhook definitions alongside REST endpoints, wiring OAuth2/OIDC security schemes correctly, and running Spectral to catch spec quality issues before they affect generated clients.
# Input JSON samples (request and response)
# POST /orders body:
{
"customer_id": "cust_001",
"items": [{ "sku": "SKU-A", "qty": 2 }],
"shipping_address": {
"street": "123 Main St", "city": "Springfield", "country": "US"
},
"coupon_code": null
}
# Response:
{
"id": "ord_abc123",
"status": "PENDING",
"total_cents": 4998,
"created_at": "2024-03-15T10:00:00Z"
}
# Generated OpenAPI 3.1 spec
openapi: 3.1.0
info:
title: Order Service API
version: 1.0.0
description: REST API for order management
components:
schemas:
OrderItem:
type: object
required: [sku, qty]
properties:
sku: { type: string, example: SKU-A }
qty: { type: integer, minimum: 1 }
price_cents:
type: integer
readOnly: true
description: Calculated server-side
Address:
type: object
required: [street, city, country]
properties:
street: { type: string }
city: { type: string }
country: { type: string, minLength: 2, maxLength: 2, example: US }
CreateOrderRequest:
type: object
required: [customer_id, items, shipping_address]
properties:
customer_id:
type: string
pattern: '^cust_[a-z0-9]+$'
items:
type: array
items: { $ref: '#/components/schemas/OrderItem' }
minItems: 1
shipping_address:
$ref: '#/components/schemas/Address'
coupon_code:
type: ['string', 'null'] # OpenAPI 3.1: nullable, not nullable: true
example: null
Order:
type: object
required: [id, status, total_cents, created_at]
properties:
id: { type: string, readOnly: true }
status:
type: string
enum: [PENDING, CONFIRMED, SHIPPED, DELIVERED, CANCELLED]
total_cents: { type: integer, readOnly: true }
created_at: { type: string, format: date-time, readOnly: true }
Error:
type: object
required: [code, message]
properties:
code: { type: string }
message: { type: string }
details: { type: array, items: { type: string } }
paths:
/orders:
post:
operationId: createOrder
summary: Create a new order
tags: [Orders]
security: [{ bearerAuth: [] }]
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/CreateOrderRequest' }
examples:
standard: { $ref: '#/components/examples/StandardOrder' }
responses:
'201':
description: Order created
headers:
Location: { schema: { type: string }, description: URL of the created order }
content:
application/json:
schema: { $ref: '#/components/schemas/Order' }
'400':
description: Validation error
content:
application/json:
schema: { $ref: '#/components/schemas/Error' }
'401':
description: Not authenticated
# OneOf with discriminator — API returns different event types on the same endpoint
components:
schemas:
EventBase:
type: object
required: [type, timestamp]
properties:
type: { type: string }
timestamp: { type: string, format: date-time }
OrderEvent:
allOf:
- $ref: '#/components/schemas/EventBase'
- type: object
required: [order_id, status]
properties:
order_id: { type: string }
status: { type: string, enum: [PENDING, CONFIRMED, SHIPPED] }
PaymentEvent:
allOf:
- $ref: '#/components/schemas/EventBase'
- type: object
required: [payment_id, amount_cents]
properties:
payment_id: { type: string }
amount_cents: { type: integer }
WebhookEvent:
oneOf:
- $ref: '#/components/schemas/OrderEvent'
- $ref: '#/components/schemas/PaymentEvent'
discriminator:
propertyName: type
mapping:
order_created: '#/components/schemas/OrderEvent'
order_shipped: '#/components/schemas/OrderEvent'
payment_success: '#/components/schemas/PaymentEvent'
payment_failed: '#/components/schemas/PaymentEvent'
# Security schemes defined in components — referenced by operations
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
oauth2:
type: oauth2
flows:
authorizationCode:
authorizationUrl: https://auth.example.com/oauth/authorize
tokenUrl: https://auth.example.com/oauth/token
scopes:
orders:read: Read orders
orders:write: Create and modify orders
admin: Full access
apiKey:
type: apiKey
in: header
name: X-API-Key
# Apply security globally (can be overridden per-operation)
security:
- bearerAuth: []
paths:
/admin/orders:
get:
security:
- oauth2: [admin] # overrides global security for this endpoint
# Webhooks describe events the server sends to clients — new in OpenAPI 3.1
webhooks:
orderStatusChanged:
post:
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/WebhookEvent'
responses:
'200':
description: Webhook received (client must return 2xx to acknowledge)
# Describe the webhook registration endpoint separately as a path
paths:
/webhooks:
post:
summary: Register a webhook endpoint
operationId: registerWebhook
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [url, events]
properties:
url: { type: string, format: uri }
secret: { type: string, description: HMAC signing secret }
events:
type: array
items: { type: string, enum: [order_created, order_shipped, payment_success] }
// Spectral lints OpenAPI specs against quality rules
// npm install -D @stoplight/spectral-cli
// .spectral.yml — quality ruleset
extends: ['spectral:oas']
rules:
# Require all operations to have an operationId
operation-operationId:
severity: error
# Require all schemas to have examples
oas3-schema-examples:
given: "$.components.schemas.*"
severity: warn
then:
field: example
function: truthy
# Require all 2xx responses to have a content schema
operation-success-response:
severity: warn
# Disallow inline schemas (require $ref for reuse)
no-inline-schemas:
given: "$.paths.*.*.responses.*.content.*.schema"
severity: warn
then:
function: schema
functionOptions:
schema: { required: ['$ref'] }
// Run in CI:
// npx spectral lint openapi.yaml --ruleset .spectral.yml
// Or use Spectral JS API:
import { Spectral } from '@stoplight/spectral-core';
import { oas } from '@stoplight/spectral-formats';
const spectral = new Spectral();
spectral.setRuleset({ extends: [oas], rules: {} });
const issues = await spectral.run(specDocument);
const errors = issues.filter(i => i.severity === 0); // 0 = error
readOnly and writeOnly on schema properties: Fields like id, created_at, and total_cents are server-generated and should be readOnly: true. Password fields should be writeOnly: true. Code generators use these hints to omit read-only fields from request bodies and write-only fields from response types.operationId to every endpoint: Code generators (openapi-generator, oazapfts, hey-api) use operationId as the function name for generated API clients. Without it, generators create awkward names from the path and method. Use snake_case or camelCase consistently: createOrder, getOrderById, listOrders.components and reuse them: Every 4xx and 5xx response should reference a shared error schema. Consumers need to know the error format to handle it. Defining it once in components/schemas/Error ensures consistency across all endpoints.nullable: true to 3.1 type: ['string', 'null']: OpenAPI 3.1 uses JSON Schema 2020-12 which doesn't have the nullable keyword. Use the type array form. Many tools still support nullable for backward compatibility, but write new specs using the correct 3.1 syntax.Q: What's the difference between allOf, anyOf, and oneOf?
A: allOf means the data must satisfy ALL listed schemas (used for inheritance/extension). anyOf means the data must satisfy AT LEAST ONE schema. oneOf means the data must satisfy EXACTLY ONE schema (used for mutually exclusive variants). For polymorphism with a discriminator property, use oneOf. For adding properties to a base schema, use allOf.
Q: How do I handle pagination in an OpenAPI spec?
A: OpenAPI doesn't have a built-in pagination concept — define it as a pattern. Use a reusable Pagination component schema with page, page_size, total, and has_more. For cursor-based pagination, use next_cursor and prev_cursor. Add these as query parameters on list endpoints and include the pagination schema in list response wrappers.
Q: Should I write the OpenAPI spec by hand or generate it from code?
A: Design-first (spec by hand → generate server stubs) produces better API design but requires discipline to keep the spec in sync with implementation. Code-first (implement → generate spec) is easier to maintain but produces worse API design (exposes internal naming, missing descriptions). Use code-first with heavy Spectral linting to enforce quality, or design-first with contract testing to verify sync.
Is the processing local-only?
Absolutely. TypeMorph operates entirely within your browser's sandbox. We use Web Workers for high-performance computation without ever transmitting your JSON, SQL, or API data to a remote server.
Can I use this for enterprise projects?
Yes. The tool is designed for professional software engineers who require GDPR compliance and data privacy. It is trusted by developers at top-tier startups and financial institutions.