---
title: JSON Serialization
---

Ack schemas validate JSON data and let you work with the validated data directly, wrap it with generated types, or pass it to your own model layer.

## Validating JSON data

Validating incoming JSON data involves two steps:

1.  **Decode JSON:** Use `dart:convert` to parse the JSON string into a Dart object (usually `Map<String, dynamic>` or `List`).
2.  **Validate:** Pass the decoded object to your [Ack schema](./schemas.mdx)'s `safeParse()` method.

```dart
import 'dart:convert';
import 'package:ack/ack.dart';

// Define userSchema using correct Ack API
final userSchema = Ack.object({
  'name': Ack.string(),
  'age': Ack.integer().min(0),
  'email': Ack.string().email().nullable(),
});

void processApiResponse(String jsonString) {
  // 1. Decode JSON string into a Dart object.
  // Note: jsonDecode returns dynamic, so the structure is unknown initially.
  dynamic jsonData; 
  try {
    jsonData = jsonDecode(jsonString);
  } catch (e) {
    print('Failed to decode JSON: $e');
    return;
  }

  // 2. Validate the decoded data against your defined schema.
  final result = userSchema.safeParse(jsonData);

  if (result.isOk) {
    // Structure and types are valid.
    final validDataMap = result.getOrThrow();
    print('Valid JSON received: $validDataMap');

    // Pass the validated map to your own model layer,
    // or use an AckType-generated wrapper (see next section).
  } else {
    // Handle validation errors (see the Error Handling guide).
    print('Invalid JSON data: ${result.getError()}');
  }
}

// Example Usage
processApiResponse('{"name": "Alice", "age": 30, "email": "alice@example.com"}');
processApiResponse('{"name": "Bob", "age": -5}'); // Invalid: age fails min(0)
processApiResponse('{"age": 25}'); // Invalid: missing required field 'name'
processApiResponse('not valid json'); // Decoding error
```

*Learn more about [Error Handling](./error-handling.mdx).*

## Working with validated data

After successful validation, `result.getOrThrow()` returns a `Map<String, Object?>` whose structure and types match your schema. You can work with it directly, pass it into a model class, or use a generated typed wrapper:

```dart
final result = userSchema.safeParse(jsonData);

if (result.isOk) {
  final validData = result.getOrThrow();

  // Option 1: Work directly with the validated Map
  final name = validData['name'] as String;
  final age = validData['age'] as int;

  // Option 2: Pass the validated map into your own model layer
  // - Constructor: User(name: validData['name'], age: validData['age'])
  // - json_serializable: User.fromJson(Map<String, dynamic>.from(validData))
  // - freezed: User.fromJson(Map<String, dynamic>.from(validData))
  // - dart_mappable: UserMapper.fromMap(validData)
  // - Manual factory: User.fromMap(validData)
}
```

## Parsing JSON into typed wrappers

Once a schema is annotated with `@AckType()` (see [TypeSafe Schemas](./typesafe-schemas.mdx) for setup), parse JSON straight into typed getters — no manual casting:

```dart
import 'dart:convert';

final jsonData = jsonDecode('{"name": "Alice", "email": "alice@example.com"}');
final result = UserType.safeParse(jsonData);

if (result.isOk) {
  final user = result.getOrThrow()!;
  print(user.name);  // typed String
  print(user.email); // typed String?
}
```

## Key considerations

- **`dart:convert`:** Use `jsonEncode`/`jsonDecode` for JSON string conversion. Ack handles validation only.
- **Type safety:** `jsonDecode` produces `dynamic`, but successful validation guarantees the structure and types of the resulting `Map<String, Object?>`.
- **Model conversion:** After validation, how you convert the validated map into an app model is up to you. Ack keeps validation and wrapper generation separate from your model layer.
