In our codebase, we have a class called OptionalOrUndefined
public class OptionalOrUndefined<T> {
@Getter private final boolean defined;
private final T value;
public static <T> OptionalOrUndefined<T> optionalOfNullable(T value) {
return new OptionalOrUndefined<>(true, value);
}
public static <T> OptionalOrUndefined<T> undefined() {
return new OptionalOrUndefined<>(false, null);
}
public static <T> OptionalOrUndefined<T> undefinedIfNull(OptionalOrUndefined<T> property) {
return property != null ? property : OptionalOrUndefined.undefined();
}
public <U> OptionalOrUndefined<U> map(Function<? super T, ? extends U> mapper) {
Objects.requireNonNull(mapper);
return isDefined()
? OptionalOrUndefined.optionalOfNullable(value != null ? mapper.apply(value) : null)
: OptionalOrUndefined.undefined();
}
}
I would like to create a patch endpoint which maps to fields with this type:
public record PatchVehicleRequest(
Long vehicleId,
@OptionalOrUndefinedMapper OptionalOrUndefined<String> vin,
@OptionalOrUndefinedMapper OptionalOrUndefined<String> color,
@OptionalOrUndefinedMapper OptionalOrUndefined<String> amountDoors) {
}
For this I had created a Deserializer and a custom annotation:
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotationsInside
@JsonDeserialize(using = OptionalOrUndefinedDeserializer.class)
@JsonInclude(JsonInclude.Include.NON_NULL)
public @interface OptionalOrUndefinedMapper {
}
public class OptionalOrUndefinedDeserializer extends JsonDeserializer<OptionalOrUndefined<?>> {
@Override
public OptionalOrUndefined<?> deserialize(JsonParser jsonParser, DeserializationContext context) throws IOException {
JsonNode node = jsonParser.getCodec().readTree(jsonParser);
if ( node.isMissingNode()) {
return OptionalOrUndefined.undefined();
} else if (node.isNull()) {
return OptionalOrUndefined.optionalOfNullable(null);
} else {
String value = node.asText();
return OptionalOrUndefined.optionalOfNullable(value);
}
}
}
But when calling the patch endpoint with following json:
{
"vehicleId":5,
"vin": null,
"color": "test"
}
What happens is that vin
and amountDoors
stays null, instead of becoming a defined empty optional and an undefined object. Color is set to defined optional with value “test” as expected.