• 카테고리

    질문 & 답변
  • 세부 분야

    백엔드

  • 해결 여부

    미해결

Swagger와 Jackson Filter 사용 시 Swagger-ui의 example value

21.06.02 21:36 작성 조회수 1.13k

0

Jackson Filter를 적용한 코드에서는 Swagger-UI에서 example value 값이 "filters"와 "value"로만 보입니다. 필터가 적용된 example value 값을 온전히 표시하기 위해서는 어떻게 해야하는지 궁금합니다.

답변 2

·

답변을 작성해보세요.

0

cnw529님의 프로필

cnw529

질문자

2021.06.07

저 또한 Postman을 이용하여 'GET /users/1'에 대한 Request로 다음과 같은 Response를 정상적으로 받고 있습니다.

User controller의 'GET /users/{id}' 요청은 JsonFilter를 적용하여 MappingJacksonValue를 반환하도록 코드를 작성하였으며, 이로인해 Swagger-ui에서 User controller의 'GET /users/{id}'에 대한 Example value에서 다음과 같이 User Domain 클래스의 필드 구조를 제대로 보여주지 않았고 이를 문제라고 생각하였습니다.

필터를 적용하지 않고, EntityModel<User>를 반환하는 경우는 다음과 같이 example value가 올바르게 표시됩니다. 저는 MappingJacksonValue와 EntitiyModel<User>, 즉, 반환값의 차이에서 이러한 문제가 발생했다고 생각하고 있습니다.

swagger-ui Models의 User에서는 User Domain Class에 대한 정보를 올바르게 표시하고 있습니다.

다음은 작성한 User.java, UserController.java, pom.xml의 코드 입니다.

User.java

package com.example.restfulwebservice.user;

import com.fasterxml.jackson.annotation.JsonFilter;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import javax.validation.constraints.Past;
import javax.validation.constraints.Size;
import java.util.Date;

@Data
@AllArgsConstructor
@NoArgsConstructor
//@JsonIgnoreProperties(value = {"password", "ssn"})
@JsonFilter("UserInfo")
@ApiModel(description = "사용자 상세 정보를 위한 도메인 객체")
public class User {
private Integer id;

@Size(min=2, message = "Name 2글자 이상 입력해 주세요.")
@ApiModelProperty(notes = "사용자 이름을 입력해 주세요.")
private String name;
// 미래 데이터 사용 X
@Past
@ApiModelProperty(notes = "사용자 등록일을 입력해 주세요.")
private Date joinDate;

@ApiModelProperty(notes = "사용자 비빌번호를 입력해 주세요.")
private String password;
@ApiModelProperty(notes = "사용자 주민등록번호를 입력해 주세요.")
private String ssn;
}

UserController.java

package com.example.restfulwebservice.user;

import com.fasterxml.jackson.databind.ser.FilterProvider;
import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter;
import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.hateoas.CollectionModel;
import org.springframework.hateoas.EntityModel;
import org.springframework.hateoas.server.mvc.WebMvcLinkBuilder;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.json.MappingJacksonValue;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;

import javax.validation.Valid;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;

import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo;
import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.methodOn;

@RestController
public class UserController {
@Autowired
private UserDaoService service;

// public UserController(UserDaoService service) {
// this.service = service;
// }

@GetMapping("/users")
public List<EntityModel<User>> retrieveAllUsers() {
List<EntityModel<User>> models = new ArrayList<>();
List<User> users = service.findAll();

// HATEOAS
for (User user : users) {
EntityModel model = EntityModel.of(user);
model.add(linkTo(methodOn(this.getClass()).retrieveAllUsers()).withSelfRel());
models.add(model);
}

// JacksonFilter
// SimpleBeanPropertyFilter filter = SimpleBeanPropertyFilter
// .filterOutAllExcept("id", "name", "joinDate");
// FilterProvider filters = new SimpleFilterProvider().addFilter("UserInfo", filter);
//
// MappingJacksonValue mapping = new MappingJacksonValue(models);
// mapping.setFilters(filters);

return models;
}

@GetMapping("/users/{id}")
public MappingJacksonValue retrieveUser(@PathVariable int id) {
User user = service.findOne(id);
if (user == null) {
throw new UserNotFoundException(id);
}

// HATEOAS
EntityModel<User> model = EntityModel.of(user);
WebMvcLinkBuilder linkTo = linkTo(methodOn(this.getClass()).retrieveAllUsers());
model.add(linkTo.withRel("all-users"));
linkTo = linkTo(methodOn(this.getClass()).updateUser(user, id));
model.add(linkTo.withRel("update-user"));

// JacksonFilter
SimpleBeanPropertyFilter filter = SimpleBeanPropertyFilter
.filterOutAllExcept("id", "name", "joinDate");
FilterProvider filters = new SimpleFilterProvider().addFilter("UserInfo", filter);

MappingJacksonValue mapping = new MappingJacksonValue(model);
mapping.setFilters(filters);
return mapping;
}

@PostMapping("/users")
public ResponseEntity<User> createUser(@Valid @RequestBody User user) {
User savedUser = service.save(user);

URI location = ServletUriComponentsBuilder.fromCurrentRequest()
.path("/{id}")
.buildAndExpand(savedUser.getId())
.toUri();
return ResponseEntity.created(location).build();
}

@DeleteMapping("/users/{id}")
public void deleteUser(@PathVariable int id) {
User user = service.deleteById(id);

if (user == null) {
throw new UserNotFoundException(id);
}
}

@PutMapping("/users/{id}")
public ResponseEntity<User> updateUser(@RequestBody User user, @PathVariable int id) {
User updatedUser = service.update(user, id);

if (updatedUser != null) {
return ResponseEntity.noContent().build();
}
else {
throw new UserNotFoundException(id);

}
}
}

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>restful-web-service</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>restful-web-service</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>16</java.version>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-hateoas</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
</dependency>

<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
<version>2.10.2</version>
</dependency>
<dependency>
<groupId>org.springframework.hateoas</groupId>
<artifactId>spring-hateoas</artifactId>
</dependency>

<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
<version>3.0.0</version>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-rest-hal-explorer</artifactId>
<version>3.5.1</version>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>

</project>
기팝님의 프로필

기팝

2021.11.07

해결 하셧나요 똑같네요 현상이

tree님의 프로필

tree

2022.01.16

저도 같은 상황이라 해결해 보려고 꽤 찾아봤는데 모르겠네여 😂

Jack님의 프로필

Jack

2022.02.15

Swagger 가 인식하는 것은 Endpoint 의 `반환타입`입니다.

고로 Wrapper 로 지정한 클래스가 보이게 됩니다.

근데 사실 Wrapper 는 편의상의 이유로 사용하게 되는데

여기서 MappingJacksonValue 는 `Serialization` 에서 `동적 Filter` 을 위해서 쓰였네요.

그렇다고 Swagger 보자고 소스코드 바꾸고 그러진 마시고

DTO class 에 지정하신 것처럼 `Endpoint` method 에

아래 `Annotation` 을 사용해서 Document UI 에 보여줄 실제 클래스를 지정해주실 수 있어요.

@ApiResponses
({
@ApiResponse(responseCode = "200", content = { @Content(mediaType = "application/json", schema = @Schema(implementation = 보여주고싶은클래스.class)) })
})

 

 

0

안녕하세요, 이도원입니다. 

위에 올려주신 코드를 그대로 적용하여 확인해 보았을 때, 오류 발생 없이 다음과 같이 메시지가 출력되는 것을 확인하였습니다. 작성하신 코드에는 문제가 없어 보입니다. 괜찮으시면 작업하신 User.java, pom.xml 코드 등을 공유해 주실 수 있을까요? 오류를 같이 찾아 보도록 하겠습니다. 

edowon0623@gmail.com

참고로 아래 github에서 작업된 코드를 확인해 보실 수 있습니다.

https://github.com/joneconsulting/my-restful-services/settings

감사합니다.