Deutsch

Entwicklertools · Base64-Encoder und -Decoder

Base64 in JSON APIs: Warum binäre Felder codiert werden und was es kostet

· Warum es wichtig ist

base64 Kodierung

Ein JSON-Feld, das eine lange Base64-Zeichenfolge enthält, die für den Transport codierte Binärdaten darstellt
Original-ToolAcre-Vektorillustration

JSON hat keinen Bytetyp, daher werden Binärdaten normalerweise Base64-codiert in eine Zeichenfolge. In diesem Beitrag wird erklärt, warum es diese Konvention gibt, was sie an Größe und CPU kostet und wann ein separater binärer Endpunkt die bessere Wahl ist.

Das Feld PDF, das die Antwort dominierte – eine konkrete API-Nutzlast, bei der ein Base64-Blob alles andere überwog

Eine API-Antwort enthält ein großes Objekt mit einem einzelnen Feld, das die Nutzlastgröße dominiert. Die Antwort ist JSON, also ist jeder Wert eine Zeichenfolge oder Zahl. Die meisten Felder sind klein: Benutzer-IDs, Zeitstempel, Statuscodes. Ein Feld enthält imageData oder fileContents und ist eine 40-Kilobyte-Base64-Zeichenfolge. Die gesamte Antwort umfasst 50 Kilobyte. Dieses einzelne Feld macht 80 Prozent der Übertragung aus, was verschwenderisch erscheint, da der Server es ursprünglich als Bytes gesendet hat und der Client irgendwann erneut Bytes benötigt.

Base64 hat dieses Problem gelöst: JSON hat keinen nativen Bytetyp, daher müssen Binärdaten in einen String eingeschlossen werden. Base64 konvertiert beliebige Bytes in ASCII-Zeichen, die in JSON sicher sind. Sowohl der Client als auch der Server müssen beim Senden kodieren und beim Empfang dekodieren, was zusätzlichen CPU-Overhead verursacht. Die resultierende Nutzlast ist etwa ein Drittel größer als die Rohbytes. In diesem Beitrag wird erklärt, warum es diese Konvention gibt, was sie in der Praxis kostet und wann es sich lohnt, die JSON-Einschränkung mit einem separaten binären Endpunkt zu durchbrechen.

Warum JSON keine Rohbytes übertragen kann – Zeichenfolgen müssen gültiger Unicode-Text sein, daher benötigen beliebige Bytes einen Text-Wrapper

JSON ist ein Textformat, bei dem alle Werte gültiger Unicode-Text sein müssen. Die Spezifikation definiert Zeichenfolgen, Zahlen, boolesche Werte und Null. Es verfügt nicht über einen Byte-Array- oder Puffertyp. Wenn eine API Binärdaten wie ein Bild, eine kryptografische Signatur oder einen Datei-Upload zurückgeben muss, kann sie die Rohbytes nicht direkt in das JSON-Objekt einfügen. Die Bytes können Zeichen enthalten, die JSON-Parser als Strukturmarkierungen interpretieren. Ein Nullbyte in der Mitte eines binären Blobs könnte eine Zeichenfolge vorzeitig beenden oder den Parser beschädigen.

Die übliche Lösung besteht darin, die Binärdaten als Base64 zu kodieren und so eine Zeichenfolge aus ASCII-Zeichen zu erzeugen, die von JSON-Parsern als einfacher Text behandelt wird. Der empfangende Client dekodiert dann das Base64 wieder in Bytes und verwendet diese. Dieser Codierungsschritt findet auf API-Ebene statt und bleibt den meisten Entwicklern verborgen. Er stellt jedoch einen echten Kostenfaktor dar, der anfällt, wenn APIs viele Binärfelder zurückgeben. Die Kosten von Base64 in JSON summieren sich über den gesamten Anfrage-Antwort-Zyklus. Der Größennachteil ist der erste: Die Base64-Ausgabe ist aufgrund des Codierungsaufwands etwa 33 Prozent größer als ihre Eingabe.

Die Kosten: ein Drittel mehr Bytes, Dekodierzeit und Speicherkopien – wobei die einzelnen Kosten bei einem typischen Client anfallen

Eine Videodatei mit 30 Megabyte wird bei Base64-Codierung zu 40 Megabyte. Das Herunterladen von 40 statt 30 Megabyte kostet Bandbreite und Akku auf Mobilgeräten sowie Zeit für Benutzer mit langsamen Verbindungen. Der zweite Kostenfaktor ist die CPU-Zeit. Der Server muss die Binärdaten als Base64 kodieren, bevor sie in JSON stringifiziert werden können. Der Client muss JSON analysieren und dann jedes Base64-Feld wieder in Bytes dekodieren. Bei einer Antwort mit mehreren Binärfeldern oder einem Client, der Tausende von Antworten verarbeitet, summiert sich diese CPU-Zeit.

Auf eingeschränkten Geräten wie Telefonen verbrauchen JavaScript-String-Vorgänge und der für die Base64-Dekodierung verwendete TextDecoder Batterie und verlangsamen die Anwendung. Der dritte Kostenfaktor ist der Speicher: Der JSON-Parser erstellt ein Zeichenfolgenobjekt für das Base64-Feld und durch die Dekodierung wird eine weitere Kopie als Uint8Array erstellt. Ein großes Feld wird zweimal im Speicher instanziiert, bevor die Anwendung es verwenden kann. Ein ausgearbeitetes Beispiel verdeutlicht die Kosten. Angenommen, ein API-Endpunkt gibt Benutzerprofildaten zurück, einschließlich eines 100-Kilobyte großen Avatar-Bildes. Der Server liest das Image in Bytes von der Festplatte, codiert es in Base64 und fügt es in die JSON-Antwort ein.

Arbeitsbeispiel: Überprüfen eines Base64-Felds aus einer API-Antwort – Dekodierung im Browser, um zu bestätigen, was der Server tatsächlich gesendet hat

Die JSON-Antwort umfasst jetzt etwa 135 Kilobyte (33 % Overhead plus die anderen Felder). Der Client lädt 135 Kilobyte statt 100 herunter. Im Browser erstellt der JSON-Parser ein JavaScript-String-Objekt für die Base64-Daten.

Wenn die Anwendung das Bild benötigt, ruft sie den Base64-Decoder auf, der ein Uint8Array der ursprünglichen 100 Kilobyte erstellt. Für die wenigen Millisekunden der Dekodierung sind beide Objekte im Speicher vorhanden. Wenn auf der Seite zehn Profile mit Avataren angezeigt werden, vervielfachen sich die Kosten. Die Alternative besteht darin, dass die API die JSON-Antwort mit einer separaten URL für jede Avatar-Ressource zurückgibt, sodass der Browser die Bilddownloads mit seinem nativen Caching, progressiven Rendering und Speichermanagement verarbeiten kann.

Alternativen: mehrteilige, separate Download-URLs und rohe binäre Endpunkte – die jeweiligen Kompromisse

Der Kompromiss zwischen der Aufnahme der Binärdatei in JSON und dem separaten Abrufen hängt vom API-Zweck und den Nutzungsmustern ab. Bei einer Suchergebnisseite, die Hunderte von winzigen Miniaturbildern zurückgibt, macht das Abrufen jedes Bilds als separate Anfrage das HTTP-Verbindungspooling und -Caching unmöglich. Das Einbinden als Base64 in die JSON-Antwort könnte schneller sein. Für eine detaillierte Profilseite, die ein oder zwei hochauflösende Bilder erfordert, sind separate Downloads eindeutig besser. Die API-Dokumentation sollte die maximale Größe von Base64-Feldern angeben und angeben, wann Clients separate Endpunkte erwarten sollten.

Wenn ein Feld regelmäßig ein oder zwei Kilobyte überschreitet, ist die Inline-Base64-Strategie ein Zeichen dafür, dass das API-Design überdacht werden muss. Es gibt Alternativen zu Base64 in JSON, aber jede hat Nachteile. Bei mehrteiligen MIME-Antworten werden Binärdaten und Text getrennt, sodass der Binärabschnitt als Rohbytes gesendet wird und nur der Textabschnitt JSON ist. Dies erfordert, dass der Client eine mehrteilige Nachricht analysiert, anstatt nur JSON.parse aufzurufen, was die Komplexität erhöht. Eine separate Download-URL in der JSON-Antwort weist den Client an, die Binärressource separat abzurufen.

Konventionen, die es wert sind, in Ihren API-Dokumenten angegeben zu werden – Standard- oder Base64-URL-Alphabet, Auffüllung und maximale Größen

Dies funktioniert gut, wenn die Binärressource groß ist oder weniger häufig darauf zugegriffen wird als auf die Metadaten. Ein roher binärer Endpunkt, der nur Bytes zurückgibt und JSON vollständig aufgibt, ist der einfachste Ansatz, entfernt jedoch die Struktur, die JSON bereitstellt. Einige APIs geben komprimierte Binärdaten zurück und kodieren diese Base64, wodurch die Größeneinbußen verringert, aber der Dekomprimierungsaufwand erhöht wird. Die Wahl hängt von der erwarteten Nutzung ab: Kleine Felder sind gut inline, große Felder gehören zu separaten Ressourcen und strukturierte Daten sind trotz der Base64-Kosten eine Aufbewahrung in JSON wert.

Konventionen sind wichtig für die Interoperabilität. APIs, die Binärdaten Base64-kodieren, sollten diese klar dokumentieren und angeben, ob das Alphabet Standard oder URL-sicher ist. Standard Base64 verwendet + und /,, die in JSON-Strings, aber nicht in URLs sicher sind. URL-sicheres Base64 ersetzt sie durch - und _, was für Daten geeignet ist: URIs, aber unnötigerweise in JSON maskiert. In der Dokumentation sollte angegeben werden, ob Auffüllungen enthalten oder weggelassen sind, da es sich bei beiden um gültiges Base64 handelt, ein Client, der Auffüllungen erwartet und nicht aufgefüllte Daten empfängt, jedoch stillschweigend fehlschlägt oder Müll produziert.

Was dies nicht abdeckt – Protobuf, CBOR und andere binäre Serialisierungsformate

Bei sehr großen oder häufig aktualisierten Feldern ist die Dokumentation eines separaten binären Endpunkts unerlässlich, damit Clients nicht versuchen, Kilobytes unnötiger Daten abzurufen. Das Debuggen von APIs mit Base64-Feldern ist mit dem richtigen Tool unkompliziert. Mit dem Base64-Encoder und -Decoder können Sie jedes Feld lokal im Browser dekodieren, ohne es zu speichern oder irgendwohin zu senden. Kopieren Sie ein Base64-Feld aus einer JSON-Antwort, fügen Sie es in den Decoder ein und klicken Sie auf „Dekodieren“. Bei textähnlichen Daten (z. B. JSON innerhalb von Base64) erscheint die dekodierte Ausgabe sofort.

Für Binärdaten wie Bilder zeigt die Hex-Ansicht die Bytes an. Dies hilft zu bestätigen, dass der Server das gesendet hat, was Sie erwartet haben, und dass Ihr Client-Decoder ordnungsgemäß funktioniert. Wenn ein Feld in unerwartete Daten dekodiert wird, liegt das Problem in der Serverkodierung oder in der Art und Weise, wie Sie das Feld kopieren. Wenn es in einen Teilblob dekodiert, wurde das Feld möglicherweise abgeschnitten oder die Base64-Länge ist möglicherweise falsch. Die lokale Dekodierung beschleunigt das Debuggen im Vergleich zum Schreiben des Felds in eine Datei und dem Öffnen externer Tools.

Fazit: Base64 in JSON ist ein Kompromiss, also dokumentieren Sie ihn – wie der Base64-Encoder und -Decoder Ihnen hilft, codierte Felder lokal zu prüfen und zu verifizieren

Der praktische Ansatz für Base64 in APIs ist Bewusstsein statt Vermeidung. Base64 ist die Standardmethode zum Übertragen von Binärdaten in JSON und es funktioniert. Beachten Sie, dass jedes Base64-Feld ein Drittel mehr Größe und ein paar Millisekunden CPU-Zeit pro Anfrage-Antwort-Zyklus kostet. Für kleine kritische Metadaten wie Authentifizierungstoken (wobei JWT selbst Base64-codiert ist) sind die Kosten vernachlässigbar. Fragen Sie bei großen Anhängen, ob die Binärdatei in derselben Antwort oder als separate Ressource übertragen werden soll.

Dokumentieren Sie das Codierungsschema und die maximalen Größen in Ihrer API-Spezifikation. Verwenden Sie bei der Überprüfung von Antworten den Base64-Encoder und -Decoder, um zu überprüfen, ob die Felder korrekt decodiert werden, und um zu verstehen, was der Server tatsächlich gesendet hat. Diese Disziplin sorgt dafür, dass der Kompromiss sichtbar ist und die Entscheidung bewusst und nicht zufällig getroffen wird.