Pydantic: Export fields of the same type with different encodings
Question:
Suppose I have a model with various timedelta
fields, but I want them each expressed in a different format when exported to JSON. I know I can specify the JSON encoder for timedelta
, but that applies to all fields of that type. Is there a way to specify the JSON encoder for a given field? or is there another way to accomplish this?
Here’s a bit of code as an example:
from datetime import datetime, timedelta
from pydantic import BaseModel
from pydantic.json import timedelta_isoformat
class Example(BaseModel):
delta_iso: timedelta # export using timedelta_isoformat
delta_seconds_int: timedelta # export as int in seconds
delta_seconds_float: timedelta # export as float in seconds
delta_milliseconds_int: timedelta # export as int in milliseconds
class Config:
# This won't work because it applies to all fields above
json_encoders = {
timedelta: timedelta_isoformat,
}
Answers:
You can provide custom json_dumps
function. Like so:
import json
from datetime import datetime, timedelta
from pydantic import BaseModel
from pydantic.json import timedelta_isoformat
def custom_dumps(v, *, default):
for key, value in v.items():
if key == "delta_iso":
v[key] = timedelta_isoformat(value)
elif key == "delta_seconds_int":
v[key] = int(value.total_seconds())
elif key == "delta_seconds_float":
v[key] = value.total_seconds()
elif key == "delta_milliseconds_int":
v[key] = value.total_seconds() * 1000
return json.dumps(v, default=default)
class Example(BaseModel):
delta_iso: timedelta # export using timedelta_isoformat
delta_seconds_int: timedelta # export as int in seconds
delta_seconds_float: timedelta # export as float in seconds
delta_milliseconds_int: timedelta # export as int in milliseconds
class Config:
json_dumps = custom_dumps
diff = datetime.timedelta(milliseconds=5000)
print(Example(delta_iso=diff, delta_seconds_int=diff, delta_seconds_float=diff, delta_milliseconds_int=diff).json())
{"delta_iso": "P0DT0H0M5.000000S", "delta_seconds_int": 5, "delta_seconds_float": 5.0, "delta_milliseconds_int": 5000.0}
Another option, model_dump(mode="...")
(which currently includes json
and python
modes) is eventually making its way into Pydantic, as discussed by the library’s author in Pydantic’s issue #1409:
This is actually already implemented in pydantic-core and works on main now.
Example:
from datetime import datetime
from pydantic import BaseModel
class MyModel(BaseModel):
things: set[str]
when: datetime
m = MyModel(things={'a', 'b'}, when=datetime.now())
print(m.model_dump(mode='json'))
"""
{
'things': ['b', 'a'],
'when': '2023-02-09T10:54:50.11279',
}
"""
As of today (March 9, 2023) it has not been packaged as a release, but it’s in the main
branch as of f4e8f71ab23a
and you can always try it out in the meantime by pinning your pydantic
package to the appropriate main
revision on GitHub.
Suppose I have a model with various timedelta
fields, but I want them each expressed in a different format when exported to JSON. I know I can specify the JSON encoder for timedelta
, but that applies to all fields of that type. Is there a way to specify the JSON encoder for a given field? or is there another way to accomplish this?
Here’s a bit of code as an example:
from datetime import datetime, timedelta
from pydantic import BaseModel
from pydantic.json import timedelta_isoformat
class Example(BaseModel):
delta_iso: timedelta # export using timedelta_isoformat
delta_seconds_int: timedelta # export as int in seconds
delta_seconds_float: timedelta # export as float in seconds
delta_milliseconds_int: timedelta # export as int in milliseconds
class Config:
# This won't work because it applies to all fields above
json_encoders = {
timedelta: timedelta_isoformat,
}
You can provide custom json_dumps
function. Like so:
import json
from datetime import datetime, timedelta
from pydantic import BaseModel
from pydantic.json import timedelta_isoformat
def custom_dumps(v, *, default):
for key, value in v.items():
if key == "delta_iso":
v[key] = timedelta_isoformat(value)
elif key == "delta_seconds_int":
v[key] = int(value.total_seconds())
elif key == "delta_seconds_float":
v[key] = value.total_seconds()
elif key == "delta_milliseconds_int":
v[key] = value.total_seconds() * 1000
return json.dumps(v, default=default)
class Example(BaseModel):
delta_iso: timedelta # export using timedelta_isoformat
delta_seconds_int: timedelta # export as int in seconds
delta_seconds_float: timedelta # export as float in seconds
delta_milliseconds_int: timedelta # export as int in milliseconds
class Config:
json_dumps = custom_dumps
diff = datetime.timedelta(milliseconds=5000)
print(Example(delta_iso=diff, delta_seconds_int=diff, delta_seconds_float=diff, delta_milliseconds_int=diff).json())
{"delta_iso": "P0DT0H0M5.000000S", "delta_seconds_int": 5, "delta_seconds_float": 5.0, "delta_milliseconds_int": 5000.0}
Another option, model_dump(mode="...")
(which currently includes json
and python
modes) is eventually making its way into Pydantic, as discussed by the library’s author in Pydantic’s issue #1409:
This is actually already implemented in pydantic-core and works on main now.
Example:
from datetime import datetime from pydantic import BaseModel class MyModel(BaseModel): things: set[str] when: datetime m = MyModel(things={'a', 'b'}, when=datetime.now()) print(m.model_dump(mode='json')) """ { 'things': ['b', 'a'], 'when': '2023-02-09T10:54:50.11279', } """
As of today (March 9, 2023) it has not been packaged as a release, but it’s in the main
branch as of f4e8f71ab23a
and you can always try it out in the meantime by pinning your pydantic
package to the appropriate main
revision on GitHub.