#python #json #pydantic
Вопрос:
При использовании (хэшируемых) объектов в качестве ключей словаря вызов .json()
завершается неудачно, поскольку, хотя значения закодированы, ключи не:
from pydantic import BaseModel
from typing import dict
from datetime import datetime
class Foo(BaseModel):
date: datetime
sdict: Dict[datetime, str]
class Config:
json_encoders = {
datetime: repr
}
foo = Foo(date=datetime.now(), sdict={datetime.now(): 'now'})
foo
# Foo(date=datetime.datetime(2021, 9, 3, 12, 9, 55, 36105), sdict={datetime.datetime(2021, 9, 3, 12, 9, 55, 36114): 'now'})
foo.json()
TypeError: keys must be a string
# to prove the other way around works:
class Foo(BaseModel):
date: datetime
sdict: Dict[str, datetime]
class Config:
json_encoders = {
datetime: repr
}
foo = Foo(date=datetime.now(), sdict={'now': datetime.now()})
foo.json()
# '{"date": "datetime.datetime(2021, 9, 3, 12, 13, 30, 606880)", "sdict": {"now": "datetime.datetime(2021, 9, 3, 12, 13, 30, 606884)"}}'
Это связано с тем, что default=
параметр, в json.dumps()
котором в конечном итоге используется для сброса, не кодирует ключи словаря. Определение класса кодировщика JSON действительно работает, но для меня это не работает по другим причинам.
Я видел TypedDict
в pydantic, но, похоже, это не решает проблему. На самом деле, я не уверен , в чем смысл TypedDict
, так как AFAICS вам нужно определить каждый ключ в dict, что делает его аналогом статического объекта?
Мой вариант использования заключается в том, что мне нужно представить следующую идею:
{
"report": {
"warehouses": {
warehouse.id: {
"name": warehouse.name,
"address": warehouse.address,
}
for warehouse in warehouses
}
}
и warehouse.id
является Identifier
объектом, который может быть преобразован в различные форматы по требованию, и который кодировщик json преобразует в строку.
Кто-нибудь знает способ, отличный от словаря, где я могу добавлять произвольные ключи к объекту таким образом, чтобы это зависело от кодировщика json, или каким-либо другим способом сериализации?
Ответ №1:
Одним из вариантов решения проблемы является использование пользовательской json_dumps
функции для модели pydantic, внутри которой для выполнения пользовательской сериализации я сделал это, унаследовав от JSONEncoder
.
Например, вот так:
import json
from pydantic import BaseModel
from typing import Dict
from datetime import datetime
class CustomEncoder(json.JSONEncoder):
def _transform(self, v):
res = v
if isinstance(v, datetime):
res = v.isoformat()
# else other variants
return self._encode(res)
def _encode(self, obj):
if isinstance(obj, dict):
return {self._transform(k): self._transform(v) for k, v in obj.items()}
else:
return obj
def encode(self, obj):
return super(CustomEncoder, self).encode(self._encode(obj))
def custom_dumps(values, *, default):
return CustomEncoder().encode(values)
class Foo(BaseModel):
date: datetime
sdict: Dict[datetime, str]
class Config:
json_dumps = custom_dumps
foo = Foo(date=datetime.now(), sdict={datetime.now(): 'now'})
Foo(date=datetime(2021, 9, 3, 12, 9, 55, 36105), sdict={datetime(2021, 9, 3, 12, 9, 55, 36114): 'now'})
print(foo.json())
{"date": "2021-09-07T16:02:51.070159", "sdict": {"2021-09-07T16:02:51.070164": "now"}}