-
Notifications
You must be signed in to change notification settings - Fork 73
JSON encoding and decoding
- Applies to: Flask and FastAPI
- Best examples:
examples/mini_examples/ex01_to_dict.py,examples/mini_examples/ex03_jsonapi_attr.py - Related pages: Customization, Relationships and Includes, Column Types
By default, SAFRS serializes exposed SQLAlchemy model attributes and relationships into JSON:API resource documents.
Override to_dict when you want to customize the serialized attribute payload for a resource.
Example:
Use @jsonapi_attr for computed attributes that should appear in the API.
This is the best fit when:
- the value is derived
- the field does not map directly to a stored column
- you want a cleaner per-attribute customization than a whole
to_dictoverride
- Getter-only
@jsonapi_attrfields are exposed as read-only JSON:API attributes. - If a client sends a read-only computed field in
POSTorPATCH, SAFRS rejects the write with a client validation error. - If a
PATCHpayload simply echoes the current computed value unchanged, SAFRS accepts the round-trip and leaves the object unchanged.
Example:
from safrs import SAFRSBase, jsonapi_attr
class Publisher(SAFRSBase, db.Model):
__tablename__ = "Publishers"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String)
@jsonapi_attr
def stock(self) -> str:
"""
description: Human-readable stock summary
default: in-stock
swagger_type: string
"""
return "in-stock"Typical response payload:
{
"data": {
"type": "Publisher",
"id": "1",
"attributes": {
"name": "ACME",
"stock": "in-stock"
}
}
}Attempting to write stock in a request is rejected because it has no setter.
@jsonapi_attr also supports writable computed properties through setters.
Use this for facade-style fields such as passwords, denormalized labels, or derived write-through values.
Example:
class Person(SAFRSBase, db.Model):
__tablename__ = "People"
id = db.Column(db.Integer, primary_key=True)
_password = db.Column(db.String)
@jsonapi_attr
def password(self) -> str:
"""
description: Password facade
default: example-secret
swagger_type: string
swagger_format: password
"""
return "********"
@password.setter
def password(self, value: str) -> None:
if len(value) < 8:
raise ValueError("password too short")
self._password = hash_password(value)Important behavior:
- SAFRS passes the raw request value to the setter.
-
ValueErrorandTypeErrorraised by the setter are surfaced as client validation errors. - SAFRS does not perform SQLAlchemy-style type coercion for
@jsonapi_attr. The setter owns input parsing and validation.
See the password examples in:
@jsonapi_attr declared on a mixin or base class is inherited by SAFRS subclasses.
This is useful for shared behavior such as audit labels, facade fields, or small reusable computed attributes.
jsonapi_attr supports lightweight doc metadata. Put YAML-like key/value pairs in the docstring before ---.
Example:
@jsonapi_attr
def some_attr(self) -> str:
"""
description: User-facing derived value
default: sample-value
swagger_type: string
swagger_format: password
---
Free-form prose after the separator is not used as structured metadata.
"""
return "sample-value"Useful metadata keys:
descriptiondefaultswagger_typeswagger_format
Current behavior:
- Flask swagger generation uses this metadata for attribute/request docs.
- FastAPI request schemas and examples include writable computed attrs and omit getter-only attrs.
- FastAPI type inference prefers a Python return annotation on the getter; if none is present, it falls back to
swagger_typewhen available.
There is no dedicated write_only=True flag today.
For secret-style inputs, the usual pattern is:
- expose a writable
@jsonapi_attr - return a placeholder or masked value from the getter
- store the real value in a hidden/internal column
This is the pattern used by the password examples above.
@jsonapi_attr is a serialization and write facade feature, not a full query abstraction.
Current limitations:
- filtering is not implemented automatically for
@jsonapi_attr - sorting is not implemented automatically for
@jsonapi_attr
If a field must participate in filtering or sorting, use a real column or implement an explicit custom hook/query strategy.
Relevant controls include:
exclude_attrsexclude_relsrelationship.expose = False
Examples:
- use
to_dictwhen you want to reshape the serialized resource broadly - use
@jsonapi_attrwhen you want targeted computed attributes - use exclusion controls when a field or relationship should not be public
- Home
- Installation
- Quickstart (Flask)
- Quickstart (FastAPI)
- JSON:API Basics
- Relationships and Includes
- Filtering
- Sorting, Pagination, and Sparse Fieldsets
- Content Types and Errors
- Bulk Requests
- RPC / Custom Methods
- Customization
- Security and Access Control
- Stateless Endpoints / JABase
- Performance
- Examples
- Existing Databases (Legacy)
- PostGIS / GeoAlchemy2
- Docker / Deployment
- Troubleshooting
- Reference: SAFRSBase
- Reference: SAFRSBase Customization