计算机系统应用教程网站

网站首页 > 技术文章 正文

如何在SpringBoot中动态过滤JSON响应正文

btikc 2024-10-13 01:51:16 技术文章 9 ℃ 0 评论

介绍:

在现代软件开发中,构建高效、灵活的 REST API 是一个常见的需求。这些 API 的一个关键方面是有效处理请求响应数据。在某些情况下,您可能需要根据客户端要求或其他条件过滤 JSON 响应字段。

假设一个带有大量 REST API 的 Spring Boot 应用程序,并且每个 API 都可以返回各种 JSON 响应字段。要求是动态过滤响应正文,仅返回“fields”请求参数中指定的字段。但是“fields”参数应该是可选的,如果省略,则应返回完整的响应。

响应示例:

完整的文档响应

GET http://localhost:8080/v1/resource/id
 
Response - 
{
    "user": "internal_s",
    "socialMedia": [
        "Weixin",
        "Douyin"
    ],
    "posts": [
        {
            "name": "Douyin",
            "id": "__id",
            "metadata": {
                "length": 100
            }
        }
    ],
    "mostUsed": "Weixin"
}

二.根据请求参数字段值中指定的值响应数据。

GET http://localhost:8080/v1/resource/id?fields=posts
 
 
Response -

{
"posts" : [ {
        "name" : "Douyin",
        "id" : "__id",
        "metadata" : {
                "length" : 100
            }
      } ]
}

三.根据指定名称响应数据。

GET http://localhost:8080/v1/resource/id?fields=posts[name]
 
Response -
 
{
"posts" : [ {
        "name" : "Douyin"
      } ]
}

为了实现动态 JSON 响应过滤,我们需要用到 Squiggly,一个JSON 响应过滤库。 Squiggly 提供了一种强大且简单的方法来根据特定字段过滤和修改 JSON 响应。并且还允许开发人员自定义他们想要接收的 JSON 请求参数。只需要指定所需的字段,Squiggly 会处理其余的事情。

下面我们来看一下示例代码,如何在 Spring Boot 应用程序中实现动态 JSON 响应过滤。我们有相当数量的 REST API,因此单独处理每个 API 中的更改并不是理想的解决方案。我们的目标是一种更通用的方法,不需要修改每个 API。

  • pom.xml中添加以下依赖项
   <dependency>
            <groupId>com.github.bohnman.squiggly</groupId>
            <artifactId>squiggly-spring-boot-starter-jackson</artifactId>
            <version>2.0.0-SNAPSHOT</version>
        </dependency>

实现 ResponseBodyAdvice接口并重写 beforeBodyWrite 方法使用 Squiggly过滤响应正文。

@ControllerAdvice
public class ResponseFieldFilterAdvice implements ResponseBodyAdvice {

    private static final String FIELDS_QUERY_PARAM = "fields";

    @Override
    public boolean supports(final MethodParameter methodParameter, final Class converter) {
        return true;
    }

    @Override
    public Object beforeBodyWrite(final Object body, final MethodParameter methodParameter, final MediaType mediaType, final Class selectedConverter, final ServerHttpRequest serverHttpRequest, final ServerHttpResponse serverHttpResponse) {
        if (body != null && serverHttpRequest.getURI().getQuery() != null) {
            final Hashtable<String, String[]> keyValuePair = parseQueryString(serverHttpRequest.getURI().getQuery());
            if (keyValuePair.containsKey(FIELDS_QUERY_PARAM)) {
                final ObjectMapper objectMapper = Squiggly.init(new ObjectMapper(), StringUtils.arrayToCommaDelimitedString(keyValuePair.get(FIELDS_QUERY_PARAM)));
                return SquigglyUtils.objectify(objectMapper, body, body.getClass());
            }
        }
        return body;
    }

    private Hashtable<String, String[]> parseQueryString(String s) {

        String valArray[] = null;
        if (s == null) {
            throw new IllegalArgumentException();
        }
        Hashtable<String, String[]> ht = new Hashtable<String, String[]>();
        StringBuilder sb = new StringBuilder();
        StringTokenizer st = new StringTokenizer(s, "&");
        while (st.hasMoreTokens()) {
            String pair = st.nextToken();
            int pos = pair.indexOf('=');
            if (pos == -1) {
                throw new IllegalArgumentException();
            }
            String key = parseName(pair.substring(0, pos), sb);
            String val = parseName(pair.substring(pos+1, pair.length()), sb);
            if (ht.containsKey(key)) {
                String oldVals[] = ht.get(key);
                valArray = new String[oldVals.length + 1];
                for (int i = 0; i < oldVals.length; i++) {
                    valArray[i] = oldVals[i];
                }
                valArray[oldVals.length] = val;
            } else {
                valArray = new String[1];
                valArray[0] = val;
            }
            ht.put(key, valArray);
        }

        return ht;
    }

    private static String parseName(String s, StringBuilder sb) {
        sb.setLength(0);
        for (int i = 0; i < s.length(); i++) {
            char c = s.charAt(i);
            switch (c) {
                case '+':
                    sb.append(' ');
                    break;
                case '%':
                    try {
                        sb.append((char) Integer.parseInt(s.substring(i+1, i+3),
                                16));
                        i += 2;
                    } catch (NumberFormatException e) {
                        throw new IllegalArgumentException();
                    } catch (StringIndexOutOfBoundsException e) {
                        String rest  = s.substring(i);
                        sb.append(rest);
                        if (rest.length()==2)
                            i++;
                    }

                    break;
                default:
                    sb.append(c);
                    break;
            }
        }

        return sb.toString();
    }
}
  1. 实现ResponseBodyAdvice接口:

我们创建了一个ResponseFieldFilterAdvice带有@@ControllerAdvice注解的类。此类实现了ResponseBodyAdvice接口,该接口允许我们在将响应正文发送到客户端之前对其进行自定义。

2. 支持所有API接口:

ResponseFieldFilterAdvice类中,我们重写了该supports方法以确保我们的自定义响应过滤应用于所有 REST API 接口。

3.beforeBodyWrite方法:

实现过滤核心在于beforeBodyWrite方法。在此方法中,我们检查 HTTP 请求是否为 GET 请求、响应正文不为 null,以及请求是否具有“fields”查询参数。

4. 应用 JSON 字段过滤器:

如果存在“fields”查询参数,我们使用 Squiggly 库来过滤响应正文。我们创建一个ObjectMapper配置有请求的字段并进行转换响应。最终的结果是仅包含指定字段的 JSON 响应。

总结:

通过 Squiggly库和实现ResponseBodyAdvice接口,我们在 Spring Boot 应用程序中实现了动态 JSON 响应过滤,而无需单独修改每个 API。该解决方案提供了灵活性,减少了冗余,并能根据客户端需求高效处理 JSON 响应字段。

Squiggly开源地址:https://github.com/bohnman/squiggly

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表