I faced same problem so here comes a little hacky solution with class factory (some elegant one with something like foo: JsonVal[T]
is almost impossible to implement due to numerous internal pydantic hacks like how it works with generic type annotations). Unfortunately type inference is not working, but validation and access to parsed value are ok, considering that Fields are always stored/serialized as str.
from abc import ABC
from typing import TypeVar, Generic, Type
from pydantic import Json, parse_obj_as, BaseModel
T = TypeVar('T')
class JsonVal(Generic[T], str, ABC):
@property
def parsed(self) -> T:
return None
def json_val(t: Type) -> Type[JsonVal[T]]:
class _JsonVal(JsonVal, str):
_t: Type
@classmethod
def __get_validators__(cls):
yield cls.validate
@classmethod
def validate(cls, v):
parse_obj_as(Json[cls._t], v)
return cls(v)
@property
def parsed(self):
return parse_obj_as(Json[self._t], self)
_JsonVal._t = t
return _JsonVal
class Bar(BaseModel):
bar: str
class Model(BaseModel):
foo: json_val(list[int])
bar: json_val(Bar)
m = Model.parse_obj({"foo": '[1,2,3]', "bar": '{"bar":"baz"}'})
print(m)
print(m.foo, type(m.foo), m.foo.parsed, type(m.foo.parsed))
print(m.bar, type(m.bar), m.bar.parsed, type(m.bar.parsed))
print(m.json())
# foo='[1,2,3]' bar='{"bar":"baz"}'
# f[1,2,3] <class '__main__.json_val.<locals>._JsonVal'> [1, 2, 3] <class 'list'>
# f{"bar":"baz"} <class '__main__.json_val.<locals>._JsonVal'> bar='baz' <class '__main__.Bar'>
# f{"foo": "[1,2,3]", "bar": "{\"bar\":\"baz\"}"}