How to get json of model pydantic in validator function

Viewed 48

Imagine I have Model of pydantic like this:

class Input(BaseModel):
    field1: ObjectIdField = ...
    field2: str = ...
    field3: int = ...
    field4: str = ...

    @validator('field4')
    def validate_field4(cls, v, values, **kwargs):
        # print(values) => {'field1': ObjectId('63185d5c721c6ef40eb2462a'), 'field2': 'deleted', 'field3': 1}
        # I want to check this
        # if json.dumps(values) == v :
            # OK
        # else :
            # raise Exception

    return v


   class Config:
      json_encoders = {
          ObjectId: lambda v: str(v),
       }

As you see, I want to get json of model except one field(field4). Is there a good solution for handle this inside validator of model???

1 Answers

What the comments failed to address is that Pydantics .json is an instance method (just like the .dict method) and thus completely useless inside a validator, which is always a class method called before an instance is even initialized.

(Note: You did not provide code or explanation about how your ObjectIdField class works, so I had to make a guess and implement a very simple class to try and simulate the behavior you showed.)

Here is a way to accomplish your validation:

import json
from typing import Any

from pydantic import BaseModel, validator


class ObjectIdField:
    def __init__(self, value: str) -> None:
        self.value = value

    def __repr__(self) -> str:
        return f"ObjectId('{self.value}')"

    def __str__(self) -> str:
        return self.value


def encode_obj_id(obj_id: ObjectIdField) -> str:
    return str(obj_id)


def json_encode(obj: Any) -> str:
    return json.dumps(obj, default=encode_obj_id)


class Input(BaseModel):
    field1: ObjectIdField = ...
    field2: str = ...
    field3: int = ...
    field4: str = ...

    @validator('field4')
    def validate_field4(cls, v: str, values: dict[str, Any]) -> str:
        serialized = json_encode(values)
        assert v == serialized
        return v

    class Config:
        arbitrary_types_allowed = True  # to allow `ObjectIdField`
        json_encoders = {
            ObjectIdField: encode_obj_id,
        }


if __name__ == '__main__':
    data = dict(
        field1=ObjectIdField("abc"),
        field2="foo",
        field3=123,
    )
    data_json = json_encode(data)
    instance = Input(**data, field4=data_json)
    assert data_json == instance.json(exclude={"field4"})

    # instance = Input(**data, field4="invalid")  # this fails validation

The json.dumps function has a default parameter, which takes a function

that gets called for objects that can’t otherwise be serialized.

We can use that to construct our own little helper function json_encode that serializes ObjectIdField types using the encode_obj_id function.

Since field4 is not present in the values dictionary passed to the validator function for that field, we can simply call our json_encode function on that dictionary and compare it to the field4 value v.

NOTE: This only works, if field4 is defined last on the model because fields are validated in the order they are defined. If you had another field after field4 on your model, its value would be missing from the values dictionary by the time the validator was called.

Hope this helps.

Related