Monta
Monta

Reputation: 1180

Make @JsonAnySetter work with @Value Lombok

I have a json object with a lot of properties (~80 properties) I want to deserialize in a POJO without creating manually all the properties. I was able to do this by using the @JsonAnySetter with a Map property like described here.

Now I want to make this work by making my POJO immutable using Lombok. I tried this but it does only deserialize the id and code properties. Any idea on how to make it work?

@Value
@Builder
@EqualsAndHashCode
@JsonDeserialize(builder = Product.ProductBuilder.class)
class Product {

   @JsonProperty
   private String id;
   @JsonProperty
   private String code;
   @Getter(AccessLevel.NONE)
   @Builder.Default
   @JsonProperty
   private Map<String, Optional<Object>> any = new HashMap<>();

   @JsonAnyGetter
   public  Map<String, Optional<Object>> getAny(){
       return this.any;
   }

   @JsonAnySetter
   public void setAny(String key, Optional<Object> value){
      this.any.put(key, value);
   }

}

Upvotes: 8

Views: 4731

Answers (1)

Jan Rieke
Jan Rieke

Reputation: 8042

Update 2021-02-01: Lombok v1.18.16

Starting with v1.18.16, Lombok automatically copies @JsonAnySetter to the @Singular methods in builder. In combination with @Jacksonized you can simply use this code:

@Value
@Jacksonized
@Builder
class Product {
    private String id;
    private String code;

    @JsonAnySetter
    @Singular("any")
    private Map<String, Object> any;
}

Older Lombok versions

For previous Lombok version, this requires some customization of the generated builder class.

Customizing a lombok builder can be done by simply adding its inner class header to your class. Lombok detects that there is already a builder class and just adds all the things that are not already present. This means you can add your own methods, and if those happen to have the same name than a method that lombok would generate, lombok skips this method.

With this approach, we replace the builder's setter method for "any", adding the required @JsonAnySetter to it. I use a LinkedHashMap as map in case the order is relevant; you can use a regular HashMap if it's not.

Furthermore, we replace the build() method to make sure the map you supply to the constructor is immutable. I use Guava's ImmutableMap here. This will make the created instance an immutable value.

@Value
@Builder
@JsonDeserialize(builder = Product.ProductBuilder.class)
class Product {

    @JsonProperty
    private String id;
    @JsonProperty
    private String code;

    @Getter(onMethod_ = @JsonAnyGetter)
    private Map<String, Object> any;
    
    @JsonPOJOBuilder(withPrefix = "")
    public static class ProductBuilder {
        @JsonAnySetter
        public ProductBuilder any(String anyKey, Object anyValue) {
            if (this.any == null) {
                this.any = new LinkedHashMap<String, Object>();
            }
            this.any.put(anyKey, anyValue);
            return this;
        }
        
        public Product build() {
            return new Product(id, code, any == null ? ImmutableMap.of() : ImmutableMap.copyOf(any));
        }

    }
}

Upvotes: 13

Related Questions