Учебное пособие по @JsonView с SpringBoot Джексона


Содержание
  1. Введение
  2. Пример в реальном времени
  3. Как помогает JSonView
  4. Краткое изложение важных моментов, касающихся JsonView
  5. Подробная реализация и учебник
  6. Уровень 1: JsonView с одним классом
  7. Шаг 1.1 : Создание класса JsonView
  8. Шаг 1.2 : Создайте Java-класс с @JsonView на необходимых переменных
  9. Шаг 1.3 : Создайте контроллер и конечные точки GET
  10. Шаг 1.4: Тестирование конечных точек GET
  11. Шаг 1.5: Добавление конечных точек POST
  12. Шаг 1.6 : Тестирование конечных точек POST
  13. Выводы из первого уровня
  14. Уровень 2 : JsonView с несколькими классами
  15. Шаг 2.1 : Создайте класс JsonView
  16. Шаг 2.2 : Создание нескольких Java-классов с @JsonView для необходимых переменных
  17. Шаг 2.3 : Создание контроллера и конечных точек GET
  18. Шаг 2.4: Протестируйте конечные точки GET
  19. Шаг 2.5 : Добавьте конечные точки POST
  20. Шаг 2.6 : Тестирование конечных точек POST
  21. Выводы из Уровня 2
  22. Уровень 3: Использование сущностей с JsonView
  23. Шаг 3.1 : Создайте класс JsonView
  24. Шаг 3.2 : Создайте Java-сущность с @JsonView на необходимых переменных
  25. Шаг 3.3 : Создайте контроллер и конечные точки GET
  26. Шаг 3.4: Тестирование конечных точек GET
  27. Вывод из уровня 3
  28. Заключение

Введение

Если вы являетесь разработчиком SpringBoot и работаете над большим проектом, вы должны знать об аннотации, предоставляемой Jackson —@JsonView, потому что она чрезвычайно мощная и элегантная. @JsonView от Jackson помогает выборочно сериализовать и де-сериализовать объекты. Тем самым он сокращает вашу кодовую базу на тонну, избегая ненужных классов, а также раздражающих геттеров и сеттеров для копирования значений одного объекта в другой.

Пример в реальном времени

Посмотрите на приведенный ниже объект JSON

{
  "userId": "markbdsouza",
  "firstName": "Mark",
  "lastName": "Dsouza",
  "socialMediaAccount: {
     "twitterUrl": "https://twitter.com/MarkBDsouza",
     "githubUrl": "https://github.com/markbdsouza",
     "devUrl": "",
     "hashNodeUrl":""
  },
  "articles": [
     {
        "summary": "JS: Sort an Array of Objects on multiple columns/keys",
        "url": "https://dev.to/markbdsouza/js-sort-an-array-of-objects-on-multiple-columns-keys-2bj1"
     },
     {
        "summary": "Spring Cloud Config Server: Step by Step",
        "url": "https://dev.to/markbdsouza/spring-cloud-config-server-step-by-step-14fd"
     }
  ]
}
Вход в полноэкранный режим Выход из полноэкранного режима

Здесь у нас есть Json-объект User.
Мы видим, что один пользователь имеет один объект SocialMediaAccount (связь OneToOne), а один клиент имеет несколько статей (связь OneToMany).
В Java для такой структуры нам понадобится 3 класса — User, SocialMediaAccount, Article.
Эти 3 класса будут выглядеть следующим образом

public class User{
  private String userId;
  private String firstName;
  private String lastName;
  private SocialMediaAccount socialMediaAccount;
  private List<Article> articles;
  // excluding getters and setters for brevity
}
Вход в полноэкранный режим Выход из полноэкранного режима
public class SocialMediaAccount {
  private String twitterUrl;
  private String githubUrl;
  // excluding getters and setters for brevity
}
Войти в полноэкранный режим Выход из полноэкранного режима
public class Article{
  private String summary;
  private String url;
  // excluding getters and setters for brevity
}
Войти в полноэкранный режим Выход из полноэкранного режима

Как помогает JSonView

Теперь, используя эту единую структуру, мы можем создавать JSON-объекты, содержащие любую комбинацию полей, чтобы сформировать следующий JSON из конечной точки 2

{
  "userId": "markbdsouza",
  "socialMediaAccount: {
     "twitterUrl": "https://twitter.com/MarkBDsouza"     
  },
  "articles": [
     {
        "summary": "JS: Sort an Array of Objects on multiple columns/keys",
        "url": "https://dev.to/markbdsouza/js-sort-an-array-of-objects-on-multiple-columns-keys-2bj1"
     },
     {
        "summary": "Spring Cloud Config Server: Step by Step",
        "url": "https://dev.to/markbdsouza/spring-cloud-config-server-step-by-step-14fd"
     }
  ]
}
Войти в полноэкранный режим Выйти из полноэкранного режима

ИЛИ следующая структура из конечной точки 3

{
  "userId": "markbdsouza",
  "firstName": "Mark", 
  "socialMediaAccount: {
     "twitterUrl": "https://twitter.com/MarkBDsouza",
     "githubUrl": "https://github.com/markbdsouza",
     "devUrl": "",
     "hashNodeUrl"
  }
}
Войти в полноэкранный режим Выйти из полноэкранного режима

ИЛИ любая другая структура, состоящая из тех же переменных в любой перестановке/комбинации.
Бизнес-логика для всех конечных точек может быть абсолютно одинаковой. Единственное различие заключается в аннотации, которая должна быть предоставлена таким образом, чтобы при преобразовании POJO в JSON или наоборот, Jackson проверял, какие поля должны быть включены при сериализации/десериализации. Таким образом, нам не придется создавать новый класс с определенной структурой, а затем копировать данные из исходного объекта в новый объект класса для каждой конечной точки.

Краткое изложение важных моментов, касающихся JsonView

Приведенные ниже пункты служат кратким резюме всего, что вам, вероятно, нужно знать о JsonView, чтобы приступить к реализации.

  • Представление в JsonView определяет, как вы хотите представить класс, когда он преобразуется из POJO в JSON пакетом Jackson. Jackson будет сериализовать/де-сериализовать только поля с соответствующим @JsonView.
  • Создайте простой Java класс (аннотация на уровне класса не требуется), который будет содержать интерфейсы/дочерние классы, которые являются именами наших представлений. Мы можем использовать наследование, чтобы View 2 также включал все, что присутствует в View 1. Это полезно, когда вы создаете новый View, который расширяет поля, видимые в уже существующем View.
public class Views{
    public interface Public{}
    public interface CustomerDetails extends Public{}
}
Вход в полноэкранный режим Выход из полноэкранного режима
  • После создания класса View аннотация необходима в двух местах. 1) В самом классе или в переменных экземпляра класса, которые необходимо сериализовать/де-сериализовать. 2) В конечных точках в вашем контроллере, чтобы Jackson знал, где применить View.
  • На уровне класса (того, что мы сериализуем/де-сериализуем), нам нужно аннотировать необходимую переменную экземпляра @JsonView(Views.Public.class), где Views — это класс, созданный в шаге выше, а Public — это интерфейс/подкласс, созданный в нем. Это то, что указывает Jackson, какую переменную экземпляра класса сериализовать/де-сериализовать. Пример
    @JsonView(Views.Public.class)
    private String firstName;
Вход в полноэкранный режим Выйти из полноэкранного режима
  • Одна переменная экземпляра может иметь любое количество JsonViews, присоединенных к ней. Пример:
    @JsonView({Views.Public.class, Views.UserDetails.class})
    private String email;
Вход в полноэкранный режим Выход из полноэкранного режима
  • Если у нас есть объект класса B как переменная экземпляра класса A. И мы прикрепили @JsonView к объекту, он будет внутренне также сериализовать/де-сериализовать только аннотированные переменные экземпляра класса B.Пример:
public class A{
    @JsonView(Views.Public.class)
    private B b;
}
Вход в полноэкранный режим Выход из полноэкранного режима
class B{
    private String password; // will not be included by jackson
    @JsonView(Views.Public.class)
    private String username;
}
Войти в полноэкранный режим Выход из полноэкранного режима

Если мы не используем @JsonView в классе B, то ни одно поле не будет сериализовано/де-сериализовано из B, и это будет пустой объект, когда A будет сериализован/де-сериализована.

  • В качестве альтернативы мы можем присвоить аннотацию всему классу.
  • В контроллерах нам нужно аннотировать конечные точки соответствующим образом, чтобы Jackson знал, какие конечные точки имеют логику при преобразовании POJO в JSON или наоборот.
  • Для конечных точек GET, если мы возвращаем класс и хотим предоставить только видимые поля, мы аннотируем конечную точку следующим образом
    @JsonView(Views.Public.class)
    @GetMapping("/1")
    public Customer getDetails() {
            // business logic to return customer 
    }
Войти в полноэкранный режим Выйти из полноэкранного режима
  • Если мы возвращаем тот же класс в другой конечной точке, то необязательно иметь @JsonView. Если ваш контроллер не имеет @JsonView, все поля возвращаемого POJO будут преобразованы в JSON.
  • Если это HTTP POST вызов и мы отправляем класс в нашем теле, используя @JsonView мы можем принять только поля, указанные в нашем View. Чтобы сделать это
@PostMapping
public Student postLevelTwoBasic(@RequestBody @JsonView(LevelTwoViews.Public.class) Student student) {
    // some POST implementation
} 
Вход в полноэкранный режим Выход из полноэкранного режима

Подробная реализация и учебник

В следующем разделе мы рассмотрим код, настроим JsonView и посмотрим, как его можно использовать. Вы можете найти ссылку на github со всем кодом: https://github.com/markbdsouza/jsonview-springboot-tutorial.
Учебник разбит на 3 уровня. Мы будем использовать все больше возможностей и изучать другие применения JsonView.

Уровень 1: JsonView с одним классом

Для начала мы создадим один JAVA класс и используем аннотацию JsonView на нем. Мы будем проверять как GET, так и POST запросы и посмотрим, как все это работает вместе. В последующих уровнях мы попробуем использовать несколько классов и даже сущностей.

Шаг 1.1 : Создание класса JsonView

Создайте класс, содержащий различные представления, которые необходимо создать.
Обратите внимание, как мы используем наследование в этих интерфейсах. Public сохраняется в качестве базового представления. У нас есть 2 различных представления, которые наследуют Public — UserNameDetails и OtherDetails. UserDetails расширяет UserNameDetails, поэтому он расширяет Public по своей сути.

public class LevelOneViews {
    /*
     * Any variable associated with Public will be picked up by this view
     */
    public interface Public {
    }

    /*
     * Any variable associated with UserNameDetails or Public will be picked up by this view
     */
    public interface UserNameDetails extends Public {
    }

    /*
     * Any variable associated with UserDetails, UserNameDetails or Public(since UserNameDetails internally
     *  extends Public)  will be picked up by this view
     */
    public interface UserDetails extends UserNameDetails {
    }

    /*
     * Any variable associated with OtherDetails or Public will be picked up by this view.
     * Note: Will not pick up variables associated with UserNameDetails or UserDetails
     */
    public interface OtherDetails extends Public {
    }
}
Вход в полноэкранный режим Выход из полноэкранного режима

Здесь мы создали 4 представления. Потратьте минуту, чтобы увидеть наследование и понять дерево наследования.

Шаг 1.2 : Создайте Java-класс с @JsonView на необходимых переменных

Создайте класс User с переменными экземпляра с представлениями, созданными в шаге выше.

@Data
@ToString
public class User {
    @JsonView({LevelOneViews.Public.class})
    private int userId;
    @JsonView(LevelOneViews.UserNameDetails.class)
    private String firstName;
    @JsonView(LevelOneViews.UserNameDetails.class)
    private String lastName;
    @JsonView({LevelOneViews.UserDetails.class, LevelOneViews.OtherDetails.class})
    private String email;
    @JsonView({LevelOneViews.UserDetails.class, LevelOneViews.OtherDetails.class})
    private String organization;
    private Boolean isActive;
}
Войдите в полноэкранный режим Выйти из полноэкранного режима

Шаг 1.3 : Создайте контроллер и конечные точки GET

Создайте конечные точки с 4 созданными представлениями. У нас есть приватный статический метод для создания объекта User. Обратите внимание, что для всех методов реализация одинакова. Мы вызываем только метод createUser(). Единственное различие во всех методах заключается в том, какой вид мы используем. Для каждого из этих GET-маппингов с использованием JsonView нам нужно аннотировать весь метод с помощью @JsonView с указанием представления. Пример: @JsonView(LevelOneViews.UserNameDetails.class).

@RestController
public class UserController {

    /**
     * GET without any view
     *
     * @return created User
     */
    @GetMapping("/levelOne/default")
    public User getLevelOneUser() {
        return createUser();
    }

    /**
     * GET with Public View
     *
     * @return created User
     */
    @JsonView(LevelOneViews.Public.class)
    @GetMapping("/levelOne/public")
    public User getLevelOnePublicUser() {
        return createUser();
    }

    /**
     * GET with UserNameDetails View which extends Public
     *
     * @return created User
     */
    @JsonView(LevelOneViews.UserNameDetails.class)
    @GetMapping("/levelOne/name")
    public User getLevelOneNameUser() {
        return createUser();
    }

    /**
     * GET with UserDetails  View which extends UserNameDetails and also Public
     *
     * @return created User
     */
    @JsonView(LevelOneViews.UserDetails.class)
    @GetMapping("/levelOne/details")
    public User getLevelDetailsUser() {
        return createUser();
    }

    /**
     * GET with OtherDetails View which extends Public
     *
     * @return created User
     */
    @JsonView(LevelOneViews.OtherDetails.class)
    @GetMapping("/levelOne/other")
    public User getLevelOneOther() {
        return createUser();
    }

    /**
     * @return created user
     */
    private static User createUser() {
        User user = new User();
        user.setUserId(1);
        user.setFirstName("Mark");
        user.setLastName("Dsouza");
        user.setEmail("mark.benjamin.dsouza@google.com");
        user.setIsActive(true);
        user.setOrganization("DEV.TO");
        return user;
    }
}
Вход в полноэкранный режим Выход из полноэкранного режима

Существует 5 конечных точек. 1 без представления и 4 с представлением.

Шаг 1.4: Тестирование конечных точек GET

Запустите приложение springboot и протестируйте наши конечные точки.

Конечная точка 1: без представления

Все поля нашего объекта User Object будут десериализованы
http://localhost:8080/levelOne/default

Конечная точка 2: С публичным представлением

Public View — это наше базовое представление по умолчанию, которое имеет наименьшее количество переменных
http://localhost:8080/levelOne/public

Обратите внимание, что возвращается только столбец userId, хотя код для конечной точки 1 и 2 абсолютно одинаков. Это делается с помощью JsonView.

Конечная точка 3: с помощью представления UserNameDetails

http://localhost:8080/levelOne/name
UserNameDetails расширяет Public. Таким образом, благодаря наследованию, он отображает все поля с UserNameDetails, а также Public. Таким образом, мы видим 3 поля в нашем ответе.

Конечная точка 4: С представлением UserDetails

http://localhost:8080/levelOne/details
UserDetails расширяет UserNameDetails, что через наследование означает, что UserDetails также расширяет Public. Таким образом, все поля с UserDetails, UserNameDetails и Public будут доступны в ответе.

Конечная точка 5: С представлением OtherDetails

http://localhost:8080/levelOne/other
OtherDetails не расширяет вышеуказанные представления, а только представление Public. Поэтому в ответе будут доступны только поля с OtherDetails или Public.

Шаг 1.5: Добавление конечных точек POST

    Logger log = LoggerFactory.getLogger(UserController.class);
    /**
     * POST without any view
     *
     * @param user
     */
    @PostMapping("/levelOne/default")
    public User postLevelOneDefault(@RequestBody User user) {
        log.info(String.valueOf(user));
        return user;
    }

    /**
     * POST with Public view
     *
     * @param user
     */
    @PostMapping("/levelOne/name")
    public User postLevelOneDetails(@RequestBody @JsonView(LevelOneViews.UserNameDetails.class) User user) {
        log.info(String.valueOf(user));
        return user;
    }
Войдите в полноэкранный режим Выйти из полноэкранного режима

Шаг 1.6 : Тестирование конечных точек POST

Конечная точка 1: Без просмотра

Пост вызов URL:http://localhost:8080/levelOne/default
Тело запроса:
Мы отправляем всю структуру.
Логгер: User(userId=1, firstName=Mark, lastName=Dsouza, email=mark.benjamin.dsouza@google.com, organization=DEV.TO, isActive=true).
Все данные регистрируются.

Все данные возвращаются.

Конечная точка 2: с помощью JsonView

Давайте попробуем сделать это с помощью конечной точки JsonView
Пост вызов URL: http://localhost:8080/levelOne/name

Логгер: User(userId=1, firstName=Mark, lastName=Dsouza, email=null, organization=null, isActive=null)
Обратите внимание, что несмотря на то, что мы отправили целый объект, только 3 поля были де-сериализованы.
Ответ — это то, что мы увидели в логах. Сам контроллер не получил все поля, поэтому только некоторые поля, связанные с соответствующим представлением, были де-сериализованы.

Выводы из первого уровня

Мы увидели, какие классы необходимы для реализации JsonView и как мы можем использовать его для HTTP GET и POST запросов. Нам нужно было добавить аннотацию к классу, который сериализуется/десериализуется, а также к конечной точке контроллера/API. Кроме того, при определении представлений и их использовании можно использовать наследование, чтобы одно поле было легко связано с несколькими представлениями.

Уровень 2 : JsonView с несколькими классами

На этом уровне мы посмотрим, как все работает, когда это не простой класс Java, содержащий только строки и числа. Давайте посмотрим, как это работает, когда у нас есть объекты других классов. Это, как правило, отношения OneToOne (один объект) и OneToMany (список объектов).

Шаг 2.1 : Создайте класс JsonView

Создайте класс, содержащий различные представления, которые необходимо создать.

public class LevelTwoViews {
    /*
     * Any variable associated with Public will be picked up by this view
     */
    public interface Public {
    }

    /*
     * Any variable associated with BasicStudentDetails or Public will be picked up by this view
     */
    public interface BasicStudentDetails extends Public {
    }

    /*
     * Any variable associated with BasicStudentDetails, AllStudentDetails or Public will be picked up by this view
     */
    public interface AllStudentDetails extends BasicStudentDetails {
    }
}
Вход в полноэкранный режим Выйти из полноэкранного режима

Здесь мы снова использовали наследование и создали 3 представления с возрастающим доступом.

Шаг 2.2 : Создание нескольких Java-классов с @JsonView для необходимых переменных

На втором уровне мы будем создавать несколько классов, чтобы проиллюстрировать, как это работает

Создайте студента
@Data
@ToString
public class Student {
    @JsonView(LevelTwoViews.Public.class)
    private String fullName;
    @JsonView(LevelTwoViews.BasicStudentDetails.class)
    private float height;
    @JsonView(LevelTwoViews.BasicStudentDetails.class)
    private float weight;
    @JsonView(LevelTwoViews.Public.class)
    private int age;
    @JsonView(LevelTwoViews.BasicStudentDetails.class)
    private String className;
    // One to Many Relationship
    @JsonView(LevelTwoViews.AllStudentDetails.class)
    private List<SubjectScore> scores;
    // One to One Relationship
    @JsonView(LevelTwoViews.BasicStudentDetails.class)
    private School school;
}
Войти в полноэкранный режим Выйти из полноэкранного режима
Создать школу

Один ученик имеет одну школу — отношения один к одному

@Data
@ToString
public class School {

    @JsonView(LevelTwoViews.BasicStudentDetails.class)
    private String schoolName;
}
Войти в полноэкранный режим Выйти из полноэкранного режима
Создать предметный балл

У одного ученика несколько предметных оценок — связь «один ко многим

@Data
@ToString
public class SubjectScore {
    @JsonView(LevelTwoViews.AllStudentDetails.class)
    private String subject;
    @JsonView(LevelTwoViews.AllStudentDetails.class)
    private int score;
    private String teacher;
}
Войти в полноэкранный режим Выход из полноэкранного режима

Обратите внимание, что у учителя нет представления. Поэтому независимо от того, какой JsonView мы предоставим на уровне контроллера, учитель никогда не будет сериализован/де-сериализован.

Шаг 2.3 : Создание контроллера и конечных точек GET

Создайте конечные точки для созданных представлений. У нас есть несколько частных статических методов для создания фиктивного объекта. Здесь также для всех методов реализация одинакова, и единственное различие заключается в том, какое представление мы используем.

@RestController
public class StudentController {

    /**
     * GET without any json view
     *
     * @return created Student
     */
    @GetMapping("/levelTwo/default")
    public Student getLevelTwoDefault() {
        return createStudent();
    }

    /**
     * GET with lowest view Public
     *
     * @return created Student
     */
    @JsonView(LevelTwoViews.Public.class)
    @GetMapping("/levelTwo/public")
    public Student getLevelTwoPublic() {
        return createStudent();
    }

    /**
     * GET with BasicStudentDetails View which extends Public
     *
     * @return created Student
     */
    @JsonView(LevelTwoViews.BasicStudentDetails.class)
    @GetMapping("/levelTwo/basic")
    public Student getLevelTwoBasicDetails() {
        return createStudent();
    }

    /**
     * GET with AllStudentDetails View which extends BasicStudentDetails,Public
     *
     * @return created Student
     */
    @JsonView(LevelTwoViews.AllStudentDetails.class)
    @GetMapping("/levelTwo/all")
    public Student getLevelTwoAllDetails() {
        return createStudent();
    }

    private static Student createStudent() {
        Student student = new Student();
        student.setAge(15);
        student.setFullName("Mark Dsouza");
        student.setWeight(49);
        student.setHeight(150);
        student.setClassName("VIII");
        student.setSchool(createSchool());
        student.setScores(createSubjectScoreList());
        return student;
    }
    private static School createSchool() {
        School school = new School();
        school.setSchoolName("Indian Public School");
        return school;
    }
    private static List<SubjectScore> createSubjectScoreList() {
        List<SubjectScore> subjectScoreList = new ArrayList<>();
        SubjectScore mathScore = new SubjectScore();
        mathScore.setScore(80);
        mathScore.setSubject("Math");
        mathScore.setTeacher("Mr. John Watts");
        SubjectScore englishScore = new SubjectScore();
        englishScore.setScore(80);
        englishScore.setSubject("English");
        englishScore.setTeacher("Mrs. Mary Johnson");
        subjectScoreList.add(mathScore);
        subjectScoreList.add(englishScore);
        return subjectScoreList;
    }
Вход в полноэкранный режим Выход из полноэкранного режима

Мы создали 4 конечные точки GET, одну без представления и три с представлениями, которые мы создали.

Шаг 2.4: Протестируйте конечные точки GET

Запустите приложение SpringBoot и протестируйте наши конечные точки для методов GET уровня 2.

Конечная точка 1: Без представления

Метод Http: GET
URL: http://localhost:8080/levelTwo/default
Ответ:

Здесь мы получаем обратно все данные объекта и дочерних объектов.

Конечная точка 2: С публичным представлением

Метод Http: GET
URL: http://localhost:8080/levelTwo/public
Ответ:

Мы видим только данные с публичным представлением

Конечная точка 3: С BasicStudentDetails

Метод Http: GET
URL: http://localhost:8080/levelTwo/basic
Ответ:

Обратите внимание, что оценки не включены в это представление, поскольку мы не задали требуемое представление для этого объекта в нашем основном классе Student.

Конечная точка 4: С представлением AllStudentDetails

Метод Http: GET
URL: http://localhost:8080/levelTwo/all
Ответ:

Аналогично, здесь учитель класса Score не присутствует, так как представление не было определено.

Шаг 2.5 : Добавьте конечные точки POST

Теперь создайте конечные точки POST

    Logger log = LoggerFactory.getLogger(StudentController.class);
    /**
     * POST Mapping without any views
     *
     * @param student
     * @return same student
     */
    @PostMapping("/levelTwo/default")
    public Student postLevelTwoDefault(@RequestBody Student student) {
        log.info(student.toString());
        return student;
    }

    /**
     * POST Mapping with Public View. Instance variables of Student without Public view will not be deserialized
     *
     * @param student
     * @return same student
     */
    @PostMapping("/levelTwo/public")
    public Student postLevelTwoBasic(@RequestBody @JsonView(LevelTwoViews.Public.class) Student student) {
        log.info(student.toString());
        return student;
    }
Вход в полноэкранный режим Выход из полноэкранного режима

Шаг 2.6 : Тестирование конечных точек POST

Конечная точка 5: POST без представления

Метод Http: POST
URL: http://localhost:8080/levelTwo/default
Тело запроса:

Логгер: Student(fullName=Mary Jane, height=137.0, weight=34.0, age=16, className=X, scores=[SubjectScore(subject=Math, score=90, teacher=Mr. John Watts), SubjectScore(subject=English, score=40, teacher=Mrs. Mary Johnson)], school=School(schoolName=New York Public School)).
Мы записали в журнал то, что получено контроллером, и видим, что все поля имеют достоверные данные.
Отклик:

Мы видим, что те же данные, которые были отправлены, возвращаются полностью.

Конечная точка 6: POST с представлением

Метод Http: POST
URL: http://localhost:8080/levelTwo/public
Тело запроса: Мы используем тот же запрос, что и ранее
Логгер: Student(fullName=Mary Jane, height=0.0, weight=0.0, age=16, className=null, scores=null, school=null).
Обратите внимание на разницу, почти ВСЁ (кроме fullName и возраста) отображается по умолчанию, несмотря на то, что мы отправили все данные в теле запроса. Именно здесь JsonView работает по своему волшебству. Несмотря на то, что мы отправляем все данные, JsonView де-сериализует ТОЛЬКО те данные, которые указаны в соответствующем представлении (в данном случае Public, которое присвоено только двум полям).
Реакция:

Поскольку контроллер сам не получил данные, то и возвращаемые данные — это только то, что стало видимым благодаря JsonView.

Выводы из Уровня 2

На Уровне 2 мы увидели, как можно работать с несколькими классами и как можно использовать представления между ними для достижения нужной нам структуры данных.

Уровень 3: Использование сущностей с JsonView

В Уровне 3 мы будем использовать базу данных H2 для выбора данных, а не просто фиктивные данные. И мы будем непосредственно использовать @JsonView на самой сущности. Это поможет нам отказаться от использования каких-либо DTO, RequestModel, ResponseModel.
Примечание: Код на Github включает data.sql и schema.sql в папку src/main/resources и некоторые свойства в application.properties, которые будут автоматически создавать схему и вставлять данные в H2 DB.

Шаг 3.1 : Создайте класс JsonView

Создайте класс, содержащий различные представления, которые необходимо создать.

/**
 * List of Views for LevelThree functionality.
 */
public class LevelThreeViews {
    /*
     * Any variable associated with Public will be picked up by this view
     */
    public interface Public {
    }

    /*
     * Any variable associated with CustomerDetails or Public will be picked up by this view
     */
    public interface CustomerDetails extends Public {
    }
Войдите в полноэкранный режим Выход из полноэкранного режима

Мы создали 2 представления, Public и CustomerDetails.

Шаг 3.2 : Создайте Java-сущность с @JsonView на необходимых переменных

При создании нашей сущности добавьте дополнительные @JsonView для необходимых переменных экземпляра.

@Entity
@Table(name = "customer")
@Data
public class Customer {
    @Id
    @GeneratedValue
    private Long id;
    @Column(name = "CUSTOMER_ID", nullable = false)
    @JsonView(LevelThreeViews.Public.class)
    private String customerId;
    @Column(name = "FIRST_NAME", nullable = false, length = 50)
    @JsonView(LevelThreeViews.CustomerDetails.class)
    private String firstName;
    @Column(name = "LAST_NAME", nullable = false, length = 50)
    @JsonView(LevelThreeViews.CustomerDetails.class)
    private String lastName;
}
Вход в полноэкранный режим Выход из полноэкранного режима

Столбец ‘id’ не имеет никакого представления, связанного с ним. Мы не хотим раскрывать первичный ключ нашей БД для любого клиента нашего API. Вместо этого мы раскрываем только CUSTOMER_ID, который является строковым значением.

Шаг 3.3 : Создайте контроллер и конечные точки GET

Создайте конечные точки для нескольких созданных представлений. Здесь для всех методов мы получаем данные из БД. Код абсолютно одинаков для всех API, разница лишь в том, какое представление мы используем.

@RestController
public class CustomerController {
    @Autowired
    private CustomerRepository customerRepository;

    /**
     * GET Mapping without JsonView
     * @return List of Customer inserted in the DB
     */
    @GetMapping("/levelThree/default")
    public List<Customer> getLevelThree() {
        return customerRepository.findAll();
    }

    /**
     * GET Mapping with Public JsonView
     * @return List of Customer inserted in the DB
     */
    @JsonView(LevelThreeViews.Public.class)
    @GetMapping("/levelThree/public")
    public List<Customer> getLevelThreePublic() {
        return customerRepository.findAll();
    }

    /**
     * GET Mapping with CustomerDetails JsonView which extends Public as well
     * @return List of Customer inserted in the DB
     */
    @JsonView(LevelThreeViews.CustomerDetails.class)
    @GetMapping("/levelThree/details")
    public List<Customer> getLevelThreeDetails() {
        return customerRepository.findAll();
    }
}
Вход в полноэкранный режим Выход из полноэкранного режима

В целом у нас есть 3 конечные точки, чтобы увидеть разницу между NoView, PublicView и CustomerDetailsView.

Шаг 3.4: Тестирование конечных точек GET

Запустите приложение springboot и протестируйте наши конечные точки.

Конечная точка 1: Без представления

Метод Http: GET
URL: http://localhost:8080/levelThree/default
Ответ:
Возвращаются все поля, поскольку для этого API нет представления.

Конечная точка 2: С публичным представлением

Метод Http: GET
URL: http://localhost:8080/levelThree/public
Ответ:
Обратите внимание, что возвращаются только поля с Public View.

Конечная точка 3: С CustomerDetailsView

Метод Http: GET
URL: http://localhost:8080/levelThree/details
Ответ:
Обратите внимание, что возвращаются поля с Public и CustomerDetailsView. Но они не содержат столбца id, поскольку мы не указали аннотацию и имя представления для столбца id.

Вывод из уровня 3

Уровень 3 показал нам, как мы можем использовать сущности напрямую и надежно скрывать данные, которые мы не хотим раскрывать в API. Это включает в себя любые столбцы, создаваемые последовательностью, столбцы аудита или любые другие столбцы. Аналогично тому, что мы видели на уровне 2, мы можем иметь связанные сущности с помощью аннотаций OneToOne, OneToMany и ManyToOne.
Побочное замечание: Если вы реализуете это в своем коде и имеете несколько отношений, вы можете столкнуться с проблемой циклических ссылок при десериализации. Проверьте JsonManagedReference и JsonBackReference на StackOverflow, чтобы решить эту проблему.

Заключение

В этом руководстве мы рассмотрели различные примеры использования @JsonView для HTTP-вызовов, когда объект является частью ответа или объект является частью тела запроса. Мы также увидели, как мы можем использовать несколько классов и сущностей с JsonView.
Надеюсь, вы найдете это полезным и дайте мне знать в комментариях или напишите мне в twitter, если у вас есть вопросы. Здоровья вам и счастливого кодинга!

Оцените статью
Procodings.ru
Добавить комментарий