Saltar al contenido

JSON con Bytes como Base64

Si tu app necesita recibir y enviar datos JSON, pero necesitas incluir datos binarios en ellos, puedes codificarlos como base64.

Base64 vs Archivos

Considera primero si puedes usar Request Files para subir datos binarios y Custom Response - FileResponse para enviar datos binarios, en lugar de codificarlos en JSON.

JSON solo puede contener strings codificados en UTF-8, así que no puede contener bytes crudos.

Base64 puede codificar datos binarios en strings, pero para hacerlo, necesita usar más caracteres que los datos binarios originales, así que normalmente sería menos eficiente que archivos regulares.

Usa base64 solo si definitivamente necesitas incluir datos binarios en JSON, y no puedes usar archivos para eso.

bytes de Pydantic

Puedes declarar un modelo de Pydantic con campos bytes, y luego usar val_json_bytes en la configuración del modelo para indicarle que use base64 para validar los datos JSON de entrada, como parte de esa validación decodificará el string base64 a bytes.

from fastapi import FastAPI
from pydantic import BaseModel


class DataInput(BaseModel):
    description: str
    data: bytes

    model_config = {"val_json_bytes": "base64"}

# Code here omitted 👈

app = FastAPI()


@app.post("/data")
def post_data(body: DataInput):
    content = body.data.decode("utf-8")
    return {"description": body.description, "content": content}

# Code below omitted 👇
👀 Vista previa del archivo completo
from fastapi import FastAPI
from pydantic import BaseModel


class DataInput(BaseModel):
    description: str
    data: bytes

    model_config = {"val_json_bytes": "base64"}


class DataOutput(BaseModel):
    description: str
    data: bytes

    model_config = {"ser_json_bytes": "base64"}


class DataInputOutput(BaseModel):
    description: str
    data: bytes

    model_config = {
        "val_json_bytes": "base64",
        "ser_json_bytes": "base64",
    }


app = FastAPI()


@app.post("/data")
def post_data(body: DataInput):
    content = body.data.decode("utf-8")
    return {"description": body.description, "content": content}


@app.get("/data")
def get_data() -> DataOutput:
    data = "hello".encode("utf-8")
    return DataOutput(description="A plumbus", data=data)


@app.post("/data-in-out")
def post_data_in_out(body: DataInputOutput) -> DataInputOutput:
    return body

Si revisas los /docs, mostrarán que el campo data espera bytes codificados en base64:

Podrías enviar una request así:

{
    "description": "Some data",
    "data": "aGVsbG8="
}

Consejo

aGVsbG8= es la codificación base64 de hello.

Y luego Pydantic decodificará el string base64 y te dará los bytes originales en el campo data del modelo.

Recibirás una response así:

{
  "description": "Some data",
  "content": "hello"
}

bytes de Pydantic para Datos de Salida

También puedes usar campos bytes con ser_json_bytes en la configuración del modelo para datos de salida, y Pydantic serializará los bytes como base64 al generar la respuesta JSON.

from fastapi import FastAPI
from pydantic import BaseModel

# Code here omitted 👈

class DataOutput(BaseModel):
    description: str
    data: bytes

    model_config = {"ser_json_bytes": "base64"}

# Code here omitted 👈

app = FastAPI()

# Code here omitted 👈

@app.get("/data")
def get_data() -> DataOutput:
    data = "hello".encode("utf-8")
    return DataOutput(description="A plumbus", data=data)

# Code below omitted 👇
👀 Vista previa del archivo completo
from fastapi import FastAPI
from pydantic import BaseModel


class DataInput(BaseModel):
    description: str
    data: bytes

    model_config = {"val_json_bytes": "base64"}


class DataOutput(BaseModel):
    description: str
    data: bytes

    model_config = {"ser_json_bytes": "base64"}


class DataInputOutput(BaseModel):
    description: str
    data: bytes

    model_config = {
        "val_json_bytes": "base64",
        "ser_json_bytes": "base64",
    }


app = FastAPI()


@app.post("/data")
def post_data(body: DataInput):
    content = body.data.decode("utf-8")
    return {"description": body.description, "content": content}


@app.get("/data")
def get_data() -> DataOutput:
    data = "hello".encode("utf-8")
    return DataOutput(description="A plumbus", data=data)


@app.post("/data-in-out")
def post_data_in_out(body: DataInputOutput) -> DataInputOutput:
    return body

bytes de Pydantic para Datos de Entrada y Salida

Y por supuesto, puedes usar el mismo modelo configurado para usar base64 para manejar tanto la entrada (validar) con val_json_bytes como la salida (serializar) con ser_json_bytes al recibir y enviar datos JSON.

from fastapi import FastAPI
from pydantic import BaseModel

# Code here omitted 👈

class DataInputOutput(BaseModel):
    description: str
    data: bytes

    model_config = {
        "val_json_bytes": "base64",
        "ser_json_bytes": "base64",
    }

# Code here omitted 👈

app = FastAPI()

# Code here omitted 👈

@app.post("/data-in-out")
def post_data_in_out(body: DataInputOutput) -> DataInputOutput:
    return body
👀 Vista previa del archivo completo
from fastapi import FastAPI
from pydantic import BaseModel


class DataInput(BaseModel):
    description: str
    data: bytes

    model_config = {"val_json_bytes": "base64"}


class DataOutput(BaseModel):
    description: str
    data: bytes

    model_config = {"ser_json_bytes": "base64"}


class DataInputOutput(BaseModel):
    description: str
    data: bytes

    model_config = {
        "val_json_bytes": "base64",
        "ser_json_bytes": "base64",
    }


app = FastAPI()


@app.post("/data")
def post_data(body: DataInput):
    content = body.data.decode("utf-8")
    return {"description": body.description, "content": content}


@app.get("/data")
def get_data() -> DataOutput:
    data = "hello".encode("utf-8")
    return DataOutput(description="A plumbus", data=data)


@app.post("/data-in-out")
def post_data_in_out(body: DataInputOutput) -> DataInputOutput:
    return body