8

I'm using spring to create a restful api, so far so good, my question is how to model the fields that go to the response dynamically?

Thats the controller that i'm using:

@Controller

public class AlbumController {

@Autowired
private MusicService musicService;

@RequestMapping(value = "/albums", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public Collection<Resource<Album>> getAllAlbums() {

    Collection<Album> albums = musicService.getAllAlbums();
    List<Resource<Album>> resources = new ArrayList<Resource<Album>>();
    for (Album album : albums) {
        resources.add(this.getAlbumResource(album));
    }
    return resources;

}

@RequestMapping(value = "/album/{id}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public Resource<Album> getAlbum(@PathVariable(value = "id") String id) {

    Album album = musicService.getAlbum(id);
    return getAlbumResource(album);

}

private Resource<Album> getAlbumResource(Album album) {

    Resource<Album> resource = new Resource<Album>(album);


    // Link to Album
    resource.add(linkTo(methodOn(AlbumController.class).getAlbum(album.getId())).withSelfRel());
    // Link to Artist
    resource.add(linkTo(methodOn(ArtistController.class).getArtist(album.getArtist().getId())).withRel("artist"));
    // Option to purchase Album
    if (album.getStockLevel() > 0) {
        resource.add(linkTo(methodOn(AlbumController.class).purchaseAlbum(album.getId())).withRel("album.purchase"));
    }

    return resource;

}

@RequestMapping(value = "/album/purchase/{id}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public Resource<Album> purchaseAlbum(@PathVariable(value = "id") String id) {

    Album a = musicService.getAlbum(id);
    a.setStockLevel(a.getStockLevel() - 1);
    Resource<Album> resource = new Resource<Album>(a);
    resource.add(linkTo(methodOn(AlbumController.class).getAlbum(id)).withSelfRel());
    return resource;

}
}

And the model:

public class Album {

private final String id;
private final String title;
private final Artist artist;
private int stockLevel;

public Album(final String id, final String title, final Artist artist, int stockLevel) {
    this.id = id;
    this.title = title;
    this.artist = artist;
    this.stockLevel = stockLevel;
}

public String getId() {
    return id;
}

public String getTitle() {
    return title;
}

public Artist getArtist() {
    return artist;
}

public int getStockLevel() {
    return stockLevel;
}

public void setStockLevel(int stockLevel) {
    this.stockLevel = stockLevel;
}
}
royhowie
  • 11,075
  • 14
  • 50
  • 67
danillosl
  • 519
  • 3
  • 7
  • 13
  • If your use-case is that in different responses you want different subsets of properties, you can use [JSON views](http://wiki.fasterxml.com/JacksonJsonViews) in jackson. – beerbajay Apr 24 '15 at 06:38
  • Seems what I want, but if I will using that method i'll have to control the marshalling manually, therefore bypassing spring, you have some example of this? – danillosl Apr 24 '15 at 16:18
  • Per [this blog post](https://spring.io/blog/2014/12/02/latest-jackson-integration-improvements-in-spring), you can annotate your controllers with `@JsonView(MyView.class)` and spring will apply the view for – beerbajay Apr 24 '15 at 21:30
  • Yes, but that way I can only choose a view by method, and i'm not able to choose the view programmatically. – danillosl Apr 24 '15 at 21:37
  • Why do you need to do this? – beerbajay Apr 25 '15 at 01:11
  • 1
    I need to filter data depending on the authorization, but I don`t know if this is a good way to handle authorization. – danillosl Apr 25 '15 at 01:22
  • What do you mean dynamically? Should the json display only the fields that are filled in from the response? Sounds like you're looking for introspection – William Falcon May 08 '15 at 23:06

1 Answers1

6

I'm struggling with this requirement, too.

The following code is a workaround using the JsonViews already mentioned by @beerbajay in the comment to your question:

http://wiki.fasterxml.com/JacksonJsonViews

I'm using Spring Boot 1.2.3.

Views.java

package demo;

public class Views {

    static interface Public {}

    static interface Internal extends Public {}
}

Album.java

package demo;

import com.fasterxml.jackson.annotation.JsonView;

public class Album {

    @JsonView(Views.Public.class)
    private String id;

    @JsonView(Views.Public.class)
    private String title;

    @JsonView(Views.Public.class)
    private String artist;

    @JsonView(Views.Internal.class)
    private String secret;

    public Album(String id, String title, String artist, String secret) {
        this.id = id;
        this.title = title;
        this.artist = artist;
        this.secret = secret;
    }

}

AlbumController.java

package demo;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectWriter;

@RestController
public class AlbumController {

    @Autowired
    ObjectMapper mapper;

    @RequestMapping(value = "/album", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
    public String getAlbum() throws JsonProcessingException {
        Album foo = new Album("1", "foo", "John Doe", "secretProperty");

        // replace the following value with runtime logic of your choice,
        // e.g. role of a user
        boolean forInternal = false;

        ObjectWriter viewWriter;
        if (forInternal) {
            viewWriter = mapper.writerWithView(Views.Internal.class);
        } else {
            viewWriter = mapper.writerWithView(Views.Public.class);
        }

        return viewWriter.writeValueAsString(foo);
    }
}

So the key is to use jackson's ObjectMapper and ObjectWriter to generate a string of the json representation of your object.

Feels kind of ugly to me, but works. Certainly the question remains how this scales when defining more than one RequestMapping.

Thomas Traude
  • 840
  • 7
  • 17
  • Seems the best way to select json templates, thanks for the answer, another way would be using the methods described https://spring.io/blog/2014/12/02/latest-jackson-integration-improvements-in-spring. – danillosl May 09 '15 at 00:38
  • 1
    I know this is very old answer but you can also use @JsonIgnore on fields which you want to ignore from response body. – Vimal Bera Jan 09 '18 at 10:48