2

I have an input structure like this

d = {
    "==": [
        "%var%",
        "10"
    ]
}

I'd like to decompose it to the Model like this:

class Model:
  op_name: str # ==
  lvalue: str # %var%
  rvalue: str # 10

Is it possible with Pydantic?

The best I reach so far is

class Model(BaseModel):
    class Expr(NamedTuple):
        lvalue: str
        rvalue: str

    __root__: Dict[str, Expr]

It can be created from the dict and serialized to json

m = Model.parse_obj(d)

print(m)
print(m.json())
-------
__root__={'==': Expr(lvalue='%var%', rvalue='10')}
{"==": ["%var%", "10"]}

But there is no op_name, so it's hard to deal with this model. Is it possible to decompose it into 3 fields with transparent serialization/deserialization to/from json?

Anton Ovsyannikov
  • 1,010
  • 1
  • 12
  • 30

1 Answers1

2

I think you can use a @root_validator to decompose your input dict into the corresponding fields:

class Model(BaseModel):
  op_name: str
  lvalue: str
  rvalue: str

  @root_validator(pre=True)
  def _decompose(cls, values: Dict[str, Any]) -> Dict[str, Any]:
    new_values = {}
    op_name = next(iter(values.keys()), None)
    if op_name is not None:
      new_values["op_name"] = op_name
      list_values = values.get(op_name)
      if isinstance(list_values, list) and len(list_values) == 2:
        new_values["lvalue"] = list_values[0]
        new_values["rvalue"] = list_values[1]
    return new_values

It is important to use pre=True to decompose the input before any other validation is applied.

See an example in Google Colab.

Hernán Alarcón
  • 3,494
  • 14
  • 16
  • Thanks, it solves the problem of deserialization. Unfortunately things become broken when we try to serialize it back to json `print(Model.parse_obj({"==":[1,2]}).json())` `{"op_name": "==", "lvalue": "1", "rvalue": "2"}` Looks like we also need some custom serializer... Surprisingly difficult actually :( – Anton Ovsyannikov Nov 10 '21 at 17:14
  • @AntonOvsyannikov, are `lvalue` and `rvalue` not `str` but `int`? In that case you just have to change the type hint to `int`. If they can be both then you can use `Union[int, str]`. – Hernán Alarcón Nov 11 '21 at 03:40
  • Yes, but I expect model will turn to original object after serialization: `{"==":[1, 2]}`, not `{"op_name": "==", "lvalue": "1", "rvalue": "2"}`, suppose custom serializer is needed. – Anton Ovsyannikov Nov 11 '21 at 14:15