[英]Spring Boot: Wrapping JSON response in dynamic parent objects
我有一個REST API規范,與后端微服務進行通信,返回以下值:
關於“集合”響應(例如GET /用戶):
{
users: [
{
... // single user object data
}
],
links: [
{
... // single HATEOAS link object
}
]
}
關於“單個對象”響應(例如GET /users/{userUuid}
):
{
user: {
... // {userUuid} user object}
}
}
選擇這種方法是為了使單個響應是可擴展的(例如,如果GET /users/{userUuid}
獲得一個額外的查詢參數,那么在?detailedView=true
我們將有額外的請求信息)。
從根本上說,我認為這是一種可以最大限度地減少API更新之間的重大變化的方法。 但是,將此模型轉換為代碼證明是非常艱巨的。
假設對於單個響應,我為單個用戶提供了以下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
類(可能是一個BaseCollectionsResponse extends ResourceSupport
類),我的模型可以擴展,但看看Jackson似乎從對象定義派生出JSON密鑰,我將不得不使用像Javaassist
這樣的Javaassist
在運行時向基本響應類添加字段 - 這是一個我希望盡可能遠離人類的骯臟黑客。
有沒有更簡單的方法來實現這一目標? 不幸的是,我可能在一年后的響應中有可變數量的頂級JSON對象,所以我不能使用像Jackson的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());
}
}
您還可以在類級別中使用Jackson注釋@JsonTypeInfo
@JsonTypeInfo(include=As.WRAPPER_OBJECT, use=JsonTypeInfo.Id.NAME)
就個人而言,我不介意額外的Dto類,你只需要創建一次,並且幾乎沒有維護成本。 如果您需要進行MockMVC測試,您很可能需要這些類來反序列化您的JSON響應以驗證結果。
您可能知道Spring框架處理HttpMessageConverter層中對象的序列化/反序列化,因此這是更改對象序列化方式的正確位置。
如果您不需要反序列化響應,則可以創建通用包裝器和自定義HttpMessageConverter(並將其放在消息轉換器列表中的MappingJackson2HttpMessageConverter之前)。 像這樣:
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);
}
}
然后,您需要在spring配置中注冊自定義HttpMessageConverter,它通過覆蓋configureMessageConverters()
來擴展WebMvcConfigurerAdapter
。 請注意,執行此操作會禁用轉換器的默認自動檢測,因此您可能必須自己添加默認值(查看Spring源代碼以獲取WebMvcConfigurationSupport#addDefaultHttpMessageConverters()
以查看默認值。如果您擴展WebMvcConfigurationSupport
而不是WebMvcConfigurerAdapter
,則可以直接調用addDefaultHttpMessageConverters
(我個人WebMvcConfigurationSupport
在WebMvcConfigurerAdapter
使用WebMvcConfigurationSupport
,如果我需要自定義任何東西,但這樣做會有一些小問題,你可以在其他文章中讀到這些內容。
Jackson對動態/變量JSON結構沒有很多支持,所以任何完成這樣的事情的解決方案都會像你提到的那樣非常hacky。 據我所知,從我所看到的,標准和最常見的方法是使用像你一樣的包裝類。 包裝類確實會加起來,但是如果你有創造力,那么你可以在類之間找到一些共性,從而減少包裝類的數量。 否則,您可能正在考慮編寫自定義框架。
我想你正在尋找Custom 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和- simplemodule類/
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.