According to my knowledge:
PUT - update object with its whole representation (replace)PATCH - update object with given fiel
As you noted the main problem is that we don't have multiple null-like values to distinguish between explicit and implicit nulls. Since you tagged this question Kotlin I tried to come up with a solution which uses Delegated Properties and Property References. One important constraint is that it works transparently with Jackson which is used by Spring Boot.
The idea is to automatically store the information which fields have been explicitly set to null by using delegated properties.
First define the delegate:
class ExpNull(private val explicitNulls: MutableSet>) {
private var v: T? = null
operator fun getValue(thisRef: R, property: KProperty<*>) = v
operator fun setValue(thisRef: R, property: KProperty<*>, value: T) {
if (value == null) explicitNulls += property
else explicitNulls -= property
v = value
}
}
This acts like a proxy for the property but stores the null properties in the given MutableSet.
Now in your DTO:
class User {
val explicitNulls = mutableSetOf>()
var name: String? by ExpNull(explicitNulls)
}
Usage is something like this:
@Test fun `test with missing field`() {
val json = "{}"
val user = ObjectMapper().readValue(json, User::class.java)
assertTrue(user.name == null)
assertTrue(user.explicitNulls.isEmpty())
}
@Test fun `test with explicit null`() {
val json = "{\"name\": null}"
val user = ObjectMapper().readValue(json, User::class.java)
assertTrue(user.name == null)
assertEquals(user.explicitNulls, setOf(User::name))
}
This works because Jackson explicitly calls user.setName(null) in the second case and omits the call in the first case.
You can of course get a bit more fancy and add some methods to an interface which your DTO should implement.
interface ExpNullable {
val explicitNulls: Set>
fun isExplicitNull(property: KProperty<*>) = property in explicitNulls
}
Which makes the checks a bit nicer with user.isExplicitNull(User::name).