我有一个REST API规范,它与后端微服务进行通信,返回以下值:
On" collections"回复(例如GET /用户):
{
users: [
{
... // single user object data
}
],
links: [
{
... // single HATEOAS link object
}
]
}
On"单个对象"回复(例如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
}
这种方法的优点是我们可以只公开 来自我们拥有公共getter的内部使用模型的字段,而不公开其他字段。然后,对于集合响应,我将有以下包装类:
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规范。这种方法的优点是我们只公开那些我们想要公开的字段。重要的缺点是我有一个 ton 的包装类飞来飞去,除了被Jackson读取以产生正确格式化的响应之外,它没有执行任何可识别的逻辑任务。
我的问题如下:
我怎样才能概括这种方法?理想情况下,我希望我的所有模型都可以扩展一个BaseSingularResponse
类(可能是BaseCollectionsResponse extends ResourceSupport
类),但是看看Jackson似乎从对象定义中获取JSON键,我会必须使用类似Javaassist
之类的东西来在运行时向基本响应类添加字段 - 这是一种我希望尽可能远离人类的肮脏黑客。
有没有更简单的方法来实现这一目标?不幸的是,我可能在一年后的响应中有可变数量的顶级JSON对象,所以我不能使用像Jackson这样SerializationConfig.Feature.WRAP_ROOT_VALUE
之类的内容,因为它将所有内容包装成一个根级对象(据我所知)。
对于类级别(或者只是方法和字段级别),是否有类似@JsonProperty
的内容?
答案 0 :(得分:7)
有几种可能性。
您可以使用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());
}
}
示例应用:
@JsonTypeInfo
您还可以在课程级别使用杰克逊注释@JsonTypeInfo(include=As.WRAPPER_OBJECT, use=JsonTypeInfo.Id.NAME)
class A{
public:
void doThings(const B& b);
}
class B{
B(int i, int j);
}
int main{
A a;
a.doThings(B(1,2)); //Is this OK?
}
答案 1 :(得分:4)
就个人而言,我不介意额外的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);
}
}
然后,您需要在弹簧配置中注册自定义HttpMessageConverter,该配置通过覆盖WebMvcConfigurerAdapter
来扩展configureMessageConverters()
。请注意,这样做会禁用转换器的默认自动检测,因此您可能必须自己添加默认值(查看WebMvcConfigurationSupport#addDefaultHttpMessageConverters()
的Spring源代码以查看默认值。如果您扩展WebMvcConfigurationSupport
而不是{{ 1}}你可以直接调用WebMvcConfigurerAdapter
(如果我需要自定义任何东西,我个人优先使用addDefaultHttpMessageConverters
而不是WebMvcConfigurationSupport
,但这样做有一些小问题,你可能会读到关于其他文章。
答案 2 :(得分:1)
杰克逊并没有对动态/变量JSON结构提供大量支持,因此任何完成此类操作的解决方案都会像你提到的那样非常黑客。据我所知和我所见,标准和最常用的方法是使用像你一样的包装类。包装类确实会加起来,但是如果你有创造力,那么你可以在类之间找到一些共性,从而减少包装类的数量。否则,您可能正在考虑编写自定义框架。
答案 3 :(得分:1)