# Detecting Absent Properties in Java REST APIs with Jackson, Spring, and Custom Deserializers

In modern software development, ensuring backward compatibility with older clients while introducing new features can be challenging. One such challenge is detecting whether a specific property was sent through a REST API request. By default, Jackson deserializes absent properties to `null`, which complicates the process of identifying whether a property was intentionally set to `null` or was absent in the request altogether. This article will walk you through a solution using a custom Jackson deserializer to handle this scenario.

## The Problem

Using Java, Spring, and Jackson, I needed to identify whether a specific property was sent through my REST API to maintain compatibility with older clients. If the property was not sent, I must detect that the property was absent in the request and ignore it. However, Jackson's default behavior deserializes absent properties to `null`, making it difficult to distinguish between an absent property and one explicitly set to `null`.

## The Solution: Custom Jackson Deserializer

To solve this problem, we can use a custom Jackson deserializer that can handle three states for a property: present, absent, and explicitly set to `null`. This is achieved by creating an `OptionalProperty` wrapper and a custom deserializer for this wrapper.

### Implementing the `OptionalProperty` Wrapper

The `OptionalProperty` class represents a property that can be present with a value, absent, or present with a `null` value. This class provides methods to check the presence of a value and handle each state appropriately.

```java
import java.io.Serializable;
import java.util.function.Consumer;

/**
 * This class represents an optional property.
 * <p>
 * The difference between this and {@code java.util.Optional} is that this class represents three states:
 *
 * <ul>
 * <li>The value is present.</li>
 * <li>The value is absent.</li>
 * <li>The value is present but it is null.</li>
 * </ul>
 * <p>
 * It is a container object which may or may not contain a non-null value.
 * <p>
 * If a value is present, {@code isPresent()} will return {@code true} and {@code get()} will return the value.
 * <p>
 * Additional methods that depend on the presence or absence of a contained value are provided, such as {@code ifPresent()}.<br>
 *
 * @param <T> the type of the value
 */
public class OptionalProperty<T extends Serializable> implements Serializable {

    private boolean isPresent;
    private T value;

    private OptionalProperty(boolean isPresent, T value) {
        this.isPresent = isPresent;
        this.value = value;
    }

    public static <T extends Serializable> OptionalProperty<T> of(T value) {
        return new OptionalProperty<>(true, value);
    }

    public static <T extends Serializable> OptionalProperty<T> absent() {
        return new OptionalProperty<>(false, null);
    }

    public T get() {
        if (!isPresent) {
            throw new IllegalStateException("No value present");
        }
        return value;
    }

    public boolean isAbsent() {
        return !isPresent;
    }

    public boolean isPresent() {
        return isPresent;
    }

    public OptionalProperty<T> ifPresent(Consumer<? super T> consumer) {
        if (isPresent) {
            consumer.accept(value);
        }
        return this;
    }

    public OptionalProperty<T> ifPresentAndNotNull(Consumer<? super T> consumer) {
        if (isPresent && value != null) {
            consumer.accept(value);
        }
        return this;
    }

    public OptionalProperty<T> ifAbsent(Runnable runnable) {
        if (!isPresent) {
            runnable.run();
        }
        return this;
    }

    public T orElse(T other) {
        return isPresent ? value : other;
    }

    public T orElseNull() {
        return orElse(null);
    }
}
```

### Creating the Custom Deserializer

Next, we create a custom deserializer for the `OptionalProperty` class. This deserializer will handle the three states: present with value, absent, and present with a `null` value.

```java
import com.fasterxml.jackson.core.JacksonException;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.deser.ContextualDeserializer;

import java.io.IOException;
import java.io.Serializable;

/**
 * This class is a custom deserializer for {@link OptionalProperty} objects.
 *
 * @param <T> the type of the value contained in the {@link OptionalProperty}
 */
public class JsonOptionalPropertyDeserializer<T extends Serializable> extends JsonDeserializer<OptionalProperty<T>> implements ContextualDeserializer {

    private Class<T> valueClass;

    @Override
    public OptionalProperty<T> getNullValue(DeserializationContext ctxt) {
        return OptionalProperty.of(null);
    }

    @Override
    public Object getAbsentValue(DeserializationContext ctxt) {
        return OptionalProperty.absent();
    }

    @Override
    public OptionalProperty<T> deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
        T value = jsonParser.readValueAs(valueClass);
        return OptionalProperty.of(value);
    }

    @Override
    public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property) throws JsonMappingException {
        valueClass = (Class<T>) property.getType().getBindings().getBoundType(0).getRawClass();
        return this;
    }
}
```

### Explanation of the Custom Deserializer Code

The custom deserializer `JsonOptionalPropertyDeserializer` is designed to handle the deserialization of the `OptionalProperty` class, which can represent three states: present with a value, absent, and present with a `null` value. Here's a breakdown of how the deserializer works:

**Class Declaration and Generics**:

```java
public class JsonOptionalPropertyDeserializer<T extends Serializable> extends JsonDeserializer<OptionalProperty<T>> implements ContextualDeserializer {
```

**Handling Null Values**:

```java
@Override
public OptionalProperty<T> getNullValue(DeserializationContext ctxt) {
    return OptionalProperty.of(null);
}
```

This method is called when the JSON value is explicitly `null`. It returns an `OptionalProperty` instance with a `null` value.

**Handling Absent Values**:

```java
@Override
public Object getAbsentValue(DeserializationContext ctxt) {
    return OptionalProperty.absent();
}
```

This method is called when the JSON value is absent. It returns an absent `OptionalProperty` instance.

**Deserializing JSON Values**:

```java
@Override
public OptionalProperty<T> deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
    T value = jsonParser.readValueAs(valueClass);
    return OptionalProperty.of(value);
}
```

This method is called to deserialize a JSON value into an `OptionalProperty` instance. It reads the JSON value as an object of type `T` and returns an `OptionalProperty` containing the deserialized object.

**Creating Contextual Deserializer**:

```java
@Override
public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property) throws JsonMappingException {
    valueClass = (Class<T>) property.getType().getBindings().getBoundType(0).getRawClass();
    return this;
}
```

This method is called to create a contextual deserializer. It retrieves the class of the generic type `T` from the property's type and stores it in the `valueClass` field for later use in deserialization.

By implementing these methods, the `JsonOptionalPropertyDeserializer` ensures that the `OptionalProperty` class can correctly handle the three states of a property during deserialization: present with a value, absent, and present with a `null` value. This allows for more precise control over the handling of optional properties in your Java REST API.

### Using the Custom Deserializer

Finally, we use the custom deserializer in a DTO class to handle the `OptionalProperty`.

```java
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;

class MyDTO {
    
    @JsonDeserialize(using = JsonOptionalPropertyDeserializer.class)
    private OptionalProperty<Long> identifier;

    public OptionalProperty<Long> getIdentifier() {
        return identifier;
    }

    public void setIdentifier(OptionalProperty<Long> identifier) {
        this.identifier = identifier;
    }
}
```

### Complete Controller Class with Business Logic

Here is a comprehensive controller class that includes the business logic for processing `MyDTO` using the custom deserializer. This controller will handle incoming POST requests, process the `identifier` property, and output the results based on its state.

```java
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;

@RestController
@RequestMapping("/api")
public class MyController {

    @PostMapping("/process")
    public void processMyDTO(@RequestBody MyDTO myDTO) {
        // Check if identifier is present
        if (myDTO.getIdentifier().isPresent()) {
            // Check if the identifier is not null
            myDTO.getIdentifier().ifPresentAndNotNull(value -> {
                // Business logic for when the identifier is present and not null
                System.out.println("Identifier is present and not null: " + value);
                // Additional processing logic here
            });

            // If the identifier is present but null
            if (myDTO.getIdentifier().orElseNull() == null) {
                System.out.println("Identifier is present but null");
                // Business logic for when the identifier is explicitly set to null
                // Additional processing logic here
            }
        } else {
            // If the identifier is absent
            System.out.println("Identifier is absent");
            // Business logic for when the identifier is absent
            // Additional processing logic here
        }
    }
}
```

## Conclusion

By using a custom Jackson deserializer, we can effectively distinguish between absent properties, properties explicitly set to `null`, and properties with a non-null value in our REST API requests. This approach ensures backward compatibility with older clients while providing the flexibility to handle new requirements. The `OptionalProperty` wrapper and custom deserializer provide a robust solution for managing optional properties in Java applications using Spring and Jackson.

Feel free to incorporate this solution into your projects and adapt it to fit your specific needs. Happy coding!
