I eventually dealt with this issue by using a python DataClass as the basic structure, and applying dicitonaries on top of it using a function like this:
from dataclasses import fields, replace
from typing import TypeVar
import json
T = TypeVar("T")
def safe_replace (instance: T, updates: dict, keepNone = False) -> T:
field_names = {f.name for f in fields(instance)}
valid_kwargs = {k: v for k, v in updates.items() if k in field_names if v is not None or keepNone == True }
invalid_keys = set(updates) - field_names
if invalid_keys:
warnings.warn(f"Ignored invalid field(s): " + ', '.join(invalid_keys), category=UserWarning)
return replace(instance, **valid_kwargs)
...which I use something like this:
@dataclass
class User:
reputation: int = 0
favoriteSite: str = "stackOverflow"
me = User(reputation=10)
updated = safe_replace(me, {'reputation':-8, 'favoriteSite':'claude.AI'})
This works well in my use case, because the dataclass gives type safety and defaults, and also decent autocomplete behaviour, and dictionaries can be merged on top. (Also it's possible to add custom handlers for updates and so on).
Not quite the answer to the question I asked, which viewed the base object as a dictionary, but when I asked it, I clearly didn't know quite what I wanted.