i.am.jabi
i.am.jabi

Reputation: 678

Exclude empty Hateoas "links" in the spring boot restapi response

I have a sample response class that extends RepresentationModel. In some scenarios I don't add any hateoas links in the response. In that case, I'm getting an empty links field in the json response

"links": []

I tried adding "JsonInclude.Include.NON_EMPTY" to the response class, but since the links field is final in RepresentationModel, it's still bringing empty links field in the response.

How can I avoid this empty links field in the response ?

Upvotes: 3

Views: 2865

Answers (3)

cyril
cyril

Reputation: 1005

update for 2024,with openapi generator for Spring boot i use the swagger.yaml, then on pom.xml, I just added the annotation Jackson non empty it works:

<additionalModelTypeAnnotations>@com.fasterxml.jackson.annotation.JsonInclude(com.fasterxml.jackson.annotation.JsonInclude.Include.NON_EMPTY)
                            </additionalModelTypeAnnotations>

After that my links that are empty are not put in the Json created

Upvotes: 0

yejianfengblue
yejianfengblue

Reputation: 2419

Firstly make sure you have a good reason to use links with media type application/json rather than media type built for hypermedia such as HAL (application/hal+json).

Though RepresentationModel has a field of List<Link>, the getter returns a Links instead of List<Link>. Jackson treats it as simple type (where a JsonSerializer is used) instead of a collection type (where a CollectionSerializer is used), so JsonInclude.Include.NON_EMPTY doesn't work as you expect.

public class RepresentationModel<T extends RepresentationModel<? extends T>> {

    private final List<Link> links;

    @JsonProperty("links")
    public Links getLinks() {
        return Links.of(links);
    }
}

public class Links implements Iterable<Link> { }

public abstract class JsonSerializer<T> {
    public boolean isEmpty(SerializerProvider provider, T value) {
        return (value == null);
    }
}

public class CollectionSerializer {
    @Override
    public boolean isEmpty(SerializerProvider prov, Collection<?> value) {
        return value.isEmpty();
    }
}

One solution is override the getter getLinks() and use a customm filter.

class User extends RepresentationModel<User> {

    // ...

    @JsonProperty("links")
    // if links is an empty JSON array, exclude it
    @JsonInclude(value = JsonInclude.Include.CUSTOM, valueFilter = EmptyLinksFilter.class)
    @Override
    public Links getLinks() {
        return super.getLinks();
    }
}

/* The word "filter" is a bit ambiguous (included? or excluded?).
Here when the equals() of this class return true, the value will be excluded. 
Choose a class name to make yourself comfortable. */
class EmptyLinksFilter{

    @Override
    public boolean equals(Object obj) {

        if (obj == null || !(obj instanceof Links)) {
            return false;
        }
        Links links = (Links) obj;
        return links.isEmpty();
    }
}

The full code is in Github.

Second solution may be custom mixin like what Spring HATEOAS already build for HAL. Related code are:

  • RepresentationModelMixin
  • Jackson2HalModule.HalLinkListSerializer
  • Jackson2HalModule
  • HalMediaTypeConfiguration

The second solution is much complicated. That's why I recommand media types like HAL, for which Spring HATEOAS already has good configuration.

Upvotes: 2

i.am.jabi
i.am.jabi

Reputation: 678

As per the answer from @yejianfengblue, I have created a custom representation model as below and extended this CustomRepresentationModel from response java classes instead of Hateoas RepresentationModel.

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.springframework.hateoas.Links;
import org.springframework.hateoas.RepresentationModel;
import org.springframework.lang.NonNull;

public class CustomRepresentationModel<T extends CustomRepresentationModel<? extends T>> extends
    RepresentationModel<T> {

  @JsonProperty("_links")
  @JsonInclude(value = JsonInclude.Include.CUSTOM, valueFilter = NonEmptyLinksFilter.class)
  @NonNull
  @Override
  public Links getLinks() {
    return super.getLinks();
  }

  static class NonEmptyLinksFilter {

    @Override
    public boolean equals(Object obj) {
      if (!(obj instanceof Links)) {
        return false;
      }
      Links links = (Links) obj;
      return links.isEmpty();
    }

    @Override
    public int hashCode() {
      return super.hashCode();
    }
  }

}

Upvotes: 1

Related Questions