Examples for getting Jackson and Lombok to work together to create immutable data types.
- Nullable Types
- Optional
- immutable java.util.List
- immutable array
- validating custom types on instantiate/unmarshalling
- use & customize Lombok-@Builder with Jackson
clone this gist and run with maven
git clone [email protected]:3d0cfa5b8b3f09e7d1c20f5ce4a3fe12.git jackson-lombok-test && cd jackson-lombok-test && mvn test
- Jackson >= 2.7.0
- Lombok >= 1.16.6
@Value
@AllArgsConstructor(access = AccessLevel.PRIVATE)
@Builder(toBuilder = true)
@JsonDeserialize(builder = ImmutableDataType.ImmutableDataTypeBuilder.class)
final class ImmutableDataType {
@NonNull // generate NULL check when setting value
String mandatoryValue;
@JsonInclude(Include.NON_NULL) // don't write value if null
String nullableValue;
@JsonInclude(Include.NON_EMPTY) // don't write value if empty
@NonNull // generate NULL check when setting value
Optional<String> optionalValue;
// declare field using ImmutableList prevents a) setting a mutable list and b) getting a mutable list
@JsonInclude(Include.NON_EMPTY) // don't write null or empty list
@NonNull // generate NULL check when setting value
ImmutableList<ImmutableStringElement> listValue;
@JsonInclude(Include.NON_EMPTY) // don't write null or empty array
@NonNull // generate NULL check when setting value
ImmutableEnumListElement[] arrayValue;
/**
* we can override get logic if needed (e.g. for arrays) - wish Lombok would do this for @Value types
* <p>
* OTOH, this is one of the reasons why you should never use plain arrays.
*
* @return list of elements, never {@code null}
*/
public ImmutableEnumListElement[] getArrayValue() {
return arrayValue.clone();
}
/**
* define builder class - Lombok will enhance this class as needed, Jackson will use this builder then through the @JsonDeserialize annotation above
*/
@JsonPOJOBuilder(withPrefix = "")
@JsonIgnoreProperties(ignoreUnknown = true) // don't barf on unknown properties during deserialization
public static class ImmutableDataTypeBuilder {
/**
* we may set default values for non-nullables here
*/
protected ImmutableDataTypeBuilder() {
this.optionalValue = Optional.empty();
this.listValue = ImmutableList.of();
this.arrayValue = new ImmutableEnumListElement[0];
}
/**
* we can override set logic if needed here (e.g. for arrays)
*
* @return list of elements, never {@code null}
*/
public ImmutableDataTypeBuilder arrayValue(@NonNull ImmutableEnumListElement[] arrayValue) {
this.arrayValue = arrayValue.clone();
return this;
}
}
}
@Value
@AllArgsConstructor(access = AccessLevel.PRIVATE) // mark instance ctor private, only static ctor may use it
final class ImmutableEnumListElement {
public enum Colour {
RED, BLUE, GREEN, YELLOW
}
@NonNull
Colour[] colours;
/**
* Again, I wished Lombok did clone immutable arrays automatically
*/
public Colour[] getColours() {
return colours.clone();
}
/**
* use this method to generate the JSON representation
*/
@JsonValue
public String toString() {
return String.join(",", Stream.of(colours).map(c -> c.toString()).toArray(String[]::new));
}
/**
* use this method to deserialize from a JSON representation. Any instantiation (code, Jackson & Spring MVC) will go through this
* static ctor - hence put any validation & parsing logic here
*/
@JsonCreator
public static ImmutableEnumListElement of(@NonNull String commaSeparatedColourList) {
if (commaSeparatedColourList.trim().length() == 0) throw new IllegalArgumentException("colour list must not be empty");
return new ImmutableEnumListElement(Stream
.of(commaSeparatedColourList.split(","))
.map(strColour -> strColour.trim())
.map(strColour -> Colour.valueOf(strColour.toUpperCase()))
.toArray(Colour[]::new)
);
}
}
@Value
@AllArgsConstructor(access = AccessLevel.PRIVATE) // mark instance ctor private, only static ctor may use it
final class ImmutableStringElement {
@NonNull
String elementName;
/**
* any instantiation (code, Jackson & Spring MVC) will go through this static ctor - put validation logic here
*/
@JsonCreator
public static ImmutableStringElement of(@JsonProperty("elementName") @NonNull String elementName) {
// put validation logic here
if (elementName.length() < 5) throw new IllegalArgumentException("name length must be >5");
return new ImmutableStringElement(elementName);
}
}