简体   繁体   中英

Modify REST API response to only include fields requested in query parameter

I want to send response containing only requested fields for GET api. Reducing total size of response.

Example, Default behavior, when to specific fields requested

GET /v1/users/

{
   "data" : 
     [
        {
            "name" : "User1",
            "phone" : "800-999-9999",
            "city" : "XYZ1",
            "country" : "PQR1"
        },
        {
            "name" : "User2",
            "phone" : "800-999-9999",
            "city" : "XYZ2",
            "country" : "PQR2"
         }
     ]
}

Use case, where fields needed are passed as query parameters

GET /v1/users/?fields=name,city

{
   "data" : 
     [
        {
            "name" : "User1",
            "city" : "XYZ1"
        },
        {
            "name" : "User2",
            "city" : "XYZ2"
         }
     ]
}

Came across " https://github.com/monitorjbl/json-view ". But was shot down by the team.

How can I implement this functionality using spring boot? How do organizations using java microservices implement this feature? Thanks!

Method 1: Convert objects to Map, remove surplus keys, convert Map to Json

@GetMapping("/v1/users")
@ResponseBody
public List<Map> getUsers(@RequestParam(required = false) Set<String> fields) { 
    List<User> users = ...;
    ObjectMapper oMapper = new ObjectMapper();
    return users.stream().map(u -> oMapper.convertValue(u, Map.class))
       .peek(map -> { 
          if (fields != null) map.keySet().retainAll(fields)
       })
       .collect(Collectors.toList());
}

Method 2: Use Gson

@GetMapping("/v1/users")
@ResponseBody
public String getUsers(@RequestParam(required = false) Set<String> fields) { 
    List<User> users = ...;
    Gson gson = new GsonBuilder()
            .setExclusionStrategies(new ExclusionStrategy() {
                @Override
                public boolean shouldSkipField(FieldAttributes f) {
                    return f.getDeclaringClass().equals(User.class)
                             && fields != null 
                             && !fields.contains(f.getFieldName())
                            ;
                }

                @Override
                public boolean shouldSkipClass(Class<?> clazz) {
                    return false;
                }
            }).create();
    return gson.toJson(users); 
}

Came across " https://github.com/monitorjbl/json-view ". But was shot down by the team.

Btw, Jackson has @JsonView annotation support but it does not allow to include/exclude fields dynamically as you want. You can define fields subset and then return these subsets

public class Views {
    public static class Short {
    }
}

public class User {
    @JsonView(Views.Short.class)
    public String city;

    @JsonView(Views.Short.class)
    public String name;

    public String phone;

    public String country;
}

@GetMapping("/v1/users")
@ResponseBody
public String getUsers(@RequestParam(required = false) boolean shortView) { 
    List<User> users = ...;
    ObjectMapper mapper = new ObjectMapper();
    if (shortView) mapper = mapper.writerWithView(Views.Short.class);
    return mapper.writeValueAsString(users);
}

Here is an another approach:

Assumption 1: I believe its better to define return type of controller to be a domain object and let spring deal alone with conversion of it to JSON (unlike @Alexander Pavlov's answer). IMO its better for general code readability and for tools like swagger that introspect the controller and should know what should be returned.

Assumption 2: You don't have primitives in your User class.

Ok, now the controller obviously calls some "service" that makes some queries to the db, or in general does some logic that eventually produces the list of users (the list that you return to the client). The return object should be something like this:

class Users {

     private List<User> data;
}

class User { // + constructors, getters, setters, etc.
   private String name;
   private String city;
   private String phone;
   private String country;    
}

In this case in the service you can place "null" in field that you do not want to return:

User user = new User("John", "LA", null, null); // you only have name and city

Now you can place the annotation @JsonInclude(Include.NON_NULL) on class User and this will instruct Jackson to not include fields that have value 'null' into serialized response at all.

If you want this behaviour to be applied globally, you can configure ObjectMapper bean:

 ObjectMapper mapper = new ObjectMapper();
 mapper.setSerializationInclusion(Include.NON_NULL);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM