簡體   English   中英

Spring Boot:在動態父對象中包裝JSON響應

[英]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 (我個人WebMvcConfigurationSupportWebMvcConfigurerAdapter使用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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM