Data Serialization
Convert domain models like Bookmark, Tag, and Collection to and from dictionary formats to handle API requests and responses using the built-in to_dict and from_dict methods.
Serializing Models for API Responses
To convert a domain model into a JSON-safe dictionary for an API response, use the to_dict() method. This method handles the conversion of internal types like datetime objects to ISO strings and Enum values to their raw string representations.
from flask import jsonify
from app.models.bookmark import Bookmark
# Example: Serializing a single bookmark in a route
@bookmarks_bp.route("/<bookmark_id>", methods=["GET"])
def get_bookmark(bookmark_id: str):
bookmark = _service.get_bookmark(bookmark_id)
if not bookmark:
return jsonify({"error": "Bookmark not found"}), 404
# to_dict() converts the model to a dictionary
return jsonify(bookmark.to_dict())
# Example: Serializing a list of bookmarks
@bookmarks_bp.route("/", methods=["GET"])
def list_bookmarks():
bookmarks, total = _service.list_bookmarks()
return jsonify({
"bookmarks": [b.to_dict() for b in bookmarks],
"total": total
})
Key Serialization Behaviors
Each model implements to_dict() to ensure all relevant state is exported:
- Bookmark: Converts
created_atandupdated_atto ISO format strings andstatus(aBookmarkStatusenum) to its value. - Tag: Converts
color(aTagColorenum) to its value and includes theusage_count. - Collection: Includes the calculated
sizeproperty (number of bookmarks) and convertscreated_atto an ISO string.
Deserializing Request Payloads
To create a new domain model instance from a JSON request body, use the @classmethod from_dict(). This is typically performed in the service layer after initial validation.
from typing import Dict, Any, Tuple, Optional
from app.models.bookmark import Bookmark
def create_bookmark(data: Dict[str, Any]) -> Tuple[Optional[Bookmark], Optional[str]]:
# Basic validation usually happens before calling from_dict
if not data.get("url"):
return None, "URL is required"
# Instantiate the model from the dictionary
bookmark = Bookmark.from_dict(data)
# The model now has a generated ID and default status
return bookmark, None
Model-Specific Deserialization
The from_dict() methods are designed for creation, meaning they only extract a subset of fields from the input dictionary:
| Model | Fields Extracted by from_dict | Defaulted/Generated Fields |
|---|---|---|
| Bookmark | url, title, description, tags | id, status, created_at, updated_at, metadata |
| Tag | name, color, description | id, usage_count |
| Collection | name, type, filter_rule | id, bookmark_ids, is_pinned, created_at |
Common Variations
Handling Optional Fields in Tags
The Tag.from_dict method provides a default color if one isn't specified in the payload.
# app/models/tag.py implementation detail
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> "Tag":
# Defaults to TagColor.GRAY if "color" is missing
color = TagColor(data["color"]) if "color" in data else TagColor.GRAY
return cls(
name=data["name"],
color=color,
description=data.get("description", "")
)
Smart vs. Manual Collections
When deserializing a Collection, the type field determines how the collection behaves, but from_dict does not allow you to prepopulate the bookmark_ids list.
# Creating a smart collection via dictionary
data = {
"name": "Python Articles",
"type": "smart",
"filter_rule": "python"
}
collection = Collection.from_dict(data)
# collection.is_smart will be True
Troubleshooting and Gotchas
Field Omissions in from_dict
A common mistake is expecting from_dict to restore a model's full state (like its id or created_at timestamp). In this codebase, from_dict is strictly for creating new instances. If you need to reconstruct a model with an existing ID (e.g., from a database), you must use the standard constructor or a repository-specific loading mechanism.
Enum Validation
The from_dict methods for Tag and Collection pass the dictionary value directly into the Enum constructor (e.g., TagColor(data["color"])). If the value in the dictionary does not match a valid Enum member, a ValueError will be raised. Ensure input is validated before calling from_dict.
ISO Date Strings
While to_dict produces ISO 8601 strings for dates, from_dict does not currently support reading these strings back into datetime objects; it ignores date fields in the input dictionary entirely to allow the dataclass default factories to set them to the current time.