JSON Serialization
Ack validates decoded JSON at your application boundary. On the way back out,
plain schemas produce JSON-native Dart values directly, while
codecs encode rich runtime values such as DateTime and Uri
back to their wire representation.
Validating JSON data
Validating incoming JSON data involves two steps:
- Decode JSON: Use
dart:convertto parse the JSON string into a Dart object (usuallyMap<String, dynamic>orList). - Validate: Pass the decoded object to your Ack schema’s
safeParse()method.
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.
// jsonDecode returns an unknown structure until the schema validates it.
Object? 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 errorLearn more about Error Handling.
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:
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 for setup), parse JSON straight into typed getters — no manual casting:
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?
}Encoding validated data
For schemas whose runtime values are already JSON-native (String, num,
bool, List, Map, and null), pass the validated value to jsonEncode:
final result = userSchema.safeParse({
'name': 'Alice',
'age': 30,
'email': 'alice@example.com',
});
if (result.isOk) {
final validData = result.getOrThrow()!;
final jsonString = jsonEncode(validData);
print(jsonString);
}When a schema decodes boundary values into richer Dart types, encode through
the schema before calling jsonEncode. This applies each codec in the nested
structure and restores the JSON-safe boundary shape:
final eventSchema = Ack.object({
'name': Ack.string(),
'startsAt': Ack.datetime(),
});
final event = eventSchema.parse({
'name': 'Launch',
'startsAt': '2026-01-15T14:00:00Z',
});
final boundaryData = eventSchema.encode(event);
final jsonString = jsonEncode(boundaryData);Use safeEncode() when you want a SchemaResult instead of an exception.
See Codecs for runtime invariants and custom bidirectional
conversions.
Key considerations
dart:convert: UsejsonDecodeandjsonEncodefor JSON text. Ack validates values and encodes codec runtime values back to their boundary representation.- Type safety:
jsonDecodeproducesdynamic, but successful validation guarantees the structure and types of the resultingMap<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.