Spring Boot: Wrapping JSON response in dynamic parent objects
我有一个与后端微服务进行对话的RESTAPI规范,它返回以下值:
关于"集合"响应(例如get/users):
1 2 3 4 5 6 7 8 9 10 11 12 | { users: [ { ... // single user object data } ], links: [ { ... // single HATEOAS link object } ] } |
关于"单目标"响应(如
1 2 3 4 5 | { user: { ... // {userUuid} user object} } } |
号
选择这种方法是为了使单个响应具有可扩展性(例如,如果
从根本上讲,我认为这是一种最小化API更新之间中断更改的好方法。然而,将这个模型转换为代码是非常困难的。
假设对于单个响应,我为单个用户提供了以下API模型对象:
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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 } |
这种方法的优点是,我们只能公开内部使用的模型中的字段,对于这些模型,我们有公共getter,但不能公开其他的。然后,对于集合响应,我将拥有以下包装类:
1 2 3 4 5 6 7 8 9 | public class UsersResource extends ResourceSupport { @JsonProperty("users") public final List<SingleUserResource> users; public UsersResource(List<MicroserviceUserModel> users) { // add each user as a SingleUserResource } } |
。
对于单个对象响应,我们将有以下内容:
1 2 3 4 5 6 7 8 9 | public class UserResource { @JsonProperty("user") public final SingleUserResource user; public UserResource(SingleUserResource user) { this.user = user; } } |
这将生成
我的问题如下:
我怎样才能概括这种方法呢?理想情况下,我希望有一个单独的
BaseSingularResponse 类(可能还有一个BaseCollectionsResponse extends ResourceSupport 类),我的所有模型都可以扩展,但是考虑到Jackson似乎是如何从对象定义中派生JSON键的,我必须使用类似Javaassist 的东西来向基本响应类添加字段。在运行时-一个肮脏的黑客,我想尽可能远离人类。有没有更简单的方法来完成这个?不幸的是,一年后的响应中可能会有数量可变的顶级JSON对象,因此我不能使用像Jackson的
SerializationConfig.Feature.WRAP_ROOT_VALUE 这样的东西,因为它将所有东西都包装成一个根级别的对象(据我所知)。对于类级别(而不仅仅是方法和字段级别),是否有类似于
@JsonProperty 的东西?
有几种可能性。
您可以使用
1 2 3 4 5 6 7 8 | 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)); |
您可以使用
1 2 3 | ObjectMapper mapper = new ObjectMapper(); ObjectWriter writer = mapper.writer().withRootName(root); result = writer.writeValueAsString(object); |
号
这里有一个推广这个系列化的建议。
处理简单对象的类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | 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; } } |
要处理集合的类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | 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; } } |
。
以及一个示例应用程序:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | 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注释
1 | @JsonTypeInfo(include=As.WRAPPER_OBJECT, use=JsonTypeInfo.Id.NAME) |
。
就我个人而言,我不介意额外的DTO类,您只需要创建它们一次,而且几乎没有维护成本。如果您需要进行mockmvc测试,您很可能需要这些类来反序列化JSON响应以验证结果。
正如您可能知道的,Spring框架处理httpMessageConverter层中对象的序列化/反序列化,因此这是更改对象序列化方式的正确位置。
如果不需要反序列化响应,则可以创建通用包装器和自定义httpmessageconverter(并将其放在消息转换器列表中映射jackson2httpmessageconverter之前)。这样地:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | 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,该配置通过覆盖
我想你在找定制的杰克逊序列化程序。使用简单的代码实现,相同的对象可以在不同的结构中序列化。
例如:https://stackoverflow.com/a/10835504/814304http://www.davismol.net/2015/05/18/jackson-create-and-register-a-custom-json-serializer-with-stdserializer-and-simplemodule-classes/
Jackson不太支持动态/变量JSON结构,所以任何实现这种功能的解决方案都会非常糟糕,正如您所提到的。据我所知,从我所看到的来看,标准和最常见的方法是使用像您目前这样的包装类。包装器类确实是组合在一起的,但是如果您对自己的固有能力有创造性,您可能会发现类之间的一些共性,从而减少包装器类的数量。否则,您可能需要编写一个自定义框架。