3

I have a Spring Boot 2 application which uses the ObjectMapper.convertValue method to convert from/to Entities and DTOs.

I've been trying to understand why that method doesn't convert some of the fields, specifically, look at the following scenario:

Product entity:

@Entity
@Table(name = "product")
@Getter
@Setter
@NoArgsConstructor(access = AccessLevel.PUBLIC)
@AllArgsConstructor
public class Product extends AbstractPersistable<Long> {

    @Column
    private String name;

    @Column
    private String description;

    @Column
    private BigDecimal price;

    @Column
    private int weight;

    @Column
    private int stock = 0;

    @Column(name = "image_url", length = 254, unique = true)
    private String imageUrl;

    @NotEmpty
    @Column(name = "banner_image_url", length = 254, unique = true)
    private String bannerImageUrl;

    @ManyToOne(optional = false, fetch = FetchType.EAGER, cascade = CascadeType.ALL)
    @JoinColumn(name = "product_category_id")
    @JsonBackReference
    private Category category;

    @OneToMany(mappedBy = "product", fetch = FetchType.EAGER, cascade = CascadeType.ALL)
    @OrderBy
    @JsonManagedReference
    private SortedSet<ProductThumbnail> thumbnails;

    public Product(Long id) {
        this.setId(id);
    }

    public Product(String name, String description, BigDecimal price, int weight) {
        this.name = name;
        this.description = description;
        this.price = price;
        this.weight = weight;
    }
}

Product DTO:

@Getter
@Setter
@NoArgsConstructor
public class ProductDTO {

    private Long id;

    private String name;

    private String description;

    private BigDecimal price;

    private int weight;

    private String imageUrl;

    private String bannerImageUrl;

    private CategoryDTO category;

    private SortedSet<ProductThumbnailDTO> thumbnails;

    public ProductDTO(@JsonProperty("id") Long id) {
        this.id = id;
    }

    @JsonCreator
    public ProductDTO(@JsonProperty("id") Long id,
                      @JsonProperty("name") String name,
                      @JsonProperty("description") String description,
                      @JsonProperty("price") BigDecimal price,
                      @JsonProperty("weight") int weight,
                      @JsonProperty("imageUrl") String imageUrl,
                      @JsonProperty("category") CategoryDTO category,
                      @JsonProperty("variants") Set<ProductVariantDTO> variants) {
        this.id = id;
        this.name = name;
        this.description = description;
        this.price = price;
        this.weight = weight;
        this.imageUrl = imageUrl;
        this.category = category;
        this.variants = variants;
    }
}

Each field gets converted automatically when executing the folliwng code:

ProductDTO productDTO = objectMapper.convertValue(product, ProductDTO.class);

but with the exception of category. So category in the product variable is set, while the resulting productDTO.category field is null after the conversion.

Category entity:

@Entity
@Table(name = "product_category")
@Getter
@Setter
@NoArgsConstructor(access = AccessLevel.PUBLIC)
@AllArgsConstructor
@Immutable
public class Category extends AbstractPersistable<Long> {

    @Column
    private String name;

    @Column
    private String description;

    @OneToMany(mappedBy = "category", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
    @JsonManagedReference
    private Set<Product> products = new HashSet<>();
}

Category DTO:

@Data
@NoArgsConstructor
public class CategoryDTO {

    private String name;

    private String description;

    @JsonIgnore
    private Set<Product> products = new HashSet<>();

    @JsonCreator
    public CategoryDTO(@JsonProperty("name") String name, @JsonProperty("description") String description) {
        this.name = name;
        this.description = description;
    }
}

So the question is, why the ObjectMapper is not able to convert automatically the category field as well? Is there any condition that prevents it to happen? Because no error is thrown at all.

Following is the objectmapper bean:

@Bean
public ObjectMapper objectMapper() {
    ObjectMapper objectMapper = new ObjectMapper();
    objectMapper.enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS);
    objectMapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
    objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
    objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
    objectMapper.findAndRegisterModules();
    objectMapper.registerModules(module(), new Jdk8Module());
    return objectMapper;
}

jackson dep version 2.10.1

Thank you

Shaunyl
  • 527
  • 2
  • 11
  • 24

1 Answers1

1

So the main culprit in your code is the @JsonBackReference. As @JsonBackReference annotation is assigned to

    @JsonBackReference
    private Category category;

This category is auto removed while serialization. From this reference:

@JsonManagedReference is the forward part of reference – the one that gets serialized normally. @JsonBackReference is the back part of reference – it will be omitted from serialization.

So instead of @JsonBackReference try using: @JsonManagedReference.

Hope this solve your problem.

MD Ruhul Amin
  • 4,386
  • 1
  • 22
  • 37
  • I tried both adding and removing it. No change. The thing is that the whole category object is null after the conversion, not just its fields – Shaunyl Mar 04 '20 at 12:43
  • Can you also check if the category value exists in json inside products? – MD Ruhul Amin Mar 04 '20 at 12:45
  • This call: String s = objectMapper.writeValueAsString(product); produces this output: {"id":1,"name":"Denimearrings","description":"empty description","price":10.00,"weight":1000,"stock":1000,"imageUrl":"resources/images/earrings/E-cheer-yellow.jpg","thumbnails":[{"id":3,"imageUrl":"resources/images/earrings/E-cheer-yellow-big.jpg"}]} so no category here as well – Shaunyl Mar 04 '20 at 12:50
  • So category null is expected. Try with some values which has category value as well. – MD Ruhul Amin Mar 04 '20 at 12:52
  • category from the input var `products` is not null. It's valued, so it looks like it is skipped somehow. I update the answer with a picture of the product object before conversion happen. Updated. – Shaunyl Mar 04 '20 at 12:56
  • Updated answer as well. Check it. @Shaunyl – MD Ruhul Amin Mar 04 '20 at 13:08
  • Yes, that was the problem. Thank you! – Shaunyl Mar 04 '20 at 13:25