Serialisieren Sie datetime und Decimal in JSON mit einem expliziten Python-Vertrag
Verwenden Sie ISO-Zeitstempel-Strings und Dezimal-Strings, dann rekonstruieren Sie die Felder explizit. Ein vollständiges Beispiel der Standardbibliothek zeigt Zeitzonenprüfungen, Genauigkeit und vorhersehbare Fehler.
Auf dieser Seite
Die kurze Antwort
Verwenden Sie eine benutzerdefinierte json.dumps-Standardfunktion, um zeitbewusste Datumsangaben als ISO-Strings und endliche Dezimalwerte als Dezimal-Strings zu codieren. Stellen Sie die benannten Felder explizit nach json.loads wieder her. Dies bewahrt die Beispielzeitstempel-Verschiebung und die Darstellung von Decimal(12,30) ohne Behandlung beliebiger Strings als typisierte Objekte. Das Beispiel validiert die beiden Felder, nicht ein vollständiges Geschäftsschema.
Definieren Sie den Austauschvertrag
JSON hat keinen dedizierten Datums- oder Dezimal-Typ. Definieren Sie, wie jeder zusätzliche Python-Typ die Grenze überquert, bevor Sie einen Codierer wählen. Diese Anleitung verwendet einen bestimmten Vertrag: der Zeitstempel ist ein ISO-String, der eine nutzbare UTC-Verschiebung enthält, und der Betrag ist ein endlicher Dezimal-String. Ein Verbraucher rekonstruiert die beiden Felder explizit. Weder ein datumsähnliches String noch ein zahlenähnliches String ist automatisch ein typisiertes Python-Objekt.
Das Beispiel verwendet die Python-Standardbibliothek und zielt auf Python 3.11 oder neuer ab. Sein konstruierter Eingang ist ein UTC-Datumsangabe für den 29. September 2026, um 12:00 Uhr und Decimal(12,30). Der Zweck ist es, diese Darstellung zu bewahren, nicht um beliebige Objekte zu serialisieren oder ein vollständiges Zahlungsschema zu definieren.
Serialisieren Sie zeitbewusste Daten
Ein Datumsangabe ist nur dann zeitbewusst, wenn tzinfo nicht None ist und utcoffset() ebenfalls einen Wert zurückgibt. Der Codierer überprüft beide Bedingungen, bevor er isoformat() aufruft. Das Ablehnen naiver Werte vermeidet das stille Zuweisen der lokalen Zeitzone der Maschine oder das Verwechseln einer lokalen Wanduhr-Lesezeit mit einem bestimmten Moment.
Der Beispiel-UTC-Zeitstempel wird zu 2026-09-29T12:00:00+00:00. Ein Versatz-String identifiziert den Versatz zu diesem Zeitpunkt, bewahrt jedoch keinen IANA-Zonen-Namen oder dessen zukünftige Sommerzeit-Regeln. Normalisieren Sie nur auf UTC, wenn dies Ihrem Vertrag entspricht; dieses Beispiel interpretiert keinen naiven Zeitstempel als UTC neu.
Bewahren Sie Decimal als String
Decimal(12,30) behält seine Dezimalstellen und die nachstehende Null. Die Umwandlung in eine binäre Gleitkommazahl vor der Serialisierung ändert die Darstellung und kann Rundungsfehler einführen. Der Codierer gibt stattdessen str(obj) zurück, wodurch der Betrag ein JSON-String wird. Der Empfänger kann ihn mit Decimal rekonstruieren, ohne ihn zuerst durch eine Gleitkommazahl zu leiten.
Das Beispiel lehnt nicht endliche Decimal-Werte ab. Eine Dezimalzahl gibt jedoch keine Währung, einen maximalen Betrag oder zulässige Dezimalstellen an. Dies sind separate Geschäftsbeschränkungen. JSON unterstützt auch native numerische Werte; die Wahl eines Dezimal-Strings hier ist ein expliziter Interoperabilitätsvertrag, keine Behauptung, dass JSON keine Zahlen enthalten kann.
Erstellen Sie einen expliziten Codierer
Das vollständige Beispiel enthält Importe, Codierung, Rekonstruktion und Beispiel-Eingaben. json.dumps ruft default nur für Objekte auf, die der normale Codierer nicht verarbeiten kann. Die benutzerdefinierte Funktion behandelt Datumsangaben und Decimal; andere nicht unterstützte benutzerdefinierte Objekte werfen TypeError. Normale Strings, ganze Zahlen, Listen und Wörterbücher bleiben die Verantwortung des eingebauten Codierers.
allow_nan=False lehnt native float NaN und Unendlich während der Serialisierung ab. Die Überprüfung des endlichen Decimals ist separat, weil Decimal von der benutzerdefinierten Funktion behandelt wird. Es wird keine Drittanbieter-Abhängigkeit benötigt. Das folgende Beispiel ist illustrativ; seine erwartete Ausgabe wird erklärt, anstatt als ausgeführter Test präsentiert zu werden.
import json
from datetime import datetime, timezone
from decimal import Decimal, InvalidOperation
def contract_encoder(obj):
if isinstance(obj, datetime):
if obj.tzinfo is None or obj.utcoffset() is None:
raise TypeError("Timezone-aware datetime required")
return obj.isoformat()
if isinstance(obj, Decimal):
if not obj.is_finite():
raise TypeError("Finite Decimal required")
return str(obj)
raise TypeError("Unsupported custom value")
def reconstruct_data(data):
if not isinstance(data, dict):
raise ValueError("Object required")
if not all(isinstance(data.get(key), str) for key in ("timestamp", "amount")):
raise ValueError("timestamp and amount must be strings")
timestamp = datetime.fromisoformat(data["timestamp"])
if timestamp.tzinfo is None or timestamp.utcoffset() is None:
raise ValueError("Timezone-aware timestamp required")
amount = Decimal(data["amount"])
if not amount.is_finite():
raise ValueError("Finite amount required")
return {"timestamp": timestamp, "amount": amount}
data = {"timestamp": datetime(2026, 9, 29, 12, 0, tzinfo=timezone.utc),
"amount": Decimal("12.30")}
encoded = json.dumps(data, default=contract_encoder, allow_nan=False)
print(encoded)
restored = reconstruct_data(json.loads(encoded))
print(restored["timestamp"].isoformat(), str(restored["amount"]))
Lesen Sie die Felder zurück
json.loads stellt Wörterbücher, Listen und Grundwerte wieder her. Es lässt Zeitstempel und Betrag als Strings. reconstruct_data überprüft, dass der oberste Wert ein Objekt ist und beide erforderlichen Felder Strings sind, ruft dann datetime.fromisoformat und Decimal explizit auf. Es erfordert auch einen nutzbaren Zeitstempel-Versatz und einen endlichen Betrag.
Konvertieren Sie nicht jeden String, der wie ein Datum oder eine Zahl aussieht. Die Bedeutung des Feldes ergibt sich aus dem vereinbarten Schema. Diese Prüfungen decken zwei Felder ab; sie lehnen nicht jede zusätzliche Eigenschaft ab oder erzwingen Anwendungsgrößen-, Währungs-, Autorisierungs- und Bereichsrichtlinien. Anwendungen sollten die Beschränkungen hinzufügen, die ihre Grenze tatsächlich erfordern.
Untersuchen Sie die illustrativen Ausgaben
Für die konstruierte Eingabe ist die erste gedruckte Zeile {Zeitstempel: 2026-09-29T12:00:00+00:00, Betrag: 12,30}. Die zweite ist 2026-09-29T12:00:00+00:00 12,30. Dies sind illustrativ erwartete Zeilen, die aus dem Code abgeleitet wurden, kein Bericht eines ausgeführten Tests. Der Betrag ist in JSON in Anführungszeichen gesetzt und endet nach der Rekonstruktion immer noch mit einer Null.
Vergleichen Sie die typisierten rekonstruierten Werte mit dem beabsichtigten Vertrag. Ein decodierter String allein beweist nicht, dass eine Rekonstruktion stattgefunden hat. Die Änderung des Ausgabevertrags in ein getaggtes Objekt oder einen numerischen JSON-Betrag würde entsprechende Änderungen bei den Verbrauchern erfordern; mischen Sie Formate nicht versehentlich.
Behandeln Sie ungültige Werte vorhersehbar
Die Codierung eines naiven Zeitstempels, eines nicht endlichen Decimals oder eines anderen nicht unterstützten benutzerdefinierten Objekts löst den expliziten TypeError im Beispiel aus. Fehlformatiertes JSON kann json.JSONDecodeError auslösen. Ungültiger ISO-Zeitstempel-Text löst ValueError aus. Ungültiger Dezimaltext löst normalerweise decimal.InvalidOperation unter dem Standard-Dezimal-Kontext aus, nicht ValueError.
Die Rekonstruktionsprüfungen lösen ValueError für einen falschen obersten Typ, fehlende oder nicht-String-Felder, einen Zeitstempel ohne Versatz und einen nicht endlichen Betrag aus. Dezimalfallen sind konfigurierbar, sodass das Ausnahmeverhalten vom Kontext abhängen kann. Behandeln Sie die relevanten Fehler an der Anwendungsgrenze, ohne universelle Stack-Trace-Wortwahl oder Protokollierung sensibler Nutzlasten zu garantieren.
Wissen Sie, was JSON nicht bewahren kann
JSON erinnert sich nicht daran, ob ein Array als Tupel oder Liste oder ob ein String als Datumsangabe oder Decimal entstanden ist. Explizite Rekonstruktion ist daher Teil dieses Vertrags. Die Rückgabe eines Strings von default ist hier ausreichend; benutzerdefinierte JSONEncoder- und JSONDecoder-Unterklassen sind optionale Alternativen, keine zwingenden Anforderungen.
object_hook wird für decodierte JSON-Objekte aufgerufen, nicht für jeden primitiven String. Es kann ein explizit entworfenes getaggtes Objektformat unterstützen, aber das wäre ein anderer Vertrag als die beiden benannten String-Felder, die hier verwendet werden. Die Bewahrung der Darstellung ist nützlich, stellt jedoch keine Geschäftsgültigkeit, benannte Zeitzonenregeln oder sicheren Ressourcenverbrauch her.
Was Sie prüfen sollten
- Verwenden Sie Python 3.11 oder neuer für dieses Beispiel.
- Überprüfen Sie tzinfo und utcoffset() bevor Sie eine Datumsangabe akzeptieren.
- Behalten Sie den Dezimalbetrag als JSON-String bei, anstatt ihn in eine Gleitkommazahl umzuwandeln.
- Erfordern Sie ein Objekt mit String-Zeitstempel- und Betragsfeldern während der Rekonstruktion.
- Lehnen Sie nicht endliche Beträge und zeitstempel ohne Versatz ab.
- Unterscheiden Sie ValueError, json.JSONDecodeError und decimal.InvalidOperation an der Eingabegrenze.
Geltungsbereich
Dies ist eine konstruierte Illustration der Standardbibliothek, kein ausgeführter Test. Versatz-Strings bewahren keine benannten Zeitzonenregeln. Dezimal-Strings bewahren die Darstellung, nicht die Währung oder Geschäftsgültigkeit. Das Beispiel validiert zwei Felder und Endlichkeit; Anwendungen benötigen weiterhin Bereichs-, Größen- und Autorisierungsrichtlinien.