Spring Boot: 在动态父对象中包装 JSON 响应

10 浏览
0 Comments

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 List users;
    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(而不仅仅是方法和字段级别)之类的东西吗?

0
0 Comments

问题的原因是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
  
  public  T 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没有更好的支持的情况下,是一种可行的解决方案。

0
0 Comments

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;
        Map map = 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,我们可以将对象包装在动态的父对象中,满足特定的需求。

0
0 Comments

Spring Boot: Wrapping JSON response in dynamic parent objects

在Spring Boot中,有时候我们需要将JSON响应包装在动态的父对象中。下面是一些可能的解决方案和示例代码。

一种可能的解决方案是使用java.util.Map来实现:

List userResources = 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框架。

0