Skip to main content

TinyORM: Serialization

Introduction

When building APIs using TinyORM, you will often need to convert your models and relationships to vectors, maps, or JSON. TinyORM includes convenient methods for making these conversions, as well as controlling which attributes are included in the serialized representation of your models.

Serializing Models & Collections

Serializing To Vectors & Maps

To convert a model and its loaded relationships to a vector, you should use the toVector or toMap methods. This methods are recursive, so all attributes and all relations (including the relations of relations) will be converted to vectors:

using Models::User;

auto user = User::with("roles")->first();

return user->toVector();

return user->toMap();

The attributesToVector or attributesToMap methods may be used to convert a model's attributes to a vector or map but not its relationships:

auto user = User::first();

return user->attributesToVector();

return user->attributesToMap();

You may also convert entire collections of models to vectors or maps by calling the toVector or toMap methods on the collection instance:

ModelsCollection<User> users = User::with("roles")->all();

return users.toVector();

return users.toMap();

Serializing To JSON

To convert a model to JSON, you should use the toJson method. Like toVector or toMap, the toJson method is recursive, so all attributes and relations will be converted to JSON. You may also specify any JSON encoding options that are supported by QJsonDocument::toJson:

using Models::User;

auto user = User::with("roles")->find(1);

return user->toJson();

return user->toJson(QJsonDocument::Indented);

You may also convert entire collections of models to JSON by calling the toJson method on the collection instance:

ModelsCollection<User> users = User::with("roles")->findMany({1, 2});

return users.toJson();

You can also convert models to the QJsonObject and QJsonDocument using the toJsonArray and toJsonDocument methods and collection of models to QJsonArray and QJsonDocument using the toJsonArray and toJsonDocument methods.

Relationships

When a TinyORM model is converted to JSON, its loaded relationships will automatically be included as attributes on the JSON object. Also, though TinyORM relationship methods are defined using "camelCase" method names, a relationship's JSON attributes will be "snake_case".

This behavior is affected and can be overridden by the u_snakeAttributes static data member:

#include <orm/tiny/model.hpp>

using Orm::Tiny::Model;

class Album final : public Model<Album, AlbumImage>
{
friend Model;
using Model::Model;

/*! Indicates whether attributes are snake_cased during serialization. */
inline static const bool u_snakeAttributes = false;
};

Hiding Attributes From JSON

Sometimes you may wish to limit the attributes, such as passwords, that are included in your model's vector, map, or JSON representation. To do so, add a u_hidden static data member to your model. Attributes that are listed in the u_hidden data member set will not be included in the serialized representation of your model:

#include <orm/tiny/model.hpp>

using Orm::Tiny::Model;

class User final : public Model<User>
{
friend Model;
using Model::Model;

/*! The attributes that should be hidden during serialization. */
inline static std::set<QString> u_hidden {"password"};
};
note

To hide relationships, add the relationship's method name to your TinyORM model's u_hidden static data member.

Alternatively, you may use the u_visible static data member to define an "allow list" of attributes that should be included in your model's vector, map, and JSON representation. All attributes that are not present in the u_visible set will be hidden during serialization:

#include <orm/tiny/model.hpp>

using Orm::Tiny::Model;

class User final : public Model<User>
{
friend Model;
using Model::Model;

/*! The attributes that should be visible during serialization. */
inline static std::set<QString> u_visible {
"first_name", "last_name",
};
};

Temporarily Modifying Attribute Visibility

If you would like to make some typically hidden attributes visible on a given model instance, you may use the makeVisible method. The makeVisible method returns a model reference:

return user.makeVisible("attribute").toMap();

return user.makeVisible({"id", "name"}).toMap();

Likewise, if you would like to hide some attributes that are typically visible, you may use the makeHidden method.

return user.makeHidden("attribute").toVector();

return user.makeHidden({"id", "name"}).toVector();

If you wish to temporarily override all of the visible or hidden attributes, you may use the setVisible and setHidden methods respectively:

return user.setVisible({"id", "name"}).toMap();

return user.setHidden({"email", "password", "note"}).toJson();

You can also clear all visible and hidden attributes or determine whether a visible / hidden attribute is defined:

user.clearVisible();

user.clearHidden();

return user.hasVisible("name");

return user.hasHidden("password");

Appending Values To JSON

Occasionally, when converting models to vector, map, or JSON, you may wish to add attributes that do not have a corresponding column in your database. To do so, first define an accessor for the value:

#include <orm/tiny/model.hpp>

using Orm::Tiny::Model;

class User final : public Model<User>
{
friend Model;
using Model::Model;

protected:
/*! Accessor to determine if the user is an administrator. */
Attribute isAdmin() const noexcept
{
return Attribute::make(/* get */ []() -> QVariant
{
return QStringLiteral("yes");
});
}
};

If you would like the accessor to always be appended to your model's vector, map, and JSON representations, you may add the attribute name to the u_appends data member set of your model. Note that attribute names are typically referenced using their "snake_case" serialized representation, even though the accessor's method name is defined using "camelCase":

#include <orm/tiny/model.hpp>

using Orm::Tiny::Model;

class User final : public Model<User>
{
friend Model;
using Model::Model;

/*! Map of mutator names to methods. */
inline static const QHash<QString, MutatorFunction> u_mutators {
{"is_admin", &User::isAdmin},
};

/*! The attributes that should be appended during serialization. */
std::set<QString> u_appends {"is_admin"};
};

Once the attribute has been added to the u_appends set, it will be included in both the model's vector, map, and JSON representations. Attributes in the u_appends set will also respect the u_visible and u_hidden attribute settings configured on the model.

Special note should be given to the u_mutators static data member map, which maps accessors' attribute names to its methods. This data member is required because C++ does not currently support reflection.

Appending At Run Time

At runtime, you may instruct a model instance to append additional attributes using the append method. Or, you may use the setAppends method to override the entire set of appended attributes for a given model instance:

return user.append("is_admin").toVector();

return user.append({"is_admin", "is_banned"}).toMap();

return user.setAppends({"is_admin"}).toJson();

And you can also clear all appends or determine whether an append is defined:

user.clearAppends();

return user.hasAppend("is_admin");

Date Serialization

Customizing The Default Date Format

You may customize the default serialization format by overriding the serializeDate and serializeDateTime methods. These methods do not affect how your dates are formatted for storage in the database:

/*! Prepare a date for vector, map, or JSON serialization. */
QString serializeDate(const QDate date)
{
return date.toString("yyyy-MM-dd");
}

/*! Prepare a datetime for vector, map, or JSON serialization. */
QString serializeDateTime(const QDateTime &datetime)
{
return datetime.toUTC().toString("yyyy-MM-ddTHH:mm:ssZ");
}

Customizing The Date Format Per Attribute

You may customize the serialization format of individual TinyORM date attributes by specifying the date format in the model's cast declarations:

/*! The attributes that should be cast. */
inline static std::unordered_map<QString, CastItem> u_casts {
{"birthday", {CastType::CustomQDate, "yyyy-MM-dd"}},
{"joined_at", {CastType::CustomQDateTime, "yyyy-MM-dd HH:00"}},
};