Is there a way to validate some JSON fields while keeping it general for non-mapped fields?

Viewed 45

I'm working with legacy a "Meta" field in a Model.

This field has not been documented or validated in the past, so it is being used everywhere without much of a structure.

Now I want to add a new field, and validate it, while not breaking older usages.

Think of:

from sqlalchemy.dialects.postgresql import JSONB
from sqlalchemy import BaseModel

class Object(BaseModel):
   #...
   old_field = JSONB('old_field')

   def set_old_field(self, inp: str):
      self.old_field = inp

Now I want to add a foo field to the old_field, and I have a structure like such:

{"foo": 
  {
    "settings":
      {"color":"blue",
       "position":"top"},
    "active":false
  }
}

How could I do it (maybe using pydantic), while allowing any other key but foo to pass without validation?

1 Answers

I found a solution in pydantic docs based on using get_validators:

Basically, we have to rewrite the validator to be able to check for specific keys and ignore the others, like so:

from pydantic import BaseModel, ValidationError
import json

class SpecialField(BaseModel):
   foo: str
   bar: int

class FlexibleJson(str):

   validated_fields = {'special_field':SpecialField}

   @classmethod
   def __get_validators__(cls):
      yield cls.validate

   @classmethod
   def validate(cls, v):
      try:
         validated = json.loads(v)
         for field in validated_fields:
             # you can do try/catching here to sum up all the validation errors
             if field in validated:
                #replace dict with a validated field
                validated[field]=validated_fields[field](**validated[field])
      except ValidationError as e:
         raise
      except Exception as excp:
         raise ValidationError(error=[excp], model=cls.__name__)
      return cls(validated)


class ObjModel(BaseModel):
   metadata: FlexibleJson

#works:
obj1 = ObjModel(**{"metadata":'{"special_field":{"foo":"zazz","bar":7}, "baz":"whatever"}'})

#works - no special_field:
obj2 = ObjModel(**{"metadata":'{"baz":"whatever"}'})

#fails - wrong type
obj3 = ObjModel(**{"metadata":'{"special_field":{"foo":"zazz","bar":"zoooo"}, "baz":"whatever"}'})

#fails - missing
obj4 = ObjModel(**{"metadata":'"special_field":{"foo":"zazz"}, "baz":"whatever"}'})
Related