스프링 부트: 동적 상위 개체에서 JSON 응답 래핑
나는 백엔드 마이크로서비스와 대화하는 REST API 사양을 가지고 있으며, 이는 다음 값을 반환합니다.
"수집" 응답(예: GET/users):
{
users: [
{
... // single user object data
}
],
links: [
{
... // single HATEOAS link object
}
]
}
"단일 객체" 응답(예:GET /users/{userUuid}) :
{
user: {
... // {userUuid} user object}
}
}
이 접근 방식은 단일 응답이 확장 가능하도록 선택되었습니다(예: 다음과 같은 경우).GET /users/{userUuid} 변수를 .?detailedView=true추가 요청 정보가 있을 것입니다.
근본적으로 API 업데이트 간의 변화를 최소화하기 위한 OK 접근법이라고 생각합니다.그러나 이 모델을 코드로 변환하는 것은 매우 어려운 것으로 판명되었습니다.
단일 응답에 대해 단일 사용자를 위한 다음 API 모델 개체가 있다고 가정해 보겠습니다.
public class SingleUserResource {
private MicroserviceUserModel user;
public SingleUserResource(MicroserviceUserModel user) {
this.user = user;
}
public String getName() {
return user.getName();
}
// other getters for fields we wish to expose
}
이 방법의 장점은 퍼블릭 게터가 있는 내부적으로 사용되는 모델의 필드만 노출할 수 있고 다른 모델은 노출할 수 없다는 것입니다.그러면 컬렉션 응답에 대해 다음과 같은 래퍼 클래스를 갖게 됩니다.
public class UsersResource extends ResourceSupport {
@JsonProperty("users")
public final List<SingleUserResource> users;
public UsersResource(List<MicroserviceUserModel> users) {
// add each user as a SingleUserResource
}
}
단일 개체 응답의 경우 다음과 같습니다.
public class UserResource {
@JsonProperty("user")
public final SingleUserResource user;
public UserResource(SingleUserResource user) {
this.user = user;
}
}
은 이것수량을 산출합니다.JSON이 게시물의 상단에 있는 API 사양에 따라 형식이 지정된 응답입니다.이 접근 방식의 장점은 우리가 노출하고자 하는 분야만 노출한다는 것입니다.심각한 단점은 잭슨이 정확한 형식의 응답을 생성하기 위해 읽는 것 외에 식별 가능한 논리적 작업을 수행하지 않는 수많은 래퍼 클래스가 날아다니고 있다는 것입니다.
제 질문은 다음과 같습니다.
어떻게 하면 이 접근법을 일반화할 수 있습니까?로 부탁드립니다.
BaseSingularResponse클스그아마a)BaseCollectionsResponse extends ResourceSupportclass내 모델이 확장할 수 것을 , 는 " 클스래확이할장내수모"와 같은 해야 할 입니다.JavaassistRuntime의 기본 응답 클래스에 필드를 추가합니다. 인간적으로 가능한 한 멀리 떨어져 있고 싶은 더러운 해킹입니다.이것을 달성하는 더 쉬운 방법이 있습니까? 최상위 있을 도 있기 의 JSON 객체, 1개의 JSON 객체와 같은 할 수 .
SerializationConfig.Feature.WRAP_ROOT_VALUE모든 것을 하나의 루트 레벨 개체로 포장하기 때문입니다(내가 알기로는).아마도 그런 것이 있을 것입니다.
@JsonProperty클래스 레벨의 경우(방법 및 필드 레벨과 반대)?
몇 가지 가능성이 있습니다.
를 사용할 수 있습니다.java.util.Map:
List<UserResource> userResources = new ArrayList<>();
userResources.add(new UserResource("John"));
userResources.add(new UserResource("Jane"));
userResources.add(new UserResource("Martin"));
Map<String, List<UserResource>> usersMap = new HashMap<String, List<UserResource>>();
usersMap.put("users", userResources);
ObjectMapper mapper = new ObjectMapper();
System.out.println(mapper.writeValueAsString(usersMap));
사용할 수 있습니다.ObjectWriter아래와 같이 사용할 수 있는 응답을 정리합니다.
ObjectMapper mapper = new ObjectMapper();
ObjectWriter writer = mapper.writer().withRootName(root);
result = writer.writeValueAsString(object);
다음은 이 직렬화를 일반화하기 위한 제안입니다.
단순 개체를 처리하는 클래스:
public abstract class BaseSingularResponse {
private String root;
protected BaseSingularResponse(String rootName) {
this.root = rootName;
}
public String serialize() {
ObjectMapper mapper = new ObjectMapper();
ObjectWriter writer = mapper.writer().withRootName(root);
String result = null;
try {
result = writer.writeValueAsString(this);
} catch (JsonProcessingException e) {
result = e.getMessage();
}
return result;
}
}
수집을 처리할 클래스:
public abstract class BaseCollectionsResponse<T extends Collection<?>> {
private String root;
private T collection;
protected BaseCollectionsResponse(String rootName, T aCollection) {
this.root = rootName;
this.collection = aCollection;
}
public T getCollection() {
return collection;
}
public String serialize() {
ObjectMapper mapper = new ObjectMapper();
ObjectWriter writer = mapper.writer().withRootName(root);
String result = null;
try {
result = writer.writeValueAsString(collection);
} catch (JsonProcessingException e) {
result = e.getMessage();
}
return result;
}
}
그리고 샘플 애플리케이션:
public class Main {
private static class UsersResource extends BaseCollectionsResponse<ArrayList<UserResource>> {
public UsersResource() {
super("users", new ArrayList<UserResource>());
}
}
private static class UserResource extends BaseSingularResponse {
private String name;
private String id = UUID.randomUUID().toString();
public UserResource(String userName) {
super("user");
this.name = userName;
}
public String getUserName() {
return this.name;
}
public String getUserId() {
return this.id;
}
}
public static void main(String[] args) throws JsonProcessingException {
UsersResource userCollection = new UsersResource();
UserResource user1 = new UserResource("John");
UserResource user2 = new UserResource("Jane");
UserResource user3 = new UserResource("Martin");
System.out.println(user1.serialize());
userCollection.getCollection().add(user1);
userCollection.getCollection().add(user2);
userCollection.getCollection().add(user3);
System.out.println(userCollection.serialize());
}
}
▁the▁use다를 사용할 .@JsonTypeInfo
@JsonTypeInfo(include=As.WRAPPER_OBJECT, use=JsonTypeInfo.Id.NAME)
개인적으로 저는 추가적인 Dto 수업은 상관없고, 당신은 그것들을 한 번만 만들면 되고 유지보수 비용은 거의 또는 전혀 들지 않습니다.그리고 MockMVC 테스트를 수행해야 하는 경우 결과를 확인하기 위해 JSON 응답을 역직렬화하는 클래스가 필요할 가능성이 높습니다.
Spring 프레임워크는 HttpMessageConverter Layer에서 객체의 직렬화/직렬화를 처리하므로 객체의 직렬화 방식을 변경할 수 있습니다.
응답을 역직렬화할 필요가 없는 경우 일반 래퍼와 사용자 지정 HttpMessageConverter를 만들 수 있습니다(그리고 MappingJackson2 앞에 배치).메시지 변환기 목록에 HttpMessageConverter)가 있습니다.다음과 같이:
public class JSONWrapper {
public final String name;
public final Object object;
public JSONWrapper(String name, Object object) {
this.name = name;
this.object = object;
}
}
public class JSONWrapperHttpMessageConverter extends MappingJackson2HttpMessageConverter {
@Override
protected void writeInternal(Object object, Type type, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
// cast is safe because this is only called when supports return true.
JSONWrapper wrapper = (JSONWrapper) object;
Map<String, Object> map = new HashMap<>();
map.put(wrapper.name, wrapper.object);
super.writeInternal(map, type, outputMessage);
}
@Override
protected boolean supports(Class<?> clazz) {
return clazz.equals(JSONWrapper.class);
}
}
사용자 에 등록해야 . 이 은 HttpMessageConverter를 확장합니다.WebMvcConfigurerAdapter를 하여.configureMessageConverters()이렇게 하면 변환기의 기본 자동 감지가 비활성화되므로, 사용자가 직접 기본값을 추가해야 합니다(스프링 소스 코드:WebMvcConfigurationSupport#addDefaultHttpMessageConverters()기본값을 확인합니다.연장하면WebMvcConfigurationSupport에 신대WebMvcConfigurerAdapter전화하셔도 됩니다addDefaultHttpMessageConverters직접(개인적으로 사용하는 것을 선호합니다.WebMvcConfigurationSupport1파운드가 WebMvcConfigurerAdapter사용자 정의가 필요하지만 이 작업에 대한 몇 가지 사소한 영향이 있을 수 있습니다. 다른 기사에서 이에 대해 읽을 수 있습니다.
Jackson은 동적/가변 JSON 구조를 많이 지원하지 않기 때문에 이와 같은 작업을 수행하는 솔루션은 귀하가 언급한 것처럼 매우 까다로울 것입니다.제가 알기로는, 제가 본 바로는, 표준적이고 가장 일반적인 방법은 현재 당신이 사용하는 래퍼 클래스를 사용하는 것입니다.래퍼 클래스는 추가되지만 고유한 속성으로 창의적이 되면 클래스 간의 공통점을 찾을 수 있으므로 래퍼 클래스의 양을 줄일 수 있습니다.그렇지 않으면 사용자 정의 프레임워크를 작성할 수 있습니다.
사용자 지정 Jackson Serializer를 찾고 있는 것 같습니다.간단한 코드 구현으로 동일한 개체를 서로 다른 구조로 직렬화할 수 있습니다.
예: https://stackoverflow.com/a/10835504/814304 http://www.davismol.net/2015/05/18/jackson-create-and-register-a-custom-json-serializer-with-stdserializer-and-simplemodule-classes/
언급URL : https://stackoverflow.com/questions/41880572/spring-boot-wrapping-json-response-in-dynamic-parent-objects
'programing' 카테고리의 다른 글
| Oracle 11g 서버에서 시작 링크가 작동하지 않음 (0) | 2023.07.08 |
|---|---|
| JavaScript:ASP.NET 코드백에서 Alert.Show(메시지) (0) | 2023.07.08 |
| 폴더의 모든 파일을 여는 방법 (0) | 2023.07.08 |
| PLSQL 하위 쿼리 및 반환 절을 사용하여 에 삽입 (0) | 2023.07.08 |
| Spring Boot에서 테스트 목적으로 DB 연결을 모의 실행하려면 어떻게 해야 합니까? (0) | 2023.07.08 |