Spring Boot: 在动态父对象中包装 JSON 响应
Spring Boot: 在动态父对象中包装 JSON 响应
我有一个与后端微服务通信的REST API规范,后端微服务返回以下值:\n在“集合”响应中(例如GET /users):\n{\n users: [\n {\n ... // 单个用户对象数据\n }\n ],\n links: [\n {\n ... // 单个HATEOAS链接对象\n }\n ]\n}\n
\n在“单个对象”响应中(例如GET /users/{userUuid}):\n
{ user: { ... // {userUuid}用户对象 } }
\n选择这种方法是为了使单个响应可扩展(例如,如果GET /users/{userUuid}在以后的某个时候获得了附加的查询参数,如?detailedView=true,我们将拥有附加的请求信息)。\n从根本上说,我认为这是一种减少API更新之间破坏性变更的可行方法。然而,将这个模型转化为代码的过程非常艰难。\n假设对于单个响应,我有一个用于单个用户的API模型对象:\n
public class SingleUserResource { private MicroserviceUserModel user; public SingleUserResource(MicroserviceUserModel user) { this.user = user; } public String getName() { return user.getName(); } // 其他我们希望公开的字段的getter方法 }
\n这种方法的优点是,我们可以仅公开从内部使用的模型中具有公共getter方法的字段,而不是其他字段。然后,对于集合响应,我将有以下包装类:\n
public class UsersResource extends ResourceSupport { @JsonProperty("users") public final Listusers; public UsersResource(List users) { // 将每个用户添加为SingleUserResource } }
\n对于单个对象响应,我们将有以下内容:\n
public class UserResource { @JsonProperty("user") public final SingleUserResource user; public UserResource(SingleUserResource user) { this.user = user; } }
\n这将生成按照帖子顶部的API规范格式化的JSON响应。这种方法的优点是,我们只公开我们想要公开的字段。缺点是,我有很多没有明显逻辑任务的包装类,除了被Jackson读取以生成正确格式的响应之外。\n我的问题是:\n1.我如何可能将这种方法泛化?理想情况下,我希望有一个单一的BaseSingularResponse类(可能还有一个BaseCollectionsResponse扩展自ResourceSupport类),所有我的模型都可以扩展这个类,但是看起来Jackson似乎从对象定义中派生JSON键,我将不得不使用类似Javaassist的东西在运行时向基本响应类添加字段 - 这是一个肮脏的技巧,我希望能尽量远离它。\n2.有没有更简单的方法来完成这个任务?不幸的是,也许从一年后开始,响应中的顶级JSON对象数量可能是可变的,所以我不能使用像Jackson的SerializationConfig.Feature.WRAP_ROOT_VALUE这样的东西,因为它将所有内容包装到单个根级对象中(据我所知)。\n3.也许还有类级别的@JsonProperty(而不仅仅是方法和字段级别)之类的东西吗?
问题的原因是Jackson对于动态/可变的JSON结构支持有限,所以任何实现类似功能的解决方案都会比较hacky。目前我所知道的和我见过的最常用的方法是使用包装类,就像你目前正在使用的方式一样。包装类的数量确实会增加,但如果你在继承方面有创意,你可能能够找到一些类之间的共性,从而减少包装类的数量。否则,你可能需要编写一个自定义框架来解决这个问题。
解决方法是使用包装类来将动态/可变的JSON结构包裹起来。通过创建适当的包装类,可以将JSON数据结构包装在一个动态的父对象中。这样,就不需要为每个不同的JSON结构编写单独的类,而是可以在运行时根据需要创建相应的包装类。
例如,假设我们有以下JSON结构:
{
"type": "person",
"data": {
"name": "John Doe",
"age": 30
}
}
我们可以创建一个名为"Wrapper"的包装类,其中包含一个"type"字段和一个"data"字段,用于包装任意类型的JSON数据。然后,我们可以根据"type"字段的值将"data"字段解析为相应的类型。
下面是一个示例代码:
public class Wrapper { private String type; private Object data; // getter and setter methods publicT getData(Class clazz) { return new ObjectMapper().convertValue(data, clazz); } }
使用上述包装类,我们可以将任意类型的JSON数据包装在一个动态的父对象中,然后通过调用`getData`方法来获取原始的数据对象。例如:
Wrapper wrapper = new ObjectMapper().readValue(jsonString, Wrapper.class); if ("person".equals(wrapper.getType())) { Person person = wrapper.getData(Person.class); // do something with person object } else if ("company".equals(wrapper.getType())) { Company company = wrapper.getData(Company.class); // do something with company object }
通过这种方式,我们可以灵活地处理动态/可变的JSON结构,而不需要为每个不同的JSON结构编写单独的类。这种方法虽然有一些hacky,但是在Jackson没有更好的支持的情况下,是一种可行的解决方案。
Spring Boot: Wrapping JSON response in dynamic parent objects
在使用Spring Boot开发过程中,有时候我们需要在返回的JSON响应中添加一个动态的父对象。然而,Spring Boot默认情况下并不支持直接将对象包装在动态的父对象中。本文将介绍问题的出现原因以及解决方法。
出现原因:
Spring Boot框架在处理对象的序列化和反序列化时使用了HttpMessageConverter层。默认情况下,Spring Boot使用MappingJackson2HttpMessageConverter将对象转换为JSON响应。然而,它并不支持将对象包装在动态的父对象中。
解决方法:
为了解决这个问题,我们可以创建一个通用的包装类和自定义的HttpMessageConverter。
首先,创建一个名为JSONWrapper的包装类,该类包含一个name和一个object属性,用于动态的父对象的命名和实际的对象。
public class JSONWrapper { public final String name; public final Object object; public JSONWrapper(String name, Object object) { this.name = name; this.object = object; } }
然后,创建一个继承自MappingJackson2HttpMessageConverter的自定义HttpMessageConverter,名为JSONWrapperHttpMessageConverter。在该类中,我们重写writeInternal方法,将传入的JSONWrapper对象转换为Map对象,再调用父类的writeInternal方法将其转换为JSON响应。
public class JSONWrapperHttpMessageConverter extends MappingJackson2HttpMessageConverter { 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; Mapmap = new HashMap<>(); map.put(wrapper.name, wrapper.object); super.writeInternal(map, type, outputMessage); } protected boolean supports(Class> clazz) { return clazz.equals(JSONWrapper.class); } }
最后,我们需要在Spring Boot的配置文件中注册自定义的HttpMessageConverter。如果使用的是WebMvcConfigurerAdapter,需要重写configureMessageConverters方法,并在其中添加自定义的HttpMessageConverter。需要注意的是,这样做会禁用默认的自动检测转换器的功能,所以我们可能需要手动添加默认的转换器。
@Configuration public class WebMvcConfig extends WebMvcConfigurerAdapter { @Override public void configureMessageConverters(List> converters) { converters.add(new JSONWrapperHttpMessageConverter()); converters.add(new MappingJackson2HttpMessageConverter()); } }
以上就是解决Spring Boot中动态包装JSON响应的问题的方法。通过自定义HttpMessageConverter,我们可以将对象包装在动态的父对象中,满足特定的需求。
Spring Boot: Wrapping JSON response in dynamic parent objects
在Spring Boot中,有时候我们需要将JSON响应包装在动态的父对象中。下面是一些可能的解决方案和示例代码。
一种可能的解决方案是使用java.util.Map来实现:
ListuserResources = new ArrayList<>(); userResources.add(new UserResource("John")); userResources.add(new UserResource("Jane")); userResources.add(new UserResource("Martin")); Map > usersMap = new HashMap<>(); 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);
为了通用化这种序列化,可以创建一个处理简单对象的BaseSingularResponse类和一个处理集合的BaseCollectionsResponse类。示例如下:
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> { 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> { public UsersResource() { super("users", new ArrayList ()); } } 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(include = JsonTypeInfo.As.WRAPPER_OBJECT, use = JsonTypeInfo.Id.NAME) public class MyClass { // class fields and methods }
需要注意的是,如果在对象内部进行序列化,那么就违背了Spring MVC的设计原则。序列化应该是限定在HttpMessageConverter层级的。因此,在使用以上解决方案时,需要考虑是否适用于Spring MVC框架。