Home > Software design >  Propper way to describe request body as java class
Propper way to describe request body as java class

Time:08-11

What is the propper way to describe json body like this in spring-boot app?

{
  "name": "name",
  "releaseDate": "2000-01-01",
  "description": "desc",
  "duration": 10,
  "rate": 1,
  "mpa": { "id": 3},
  "genres": [{ "id": 1}]
}

For now i have class like bellow, but i have problem with serialization of mpa and genres fields.

@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public class Film extends Entity implements Comparable<Film> {

    @Builder
    public Film(long id, String name, @NonNull String description, @NonNull LocalDate releaseDate, @NonNull int duration, List<Genre> genres, Rating mpa, Set<Long> likes) {
        super(id);
        this.name = name;
        this.description = description;
        this.releaseDate = releaseDate;
        this.duration = duration;
        this.genres = genres;
        this.mpa = mpa;
        this.likes = likes;
    }

    @NotBlank
    private final String name;
    @NonNull
    @Size(max = 200, message = "Description name longer than 200 symbols")
    private final String description;
    @NonNull
    @Past
    @JsonFormat(pattern = "yyyy-MM-dd", shape = JsonFormat.Shape.STRING)
    private LocalDate releaseDate;

    @NonNull
    @Positive
    private int duration;

    private Rating mpa;

    private List<Genre> genres;

    @Setter(value = AccessLevel.PRIVATE)
    private Set<Long> likes;
}

Genre and Rating:

@Data
public class Genre {

    @Positive
    private final long id;

}
@Data
public class Rating {

    @Positive
    private final long id;

}

CodePudding user response:

Jackson ObjectMapper cannot create the object, because neither default constructor exists nor any other creator is provided (e.g. @JsonCreator, @ConstructorProperties).

You also have a rate property that is not defined in the Film class, which will cause problems unless you use the @JsonIgnoreProperties annotation (possibly with ignoreUnknown attribute set to true) or configure the ObjectMapper globally (DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES). It could also be that you wanted the likes property to handle that - anyway, it should be fixed.

I've also stumbled upon lack of the jackson-datatype-jsr310 dependency, but maybe your project already has it (it's required for the Java 8 classes like LocalDate).


There are different ways to solve the first problem described above, but generally you need to either provide a default constructor or define a creator for Jackson.

If you don't want to expose the default constructor, you can change the settings of the ObjectMapper (for Spring Boot read about Jackson2ObjectMapperBuilder) to allow usage of private creators (setVisibility method). In Lombok there's an annotation: @NoArgsConstructor. To limit the visibility, use the access annotation attribute.

The creator can be handled by annotating the constructor with ordered argument names: @ConstructorProperties({"id", "name", "description", "releaseDate", "duration", "genres", "mpa", "likes"}). It gets complicated with the Genre and Rating classes as you do not have an explicit access to their constructors. You could either create them and mark appropriately or create a lombok.config file in your project's root directory and inside the file define the property:

lombok.anyConstructor.addConstructorProperties = true

This way Lombok will automatically add the @ConstructorProperties annotations to the classes.


I've created a GitHub repository with the tests for the solution - you can find it here. The lombok.config file is also included, as well as the fixed Film class.

CodePudding user response:

In another way, I put Genre and Rating classes in Film class and the variables are not final in Genre and Rating , tried like this and it worked

@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public class Film extends Entity implements Comparable<Film> {

@Builder
public Film(long id, String name, @NonNull String description, @NonNull LocalDate releaseDate, @NonNull int duration, List<Genre> genres, Rating mpa, Set<Long> likes) {
    super(id);
    this.name = name;
    this.description = description;
    this.releaseDate = releaseDate;
    this.duration = duration;
    this.genres = genres;
    this.mpa = mpa;
    this.likes = likes;
}

@NotBlank
private final String name;
@NonNull
@Size(max = 200, message = "Description name longer than 200 symbols")
private final String description;
@NonNull
@Past
@JsonFormat(pattern = "yyyy-MM-dd", shape = JsonFormat.Shape.STRING)
private LocalDate releaseDate;

@NonNull
@Positive
private int duration;

private Rating mpa;

private List<Genre> genres;

@Setter(value = AccessLevel.PRIVATE)
private Set<Long> likes;

@Data
public static class Genre {

  @Positive
  private long id;

}

@Data
public static class Rating {

  @Positive
  private long id;

}
}
  • Related