java Spring Boot:在动态父对象中包装 JSON 响应
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/41880572/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Spring Boot: Wrapping JSON response in dynamic parent objects
提问by user991710
I have a REST API specification that talks with back-end microservices, which return the following values:
我有一个与后端微服务通信的 REST API 规范,它返回以下值:
On "collections" responses (e.g. GET /users) :
关于“集合”响应(例如 GET /users):
{
users: [
{
... // single user object data
}
],
links: [
{
... // single HATEOAS link object
}
]
}
On "single object" responses (e.g. GET /users/{userUuid}
) :
关于“单个对象”的响应(例如GET /users/{userUuid}
):
{
user: {
... // {userUuid} user object}
}
}
This approach was chosen so that single responses would be extensible (for example, maybe if GET /users/{userUuid}
gets an additional query parameter down the line such at ?detailedView=true
we would have additional request information).
选择这种方法是为了使单个响应具有可扩展性(例如,也许如果在线下GET /users/{userUuid}
获取额外的查询参数,那么?detailedView=true
我们将获得额外的请求信息)。
Fundamentally, I think it is an OK approach for minimizing breaking changes between API updates. However, translating this model to code is proving very arduous.
从根本上说,我认为这是最小化 API 更新之间的重大更改的好方法。然而,将这个模型转化为代码是非常困难的。
Let's say that for single responses, I have the following API model object for a single user:
假设对于单个响应,我为单个用户提供以下 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
}
The advantage of this method is that we can expose onlythe fields from the internally used models for which we have public getters, but not others. Then, for collections responses I would have the following wrapper class:
这种方法的优点是我们可以只公开内部使用的模型中的字段,我们有公共 getter,而不能公开其他模型。然后,对于集合响应,我将拥有以下包装类:
public class UsersResource extends ResourceSupport {
@JsonProperty("users")
public final List<SingleUserResource> users;
public UsersResource(List<MicroserviceUserModel> users) {
// add each user as a SingleUserResource
}
}
For single objectresponses, we would have the following:
对于单个对象响应,我们将有以下内容:
public class UserResource {
@JsonProperty("user")
public final SingleUserResource user;
public UserResource(SingleUserResource user) {
this.user = user;
}
}
This yields JSON
responses which are formatted as per the API specification at the top of this post. The upside of this approach is that we only expose those fields that we wantto expose. The heavy downside is that I have a tonof wrapper classes flying around that perform no discernible logical task aside from being read by Hymanson to yield a correctly formatted response.
这会产生JSON
按照本文顶部的 API 规范格式化的响应。这种方法的好处是我们只公开那些我们想要公开的字段。严重的缺点是我有大量的包装类四处乱飞,除了被 Hymanson 读取以产生正确格式的响应之外,它们没有执行任何可辨别的逻辑任务。
My questions are the following:
我的问题如下:
How can I possibly generalize this approach? Ideally, I would like to have a single
BaseSingularResponse
class (and maybe aBaseCollectionsResponse extends ResourceSupport
class) that all my models can extend, but seeing how Hymanson seems to derive the JSON keys from the object definitions, I would have to user something likeJavaassist
to add fields to the base response classes at Runtime - a dirty hack that I would like to stay as far away from as humanly possible.Is there an easier way to accomplish this? Unfortunately, I may have a variable number of top-level JSON objects in the response a year from now, so I cannot use something like Hymanson's
SerializationConfig.Feature.WRAP_ROOT_VALUE
because that wraps everythinginto a single root-level object (as far as I am aware).Is there perhaps something like
@JsonProperty
for class-level (as opposed to just method and field level)?
我怎么可能推广这种方法?理想情况下,我希望我的所有模型都可以扩展一个
BaseSingularResponse
类(也可能是一个BaseCollectionsResponse extends ResourceSupport
类),但是看到 Hymanson 似乎如何从对象定义中派生出 JSON 密钥,我将不得不使用诸如Javaassist
向运行时的基本响应类 - 我希望尽可能远离人类的肮脏黑客。有没有更简单的方法来实现这一点?不幸的是,从现在起一年后,我的响应中可能会有可变数量的顶级 JSON 对象,所以我不能使用类似 Hymanson 的东西,
SerializationConfig.Feature.WRAP_ROOT_VALUE
因为它将所有内容都包装到一个单一的根级对象中(据我所知)。是否有类似
@JsonProperty
类级别的东西(而不仅仅是方法和字段级别)?
采纳答案by Arnaud Develay
There are several possibilities.
有几种可能性。
You can use a java.util.Map
:
您可以使用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));
You can use ObjectWriter
to wrap the response that you can use like below:
您可以使用ObjectWriter
来包装您可以使用的响应,如下所示:
ObjectMapper mapper = new ObjectMapper();
ObjectWriter writer = mapper.writer().withRootName(root);
result = writer.writeValueAsString(object);
Here is a proposition for generalizing this serialization.
这是一个概括这种序列化的提议。
A class to handle simple 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;
}
}
A class to handle collection:
处理集合的类:
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;
}
}
And a sample application:
和一个示例应用程序:
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());
}
}
You can also use the Hymanson annotation @JsonTypeInfo
in a class level
您还可以@JsonTypeInfo
在类级别使用 Hymanson 注释
@JsonTypeInfo(include=As.WRAPPER_OBJECT, use=JsonTypeInfo.Id.NAME)
回答by Klaus Groenbaek
Personally I don't mind the additional Dto classes, you only need to create them once, and there is little to no maintenance cost. And If you need to do MockMVC tests, you will most likely need the classes to deserialize your JSON responses to verify the results.
我个人不介意额外的 Dto 类,您只需要创建它们一次,并且几乎没有维护成本。如果您需要进行 MockMVC 测试,您很可能需要这些类来反序列化您的 JSON 响应以验证结果。
As you probably know the Spring framework handles the serialization/deserialization of objects in the HttpMessageConverter Layer, so that is the correct place to change how objects are serialized.
您可能知道 Spring 框架处理 HttpMessageConverter 层中对象的序列化/反序列化,因此这是更改对象序列化方式的正确位置。
If you don't need to deserialize the responses, it is possible to create a generic wrapper, and a custom HttpMessageConverter (and place it before MappingHymanson2HttpMessageConverter in the message converter list). Like this:
如果您不需要反序列化响应,则可以创建一个通用包装器和一个自定义 HttpMessageConverter(并将其放在消息转换器列表中的 MappingHymanson2HttpMessageConverter 之前)。像这样:
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 MappingHymanson2HttpMessageConverter {
@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);
}
}
You then need to register the custom HttpMessageConverter in the spring configuration which extends WebMvcConfigurerAdapter
by overriding configureMessageConverters()
. Be aware that doing this disables the default auto detection of converters, so you will probably have to add the default yourself (check the Spring source code for WebMvcConfigurationSupport#addDefaultHttpMessageConverters()
to see defaults. if you extend WebMvcConfigurationSupport
instead WebMvcConfigurerAdapter
you can call addDefaultHttpMessageConverters
directly (Personally I prefere using WebMvcConfigurationSupport
over WebMvcConfigurerAdapter
if I need to customize anything, but there are some minor implications to doing this, which you can probably read about in other articles.
然后,您需要在 spring 配置中注册自定义 HttpMessageConverter,该配置WebMvcConfigurerAdapter
通过覆盖configureMessageConverters()
. 请注意,这样做会禁用转换器的默认自动检测,因此您可能必须自己添加默认值(检查 Spring 源代码WebMvcConfigurationSupport#addDefaultHttpMessageConverters()
以查看默认值。如果您扩展,WebMvcConfigurationSupport
则WebMvcConfigurerAdapter
可以addDefaultHttpMessageConverters
直接调用(我个人更喜欢使用WebMvcConfigurationSupport
over WebMvcConfigurerAdapter
if I需要自定义任何东西,但这样做有一些小影响,您可能会在其他文章中阅读到。
回答by iMysak
I guess you are looking for Custom Hymanson Serializer. With simple code implementation same object can be serialized in different structures
我猜您正在寻找Custom Hymanson Serializer。通过简单的代码实现,可以将相同的对象序列化为不同的结构
some example: https://stackoverflow.com/a/10835504/814304http://www.davismol.net/2015/05/18/Hymanson-create-and-register-a-custom-json-serializer-with-stdserializer-and-simplemodule-classes/
一些例子:https: //stackoverflow.com/a/10835504/814304 http://www.davismol.net/2015/05/18/Hymanson-create-and-register-a-custom-json-serializer-with- stdserializer-and-simplemodule-classes/
回答by Trevor Bye
Hymanson doesn't have a lot of support for dynamic/variable JSON structures, so any solution that accomplishes something like this is going to be pretty hacky as you mentioned. As far as I know and from what I've seen, the standard and most common method is using wrapper classes like you are currently. The wrapper classes do add up, but if you get creative with your inheretence you may be able to find some commonalities between classes and thus reduce the amount of wrapper classes. Otherwise you might be looking at writing a custom framework.
Hymanson 对动态/可变 JSON 结构的支持并不多,因此任何完成此类操作的解决方案都将像您提到的那样非常笨拙。据我所知,根据我所看到的,标准和最常见的方法是使用像您目前这样的包装类。包装类确实加起来了,但是如果您对自己的继承有创意,您可能能够找到类之间的一些共性,从而减少包装类的数量。否则,您可能正在考虑编写自定义框架。