JSON에 바이트(Bytes)를 Base64로 담기

JSON에 바이트(Bytes)를 Base64로 담기

JSON은 UTF-8로 인코딩된 문자열만 담을 수 있어서, 원시 바이너리 데이터를 그대로 넣을 수 없어요. 그런데 앱이 JSON 형태로 통신하면서도 바이너리 데이터를 주고받아야 하는 상황이 있다면, base64로 인코딩해서 해결할 수 있어요. Pydantic 모델의 bytes 필드 설정만으로 입출력 양쪽을 모두 처리하는 방법을 살펴볼게요.

출처: FastAPI 공식 문서 - JSON with Bytes as Base64

Base64 vs 파일 업로드

먼저 base64를 쓰기 전에, 정말 이 방식이 필요한지 한 번 따져 보는 게 좋아요. 바이너리 데이터를 올릴 때는 Request Files로 업로드하고, 내려보낼 때는 Custom Response - FileResponse로 보내는 게 더 효율적일 수 있어요.

base64는 바이너리 데이터를 문자열로 인코딩할 수 있지만, 원본보다 더 많은 문자를 사용해요. 그래서 보통 일반 파일보다 비효율적이에요. 바이너리 데이터를 반드시 JSON에 넣어야 하고, 파일 방식을 쓸 수 없을 때만 base64를 쓰는 게 맞아요.

Pydantic bytes 필드로 입력 받기

bytes 타입 필드를 가진 Pydantic 모델을 선언하고, model_config에서 val_json_bytes로 base64를 지정하면 입력 JSON 데이터를 검증할 때 base64 문자열을 디코드해서 원래 바이트로 만들어줘요.

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

/docs에서 확인해 보면 data 필드가 base64로 인코딩된 바이트를 기대한다고 표시돼요. 아래처럼 요청을 보내면,

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

(aGVsbG8=hello를 base64로 인코딩한 값이에요.) Pydantic이 그 문자열을 디코드해서 모델의 data 필드에 원래 바이트를 담아줘요. 응답은 이렇게 돌아와요.

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

Pydantic bytes 필드로 출력하기

응답 쪽도 비슷해요. model_config에서 ser_json_bytes를 지정하면, 응답 JSON을 만들 때 바이트를 base64로 직렬화해요. 위 코드의 DataOutput 모델이 그 예시예요.

입력과 출력 모두 처리하기

입력 검증(val_json_bytes)과 출력 직렬화(ser_json_bytes)를 같은 모델에 둘 다 지정하면, JSON 데이터를 받고 보낼 때 base64로 처리하는 모델 하나로 양방향을 모두 다룰 수 있어요. 위 코드의 DataInputOutput이 바로 그 경우예요.

더 알아보기 (Learn more)