The closest thing in Today's Terraform is to have your callers set the attribute value to null and then handle that null somehow inside your module.
One way to do that is to combine the input variable with a local value that normalizes it, like this:
variable "example" {
type = map(object({
a = string
b = string
}))
}
locals {
example = {
for k, v in var.example : k => {
a = coalesce(v.a, "default a")
b = coalesce(v.b, "default b")
}
}
}
The coalesce function can be a good choice if you have a fallback value that isn't null or "", but really you can use any expression/function you like here to handle the null case in whatever way is appropriate for your needs.
You can then use local.example elsewhere in the module to get the normalized value, or var.example to get the raw value as given by the caller.
From a caller's perspective omitting those two attributes would look like this:
example = {
a = null
b = null
}
Terraform v0.14 (which is about to have its first beta release at the time I'm writing this) will include an experimental new feature to allow marking object type attributes as optional in type constraints:
terraform {
experiments = [module_variable_optional_attrs]
}
variable "example" {
type = map(object({
a = optional(string)
b = optional(string)
}))
}
The effect of this new optional(...) annotation is that the caller can omit that attribute when passing in an object value, in which case Terraform will perform the type conversion by inserting the attribute with the value null, rather than returning an error as it would by default.
Combined with the normalization approach I showed above this would then achieve the module interface you were looking for, without the need for callers to explicitly set null in order to omit the attributes:
example = {}
Unless this feature sees some show-stopping feedback during the Terraform 0.14 series it'll likely be stabilized in Terraform 0.15.