18

I'm trying to use Spring-data-rest with spring-data-mongodb to expose read-only resources.

The problem I met, is that I want to have different views of my documents. Let's say I have some private information in a document, I don't want to expose them publicly.

So I tried several ways. I read this post https://spring.io/blog/2014/12/02/latest-jackson-integration-improvements-in-spring describing how to use JsonView in order to select the fields we want to expose.

I've tried like this :

@RepositoryRestResource(collectionResourceRel = "recommandation", path =    "recommandations")
interface RecommandationRepository extends MongoRepository<Recommendation,   ObjectId> {

@Override
@JsonView(View.Public.class)
Iterable<Recommendation> findAll(Iterable<ObjectId> objectIds);
... // other find methods
}

It doesn't works. It is however said in the comments : https://spring.io/blog/2014/12/02/latest-jackson-integration-improvements-in-spring#comment-1725671983 The answer suggests to use @Projections However @Projections result in url like that : "…/recommandations{?projection}" It means that the projection is just an option, so the full object is still exposed.

There is another method described here https://github.com/spring-projects/spring-data-rest/wiki/Configuring-the-REST-URL-path It suggests to use @RestResource(exported = false) annotation for the fields we don't want to expose.

But it's not flexible. If I want to expose a public read-only API and a private full access API. This annotation can't be disabled per api.

Is there another suggestion ?

pushkin
  • 9,575
  • 15
  • 51
  • 95
Hugo Lassiège
  • 986
  • 1
  • 11
  • 26
  • 1
    How do you distinguish between the public and private APIs? Do you have two repositories for the same class? How about two classes then? – a better oliver Feb 04 '15 at 17:24

2 Answers2

23

The important point is that Spring Data REST uses Jackson serialization parameters based on the domain object, not the repository definition. One simple way to hide a particular field from appearing in the JSON is like this:

@Entity
public class User {

    @Id @GeneratedValue
    private Long id;

    private String name;

    @JsonIgnore
    private String password;
    ...

In this example, my User object will NEVER export a password field no matter how this entity is used. Jackson supports either putting this on the field, or putting on the corresponding getter method.

When you put @JsonIgnore in the domain model, it makes it the default definition. Projections are options to alter what fields get rendered. Look at the following example:

@Projection(name = "noImages", types = {Item.class})
public interface NoImages {

    public Link getHtmlUrl();

}

This project can only be used when rendering Item domain objects. It isn't the default view, but instead an option to use via ?projection=noImages. But don't forget: when it comes time to apply Jackson serialization, the project will override the domain model's settings. This means you can write a projection for that User object up above and have it include String getPassword(). This would override the domain model's default setting an in turn export a password. Responsibility is yours.

One last thing. Spring Data REST supports Excerpt Projections. The most common use case is where you have a Customer object related to an Address object. By default, the relationship to see a customer's address would show a URI to navigate. But if you are wanting the address information all the time, you can avoid this extra GET operation by creating a projection that renders the address details. Then you can configure that for Customer objects, turn on this projection by default and essentially inline the address details whenever you fetch a customer record.

I realize the reference docs aren't quite up to date on all these details. You can track our progress to update the docs suitably as follows:

There is also a bug in that the ALPS metadata from Spring Data REST also needs to filter out domain fields tagged with @JsonIgnore (see https://jira.spring.io/browse/DATAREST-463)

P.S. @RestResource is deprecated and not the recommended approach to setting what fields get exported. Instead, use @JsonIgnore as shown earlier.

gregturn
  • 2,625
  • 3
  • 25
  • 40
  • 4
    `@JsonIgnore` completely removes fields from the response. How to return the read-only fields in response but just let update them? – Stackee007 Feb 04 '15 at 21:33
  • That's a Jackson configuration issue: see [this question](http://stackoverflow.com/questions/16019834/ignoring-property-when-deserializing) for details. – Oliver Drotbohm Feb 05 '15 at 06:37
  • 4
    If I need an internal representation (allowing this field to be displayed and modified) and a public read-only representation that don't want to show this field at all, @JsonIgnore will not help me right ? It will remove the field for both representations, no ? – Hugo Lassiège Feb 05 '15 at 18:07
  • 1
    I had similar requirements to hide remove fields in spring data rest response dynamically. Was wondering if we can hook @jsonfilter for some entities via Jackson modules to work with spring data rest – Gaurav Rawat Jul 23 '15 at 14:29
  • 7
    @JsonProperty(access = JsonProperty.Access.WRITE_ONLY) – sofend Feb 16 '17 at 21:05
  • @sofend Please consider making you suggestion a new answer to this question as it was the only useful (and non-outdated) answer for me to this question. I'd certainly upvote it :-) – Johannes Rudolph Apr 10 '17 at 13:34
21

Per @johannes-rudolph suggestion...

Consider applying this setting to the field (or property if you're mapping from accessors):

@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)    
private String password;

This does what the access value implies: marking the associated field as write-only. Thus the value can be set but not retrieved via the Jackson/JSON serialized form.

sofend
  • 647
  • 8
  • 17