Skip to content

Serializer

A serializer sets the JSON shape of a response: which keys the client gets, their names and their values. It works with any Dart type: a model, a plain class, or a map.

Tip

You do not always need a serializer. A controller can return a model, a Map or a List as it is. See Response. To only hide some model fields, use hidden.

Create a serializer

dox make:serializer Blog

This makes lib/app/http/serializers/blog.serializer.dart. The file is for a model with the same name. Change the type to use it for something else.

How it works

Extend Serializer<T> and write convert. convert gets one value of type T and gives back one JSON object.

lib/app/http/serializers/blog.serializer.dart
import 'package:dox_core/dox_core.dart';

import '../../../app/models/blog/blog.model.dart';

class BlogSerializer extends Serializer<Blog> {
  BlogSerializer(super.data);

  @override
  Map<String, dynamic> convert(Blog m) {
    return <String, dynamic>{
      'uid': m.id,
      'title_en': m.title,
    };
  }
}

Return the serializer from a controller. Dox sends its toJson().

lib/app/http/controllers/blog.controller.dart
class BlogController {
  Future<BlogSerializer> index(DoxRequest req) async {
    List<Blog> blogs = await Blog().all();
    return BlogSerializer(blogs); // a list
  }

  Future<BlogSerializer> show(DoxRequest req, String id) async {
    Blog? blog = await Blog().find(id);
    return BlogSerializer(blog); // one model
  }
}

The response of show:

{
  "uid": 1,
  "title_en": "This is title"
}

What you give, and what the client gets:

You give The client gets
one value of type T one JSON object
a List<T> a JSON list ([] when the list is empty)
null (for example, find found nothing) null
any other type null

The list type must be List<T>

A List<dynamic> is not a List<T>, so the response is null. This happens with a list from jsonDecode. Call .cast<T>() first. See JSON from another API.

Examples with models

Change values

convert is plain Dart. You can format dates, join fields and add new keys.

lib/app/http/serializers/user.serializer.dart
class UserSerializer extends Serializer<User> {
  UserSerializer(super.data);

  @override
  Map<String, dynamic> convert(User m) {
    return <String, dynamic>{
      'id': m.id,
      'name': '${m.firstName} ${m.lastName}',
      'is_active': m.status == 'active',
      'joined_at': m.createdAt?.toIso8601String(),
    };
  }
}

A serializer inside a serializer

Use one serializer for a relation inside another. Load the relation first, for example with preload.

lib/app/http/serializers/comment.serializer.dart
class CommentSerializer extends Serializer<Comment> {
  CommentSerializer(super.data);

  @override
  Map<String, dynamic> convert(Comment m) {
    return <String, dynamic>{
      'id': m.id,
      'content': m.content,
      'user': UserSerializer(m.user).toJson(), // null when there is no user
    };
  }
}
Future<CommentSerializer> index(DoxRequest req) async {
  List<Comment> comments = await Comment().preload('user').all();
  return CommentSerializer(comments);
}

With pagination

page.data is a List<User>. Put the serializer output in place of data.

Future<Map<String, dynamic>> index(ListUsers query) async {
  Pagination<User> page = await User()
      .orderBy('id', 'desc')
      .paginate(currentPage: query.page, perPage: query.perPage);
  return <String, dynamic>{
    ...page.toJson(),
    'data': UserSerializer(page.data).toJson(),
  };
}

See Pagination for the full example.

Examples without models

T can be any type. You do not need the ORM.

A plain Dart class

lib/app/http/serializers/weather.serializer.dart
import 'package:dox_core/dox_core.dart';

class Weather {
  final String city;
  final double celsius;

  Weather(this.city, this.celsius);
}

class WeatherSerializer extends Serializer<Weather> {
  WeatherSerializer(super.data);

  @override
  Map<String, dynamic> convert(Weather m) {
    return <String, dynamic>{
      'city': m.city,
      'celsius': m.celsius,
      'fahrenheit': m.celsius * 9 / 5 + 32,
    };
  }
}
WeatherSerializer today(DoxRequest req) {
  return WeatherSerializer(<Weather>[Weather('Yangon', 30)]);
}

Rows from the query builder

QueryBuilder.table(...) gives rows as maps, not models. Use Serializer<Map<String, dynamic>> to pick and rename the keys.

lib/app/http/serializers/sales_report.serializer.dart
class SalesReportSerializer extends Serializer<Map<String, dynamic>> {
  SalesReportSerializer(super.data);

  @override
  Map<String, dynamic> convert(Map<String, dynamic> m) {
    return <String, dynamic>{
      'product': m['name'],
      'sold': m['total'],
    };
  }
}
Future<SalesReportSerializer> report(DoxRequest req) async {
  List<Map<String, dynamic>> rows = await QueryBuilder.table('orders')
      .selectRaw('name, count(*) as total')
      .groupBy('name')
      .get();
  return SalesReportSerializer(rows);
}

JSON from another API

jsonDecode gives a List<dynamic>. Cast it to the type of the serializer first.

Future<SalesReportSerializer> remote(DoxRequest req) async {
  String body = await fetchReport(); // your HTTP call
  List<dynamic> rows = jsonDecode(body);
  return SalesReportSerializer(rows.cast<Map<String, dynamic>>());
}

See also