JSON Schema Integration

Ack schemas can be automatically converted into JSON Schema objects, allowing you to generate API documentation directly from your validation schemas.

Generating JSON Schemas

Use the toJsonSchema() extension method available on any AckSchema instance (requires importing package:ack/json_schema.dart).

import 'package:ack/ack.dart';
import 'package:ack/json_schema.dart'; // Required for toJsonSchema() extension
import 'dart:convert';

// Example User Schema
final userSchema = Ack.object({
  'id': Ack.integer().positive().withDescription('Unique user identifier'),
  'name': Ack.string().minLength(2).maxLength(50).withDescription('User\'s full name'),
  'email': Ack.string().email().withDescription('User\'s email address'),
  'role': Ack.enumString(['admin', 'user', 'guest']).withDefault('user'),
  'isActive': Ack.boolean().withDefault(true),
  'tags': Ack.list(Ack.string()).unique().withDescription('List of user tags').nullable(),
  'age': Ack.integer().min(0).max(120).nullable().withDescription('User\'s age'),
}).withDescription('Represents a user in the system');

void main() {
  // Convert the AckSchema to a JSON Schema Object Map
  final jsonSchemaMap = userSchema.toJsonSchema();

  // Pretty print the JSON representation of the JSON Schema
  final jsonEncoder = JsonEncoder.withIndent('  ');
  print(jsonEncoder.convert(jsonSchemaMap));
}

Output JSON (JSON Schema Object):

{
  "type": "object",
  "description": "Represents a user in the system",
  "properties": {
    "id": {
      "type": "integer",
      "format": "int64",
      "description": "Unique user identifier",
      "minimum": 0,
      "exclusiveMinimum": true
    },
    "name": {
      "type": "string",
      "description": "User\'s full name",
      "minLength": 2,
      "maxLength": 50
    },
    "email": {
      "type": "string",
      "format": "email",
      "description": "User\'s email address"
    },
    "role": {
      "type": "string",
      "enum": [
        "admin",
        "user",
        "guest"
      ],
      "default": "user"
    },
    "isActive": {
      "type": "boolean",
      "default": true
    },
    "tags": {
      "type": "array",
      "description": "List of user tags",
      "items": {
        "type": "string"
      },
      "uniqueItems": true,
      "nullable": true
    },
    "age": {
      "type": "integer",
      "format": "int64",
      "description": "User\'s age",
      "minimum": 0,
      "maximum": 120,
      "nullable": true
    }
  },
  "required": [
    "id",
    "name",
    "email"
  ]
}

How Constraints Map to JSON Schema

Ack attempts to map its built-in constraints to corresponding JSON Schema keywords:

Ack ConstraintJSON Schema KeywordNotes
minLength(n)minLength: nString
maxLength(n)maxLength: nString
matches(p)pattern: pString
email()format: emailString
date()format: dateString
datetime()format: date-timeString
time()format: timeString
uri()format: uriString
uuid()format: uuidString
ipv4()format: ipv4String
ipv6()format: ipv6String
hostname()format: hostnameString (not currently implemented)
enumString([...])enum: [...]String
min(n)minimum: nNumber (Int/Double)
max(n)maximum: nNumber (Int/Double)
min(n, exclusive:true)minimum: n, exclusiveMinimum: trueNumber (exclusive not currently supported)
max(n, exclusive:true)maximum: n, exclusiveMaximum: trueNumber (exclusive not currently supported)
multipleOf(n)multipleOf: nNumber (Int/Double)
minLength(n)minItems: nList (Array)
maxLength(n)maxItems: nList (Array)
unique()uniqueItems: trueList (Array)
nullable()nullable: trueAny Type
withDefault(v)default: vAny Type (Note: Use .withDefault() on schema)
withDescription(d)description: dAny Type (Note: Use .withDescription() on schema)
Ack.integertype: integer, format: int64Type
Ack.doubletype: number, format: doubleType
Ack.stringtype: stringType
Ack.booleantype: booleanType
Ack.list(...)type: array, items: {...}Type
Ack.object(...)type: object, properties: {...}, required: [...]Type

Limitations:

  • Custom Constraints: SchemaConstraint instances added via .constrain() are not translated to JSON Schema as there's no standard way to represent arbitrary logic.
  • additionalProperties: The translation of additionalProperties might need review depending on the exact JSON Schema behavior desired (e.g., additionalProperties: true vs additionalProperties: {} vs additionalProperties: <Schema>).

Integrating into API Documentation

You can use the generated JSON Schema map within a larger API documentation structure.

// Assume you have a function to build the full API spec
Map<String, dynamic> buildApiSpecification() {
  final userJsonSchema = userSchema.toJsonSchema();
  
  return {
    'schemas': {
      'User': userJsonSchema
    },
    'endpoints': {
      '/users': {
        'post': {
          'summary': 'Create a new user',
          'requestBody': {
            'required': true,
            'content': {
              'application/json': {
                // Reference the generated schema
                'schema': {
                  '\$ref': '#/schemas/User'
                }
              }
            }
          }
        }
      }
    }
  };
}

// Usage
final fullApiSpec = buildApiSpecification();
print(JsonEncoder.withIndent('  ').convert(fullApiSpec));

This allows you to maintain your validation logic and API documentation source in one place (your Ack schemas).

Advanced JSON Schema Features

Schema Descriptions and Metadata

Add descriptions and metadata to your schemas for better documentation:

final userSchema = Ack.object({
  'id': Ack.string().uuid().withDescription('Unique user identifier'),
  'name': Ack.string().minLength(1).withDescription('User\'s full name'),
  'email': Ack.string().email().withDescription('User\'s email address'),
  'age': Ack.integer().min(0).max(150).withDescription('User\'s age in years').optional(),
}).withDescription('Represents a user in the system');

final jsonSchema = userSchema.toJsonSchema();
// Generated schema will include description fields

Default Values in JSON Schema

Schemas with default values will include them in the generated JSON Schema:

final configSchema = Ack.object({
  'theme': Ack.enumString(['light', 'dark']).withDefault('light'),
  'notifications': Ack.boolean().withDefault(true),
  'maxItems': Ack.integer().min(1).max(100).withDefault(10),
});

final jsonSchema = configSchema.toJsonSchema();
// Will include "default" properties in the JSON Schema

Complex Schema Patterns

JSON Schema generation works with all Ack schema types:

// Union types
final mixedValueSchema = Ack.anyOf([
  Ack.string(),
  Ack.integer(),
  Ack.boolean(),
]);

// Discriminated unions
final shapeSchema = Ack.discriminated(
  discriminatorKey: 'type',
  schemas: {
    'circle': Ack.object({
      'type': Ack.literal('circle'),
      'radius': Ack.double().positive(),
    }),
    'rectangle': Ack.object({
      'type': Ack.literal('rectangle'),
      'width': Ack.double().positive(),
      'height': Ack.double().positive(),
    }),
  },
);

// Nested arrays and objects
final complexSchema = Ack.object({
  'users': Ack.list(userSchema).minLength(1),
  'metadata': Ack.object({
    'version': Ack.string(),
    'tags': Ack.list(Ack.string()).unique(),
  }).optional(),
});

// All generate valid JSON Schema
final mixedJson = mixedValueSchema.toJsonSchema();
final shapeJson = shapeSchema.toJsonSchema();
final complexJson = complexSchema.toJsonSchema();