Watching a reactive object with watch only reacts when the object itself is replaced. For changes inside it, go deep:
watch(form, (value) => save(value), { deep: true })
Deep watching walks the whole object on every change, so for big objects watch just the thing you care about instead:
watch(() => form.address.city, (city) => lookupZip(city))
The getter version is also what you need when the property might not exist yet, since Vue can't watch a path that is undefined.