Customizing serialization
If you need custom handling for your serialization logic, either to conform to an existing interface or to ensure parity with other parts
of your application, define the fromJson
and toJson
methods to your data type. Celest will use these methods when provided instead of
its default serialization logic.
Example
Consider the Price
type that we defined in Defining your data types.
enum Currency { usd, cad, ... }
class Price {
const Price({
required this.currency,
required this.dollars,
required this.cents,
}): assert(cents < 100);
final Currency currency;
final int dollars;
final int cents;
}
By default, Celest follows the conventions of package:json_serializable
and will serialize the Price
type as:
{
"currency": "usd",
"dollars": 100,
"cents": 34
}
If, however, you want to serialize the currency
field as uppercase, you can define the fromJson
and toJson
methods
in the Price
class.
class Price {
// ...
factory Price.fromJson(Map<String, dynamic> json) {
return Price(
currency: Currency.values.firstWhere(
(e) => e.toString().toUpperCase() == json['currency'],
),
dollars: json['dollars'] as int,
cents: json['cents'] as int,
);
}
Map<String, dynamic> toJson() => {
'currency': currency.name.toUpperCase(),
'dollars': dollars,
'cents': cents,
};
}
Then, anytime you use the Price
type in a cloud function, Celest will use this logic for handling
the serialization. And the resulting JSON response for the currency
will be properly upper-cased.
{
"customerOrder": {
"id": 123,
"customerName": "Celest",
"price": {
+ "currency": "USD",
- "currency": "usd",
"dollars": 100,
"cents": 34
}
}
}
Customize serialization of third-party types
Sometimes, though, you cannot control the fromJson
/toJson
methods of a class, such as when using a third-party library.
In these cases, you can create a @customOverride
which "redefines" the type for serialization in Celest.
For example, consider the case where the Price
class from our Order
type is imported from a third-party library, package:price
.
Since we do not own package:price
, we cannot change the fromJson
and toJson
methods of the Price
class.
Instead of modifying the Price
class, we can define a custom override for Price
which will be used in place of the original
Price
class when it's encountered in Celest.
Custom overrides are defined as extension types (opens in a new tab) which wrap and implement the original type. You can use these to override any part of the original interface to suit its use in your backend, though customizing serialization is the most common use case.
You must always implement the original type in your extension type and mark it with @customOverride
. By convention, these types are
defined in either lib/models/_overrides.dart
or lib/exceptions/_overrides.dart
and are not used directly in your backend. They serve as
global marker types which Celest will use internally.
import 'package:celest/celest.dart';
import 'package:price/price.dart';
// We create an extension type which wraps over the third-party type and
// is marked as a custom override of it.
//
// You do not need to use this type directly in your backend. Celest will
// use it internally anytime it encounters the original type.
@customOverride
extension type PriceOverride(Price price) implements Price {
factory PriceOverride.fromJson(Map<String, Object?> json) {
return PriceOverride(
Price(
currency: Currency.values.firstWhere(
(e) => e.toString().toUpperCase() == json['currency'],
),
dollars: json['dollars'] as int,
cents: json['cents'] as int,
),
);
}
Map<String, Object?> toJson() => {
'currency': price.currency.toString().toUpperCase(),
'dollars': price.dollars,
'cents': price.cents,
};
}
Next steps
You have now learned how Celest handles the serialization of requests/responses to your functions, and how to use your own custom types and serialization logic. Learn about more features of Celest Functions by following our guides for defining custom exceptions and managing environment variables.