Java JAX-RS HATEOAS 使用 Jersey,JSON 中不需要的链接属性

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/24968448/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-14 16:02:40  来源:igfitidea点击:

JAX-RS HATEOAS Using Jersey, Unwanted Link properties in JSON

javarestjerseyjax-rsjersey-2.0

提问by arjaynacion

Since Jersey 2.9, it's been possible to create link relations for hypermedia-driven REST APIs through declarative linking.

从 Jersey 2.9 开始,可以通过声明式链接为超媒体驱动的 REST API 创建链接关系。

This code, for example:

这段代码,例如:

@InjectLink(
    resource = ItemResource.class,
    style = Style.ABSOLUTE,
    bindings = @Binding(name = "id", value = "${instance.id}"),
    rel = "self"
)
@XmlJavaTypeAdapter(Link.JaxbAdapter.class)
@XmlElement(name="link")
Link self;

...in theory is expected to produce JSON like this:

...理论上预计会产生这样的 JSON:

"link" : {
    "rel" : "self",
    "href" : "http://localhost/api/resource/1"
}

However, Jersey produces different JSON with a lot of properties that I don't need:

但是,Jersey 生成不同的 JSON,其中包含许多我不需要的属性:

"link" : {
   "rel" : "self",
   "uri" : "http://localhost/api/resource/1",
   "type": null,
   "uriBuilder" : null
}

Notice also that instead of href, it uses uri. I looked at Jersey's implementation of the Linkobject and found JerseyLink.

另请注意href,它使用uri. 我查看了 Jersey 对Link对象的实现,发现JerseyLink.

I want to use Jersey's declarative linking instead of rolling out my own implementation. I ended up using Hymanson annotations just to ignore other JerseyLinkproperties.

我想使用 Jersey 的声明式链接而不是推出我自己的实现。我最终使用 Hymanson 注释只是为了忽略其他JerseyLink属性。

@JsonIgnoreProperties({ "uriBuilder", "params", "type", "rels" })

Has anyone used declarative linking with Jersey and had the expected JSON output (e.g., hrefinstead of uri, without extra Jersey properties) without having to use JsonIgnorePropertiesor other hacks?

有没有人使用过与 Jersey 的声明式链接并具有预期的 JSON 输出(例如,href而不是uri,没有额外的 Jersey 属性)而无需使用JsonIgnoreProperties或其他黑客?

Thanks.

谢谢。

EDIT

编辑

I resolved this using an approach which I think is a hack but works well for me and doesn't require the use of a complicated adapter.

我使用一种我认为是 hack 但对我来说效果很好的方法解决了这个问题,并且不需要使用复杂的适配器。

I realized that I can actually expose a different object instead of the Link injected by Jersey.

我意识到我实际上可以公开一个不同的对象,而不是由 Jersey 注入的 Link。

I created a wrapper object named ResourceLink:

我创建了一个名为 ResourceLink 的包装对象:

public class ResourceLink {
  private String rel;
  private URI href;

  //getters and setters
}

Then in my representation object I have a getter method:

然后在我的表示对象中,我有一个 getter 方法:

public ResourceLink getLink() {
   ResourceLink link = new ResourceLink();
   link.setRel(self.getRel());
   link.setHref(self.getUri());

   return link;
}

So I used Jersey to inject the link but returned a different object in a getter method in my representation object. This would be the property that would be serialized to JSON and not the injected link object because I didn't create a getter method for it.

所以我使用 Jersey 来注入链接,但在我的表示对象中的 getter 方法中返回了一个不同的对象。这将是序列化为 JSON 的属性,而不是注入的链接对象,因为我没有为它创建 getter 方法。

回答by Paul Samsotha

Invesitigation

调查

Environment: Jersey 2.13 ( all provider versions are also 2.13 ).

环境: Jersey 2.13(所有提供程序版本也是 2.13 )。

Whether you use declarative or programmatic linking, the serialization shouldn't differ. I chose programmatic, just because I can:-)

无论您使用声明式链接还是程序化链接,序列化都不应该有所不同。我选择程序化,只是因为我可以:-)

Test classes:

测试类:

@XmlRootElement
public class TestClass {
    private javax.ws.rs.core.Link link;

    public void setLink(Link link) { this.link = link; }

    @XmlElement(name = "link")
    @XmlJavaTypeAdapter(Link.JaxbAdapter.class)
    public Link getLink() { return link; }
}

@Path("/links")
public class LinkResource {  
    @GET
    @Produces(MediaType.APPLICATION_JSON)
    public Response getResponse() {
        URI uri = URI.create("https://stackoverflow.com/questions/24968448");
        Link link = Link.fromUri(uri).rel("stackoverflow").build();
        TestClass test = new TestClass();
        test.setLink(link);
        return Response.ok(test).build();
    }
}

@Test
public void testGetIt() {
    WebTarget baseTarget = target.path("links");
    String json = baseTarget.request().accept(
            MediaType.APPLICATION_JSON).get(String.class);
    System.out.println(json); 
}


Results with different Providers (with no extra configurations)

不同提供商的结果(没有额外的配置)

jersey-media-moxy

球衣媒体莫西

Dependency

依赖

<dependency>
    <groupId>org.glassfish.jersey.media</groupId>
    <artifactId>jersey-media-moxy</artifactId>
</dependency>

Result (weird)

结果(奇怪)

{
    "link": "javax.ws.rs.core.Link$JaxbLink@cce17d1b"
}

jersey-media-json-Hymanson

球衣媒体-json-Hyman逊

Dependency

依赖

<dependency>
    <groupId>org.glassfish.jersey.media</groupId>
    <artifactId>jersey-media-json-Hymanson</artifactId>
</dependency>

Result (close, but what's with the params?)

结果(关闭,但是与params? 有什么关系?)

{
    "link": {
        "params": {
            "rel": "stackoverflow"
        },
        "href": "https://stackoverflow.com/questions/24968448"
    }
}

Hymanson-jaxrs-json-provider

Hymanson-jaxrs-json-provider

Dependency

依赖

<dependency>
    <groupId>com.fasterxml.Hymanson.jaxrs</groupId>
    <artifactId>Hymanson-jaxrs-json-provider</artifactId>
    <version>2.4.0</version>
</dependency>

Result (Two different results, with two different JSON providers)

结果(两个不同的结果,具有两个不同的 JSON 提供程序)

resourceConfig.register(HymansonJsonProvider.class);

resourceConfig.register(HymansonJsonProvider.class);

{
    "link": {
        "uri": "https://stackoverflow.com/questions/24968448",
        "params": {
            "rel": "stackoverflow"
        },
        "type": null,
        "uriBuilder": {
            "absolute": true
        },
        "rels": ["stackoverflow"],
        "title": null,
        "rel": "stackoverflow"
    }
}

resourceConfig.register(HymansonJaxbJsonProvider.class);

resourceConfig.register(HymansonJaxbJsonProvider.class);

{
    "link": {
        "params": {
            "rel": "stackoverflow"
        },
        "href": "https://stackoverflow.com/questions/24968448"
    }
}


MyConclusions

我的结论

We are annotating the field with @XmlJavaTypeAdapter(Link.JaxbAdapter.class). Let look at a snippet of this adapter

我们用 注释该字段@XmlJavaTypeAdapter(Link.JaxbAdapter.class)。让我们看一下这个适配器的片段

public static class JaxbAdapter extends XmlAdapter<JaxbLink, Link> {...}

So from Link, we are being marshalled to JaxbLink

所以从Link,我们被编组到JaxbLink

public static class JaxbLink {

    private URI uri;
    private Map<QName, Object> params;
    ...
}

jersey-media-moxy

球衣媒体莫西

Seems to be a bug... See below in solutions.

似乎是一个错误......请参阅下面的解决方案。

The others

其他

The other two are dependent on Hymanson-module-jaxb-annotationsto handle marshalling using JAXB annotations. jersey-media-json-Hymansonwill automatically register the required JaxbAnnotationModule. For Hymanson-jaxrs-json-provider, using HymansonJsonProviderwill not support JAXB annotations (without confgiruation), and using HymansonJsonJaxbProviderwill give us the JAXB annotation support.

另外两个依赖于Hymanson-module-jaxb-annotations使用 JAXB 注释处理编组。jersey-media-json-Hymanson将自动注册所需的JaxbAnnotationModule. 对于Hymanson-jaxrs-json-provider, usingHymansonJsonProvider将不支持 JAXB 注释(没有配置),而 usingHymansonJsonJaxbProvider将为我们提供 JAXB 注释支持。

So if we haveJAXB annotation support, we will get marshalled to JaxbLink, which will give this result

所以如果我们JAXB 注释支持,我们将被编组到JaxbLink,这将给出这个结果

{
    "link": {
        "params": {
            "rel": "stackoverflow"
        },
        "href": "https://stackoverflow.com/questions/24968448"
    }
}

The ways we can get the result with all the unwanted properties, is to 1), use the Hymanson-jaxrs-json-provider's HymansonJsonProvideror 2), create a ContextResolverfor ObjectMapperwhere we don'tregister the JaxbAnnotationModule. You seem to be doing one of those.

我们可以通过所有不需要的属性获得结果的方法是1),使用Hymanson-jaxrs-json-provider'sHymansonJsonProvider2)ContextResolverObjectMapper我们没有注册JaxbAnnotationModule. 你似乎正在做其中之一。



Solutions

解决方案

The above still doesn't get us where we want to get to (i.e. no params).

以上仍然没有让我们到达我们想要到达的地方(即 no params)。

For jersey-media-json-Hymansonand Hymanson-jaxrs-json-provider...

对于jersey-media-json-HymansonHymanson-jaxrs-json-provider...

...which use Hymanson, the only thing I can think of at this point is to create a custom serializer

...使用Hymanson,此时我唯一能想到的就是创建一个自定义序列化程序

import com.fasterxml.Hymanson.core.JsonGenerator;
import com.fasterxml.Hymanson.core.JsonProcessingException;
import com.fasterxml.Hymanson.databind.JsonSerializer;
import com.fasterxml.Hymanson.databind.SerializerProvider;
import java.io.IOException;
import javax.ws.rs.core.Link;

public class LinkSerializer extends JsonSerializer<Link>{

    @Override
    public void serialize(Link link, JsonGenerator jg, SerializerProvider sp) 
            throws IOException, JsonProcessingException {
        jg.writeStartObject();
        jg.writeStringField("rel", link.getRel());
        jg.writeStringField("href", link.getUri().toString());
        jg.writeEndObject();
    }
}

Then create a ContextResolverfor the ObjectMapper, where we register the serializer

然后创建一个ContextResolverfor ObjectMapper,我们在那里注册序列化器

@Provider
public class ObjectMapperContextResolver 
                          implements ContextResolver<ObjectMapper> {

    private final ObjectMapper mapper;

    public ObjectMapperContextResolver() {
        mapper = new ObjectMapper();
        SimpleModule simpleModule = new SimpleModule();
        simpleModule.addSerializer(Link.class, new LinkSerializer());
        mapper.registerModule(simpleModule);
    }

    @Override
    public ObjectMapper getContext(Class<?> type) {
        return mapper;
    }
}

This is the result

这是结果

{
    "link": {
        "rel": "stackoverflow",
        "href": "https://stackoverflow.com/questions/24968448"
    }
}

With jersey-media-moxy, it appears there's a Bugwith missing setters in the JaxbLinkclass, so the marshalling reverts to calling toString, which is what's shown above. A work around, as proposed here by Garard Davidson, is just to create another adapter

使用jersey-media-moxy,看起来类中存在一个缺少 setter的错误JaxbLink,因此编组恢复为调用toString,这就是上面显示的内容。Garard Davidson 在此提出的解决方法就是创建另一个适配器

import java.net.URI;
import java.util.HashMap;  
import java.util.Map;  

import javax.ws.rs.core.Link;  
import javax.xml.bind.annotation.XmlAnyAttribute;
import javax.xml.bind.annotation.XmlAttribute;

import javax.xml.bind.annotation.adapters.XmlAdapter;  
import javax.xml.namespace.QName;  

public class LinkAdapter  
    extends XmlAdapter<LinkJaxb, Link> {  

    public LinkAdapter() {  
    }  

    public Link unmarshal(LinkJaxb p1) {  

        Link.Builder builder = Link.fromUri(p1.getUri());  
        for (Map.Entry<QName, Object> entry : p1.getParams().entrySet()) {  
            builder.param(entry.getKey().getLocalPart(), entry.getValue().toString());  
        }  
        return builder.build();  
    }  

    public LinkJaxb marshal(Link p1) {  

        Map<QName, Object> params = new HashMap<>();  
        for (Map.Entry<String,String> entry : p1.getParams().entrySet()) {  
            params.put(new QName("", entry.getKey()), entry.getValue());  
        }  
        return new LinkJaxb(p1.getUri(), params);  
    }  
}  

class LinkJaxb  {  

    private URI uri;  
    private Map<QName, Object> params;  


    public LinkJaxb() {  
        this (null, null);  
    }  

    public LinkJaxb(URI uri) {  
        this(uri, null);  
    }  

    public LinkJaxb(URI uri, Map<QName, Object> map) {  

        this.uri = uri;  
        this.params = map!=null ? map : new HashMap<QName, Object>();  

    }  

    @XmlAttribute(name = "href")  
    public URI getUri() {   
        return uri;  
    }  

    @XmlAnyAttribute  
    public Map<QName, Object> getParams() {   
        return params;  
    }  

    public void setUri(URI uri) {  
        this.uri = uri;  
    }  

    public void setParams(Map<QName, Object> params) {  
        this.params = params;  
    }    
}

Using this adapter instead

改用这个适配器

@XmlElement(name = "link")
@XmlJavaTypeAdapter(LinkAdapter.class)
private Link link;

will give us the desired output

会给我们想要的输出

{
    "link": {
        "href": "https://stackoverflow.com/questions/24968448",
        "rel": "stackoverflow"
    }
}


UPDATE

更新

Now that I think about it, the LinkAdapterwould work with the Hymanson provider also. No need to create a Hymanson Serializer/Deserializer. The Hymanson module should already support the JAXB annotations out the box, given the HymansonFeatureis enabled. The examples above show using the JAXB/JSON providers separately, but given just the HymansonFeatureis enabled, the JAXB version of the provider should be used. This may actually be the more preferred solution. No need to create an ContextResolversfor the ObjectMapper:-D

现在我考虑一下,这LinkAdapter也将与 Hymanson 提供商合作。无需创建 Hymanson Serializer/Deserializer。鉴于HymansonFeature已启用,Hymanson 模块应该已经支持开箱即用的 JAXB 注释。上面的示例显示单独使用 JAXB/JSON 提供程序,但如果仅HymansonFeature启用了 ,则应使用提供程序的 JAXB 版本。这实际上可能是更优选的解决方案。无需ContextResolversObjectMapper:-D创建

It's also possible to declare the annotation at the package level, as seen here

它也可以在包级别声明注释,如看到这里

回答by Nir Sivan

Using the suggested update solution I was still getting the rel part inside the params map.

使用建议的更新解决方案,我仍然在 params 映射中获取 rel 部分。

I have made some changes in the Link adapter class

我在链接适配器类中做了一些更改

public class LinkAdapter
    extends XmlAdapter<LinkJaxb, Link> {

    public LinkAdapter() {
    }

    public Link unmarshal(LinkJaxb p1) {

        Link.Builder builder = Link.fromUri(p1.getUri());

        return builder.build();
    }

    public LinkJaxb marshal(Link p1) {

        return new LinkJaxb(p1.getUri(), p1.getRel());
    }
}

class LinkJaxb  {

    private URI uri;
    private String rel;


    public LinkJaxb() {
        this (null, null);
    }

    public LinkJaxb(URI uri) {
        this(uri, null);
    }

    public LinkJaxb(URI uri,String rel) {

        this.uri = uri;
        this.rel = rel;

    }

    @XmlAttribute(name = "href")
    public URI getUri() {
        return uri;
    }
    @XmlAttribute(name = "rel")
    public String getRel(){return rel;}

    public void setUri(URI uri) {
        this.uri = uri;
    }


}

It now holds only the two params which are needed (rel,href) I did not really understand when do I need to unmarshal a Jaxb link to a Link. What mattered to me was the Link to Jaxb link marshaling.

它现在只包含需要的两个参数 (rel,href) 我真的不明白什么时候需要将 Jaxb 链接解组到链接。对我来说重要的是 Link to Jaxb 链接编组。

回答by Karan Chadha

Thank You, @peeskillet and @Nir Sivan, for your answers. But I was able to make it work withoutusing the LinkAdapteror ContextResolver<ObjectMapper>.

谢谢@peeskillet 和@Nir Sivan 的回答。但是我能够在使用LinkAdapteror 的情况下使其工作ContextResolver<ObjectMapper>

I just added a instance variable of the custom Link type (here ResourceLinkwhich is analogous to your LinkJaxb) to my entity class as a @Transientproperty and after that Hymanson configuration automatically included that attribute in the Response JSON

我刚刚将自定义 Link 类型的实例变量(此处ResourceLink类似于您的LinkJaxb)作为@Transient属性添加到我的实体类中,之后 Hymanson 配置自动将该属性包含在响应 JSON 中

Resource Link - Class

资源链接 - 类

import javax.xml.bind.annotation.XmlAttribute;

import com.fasterxml.Hymanson.annotation.JsonInclude;
import com.fasterxml.Hymanson.annotation.JsonInclude.Include;

@JsonInclude(Include.NON_EMPTY)
public class ResourceLink  {

private String uri;
private String rel;


public ResourceLink() {
    this (null, null);
}

public ResourceLink(String uri) {
    this(uri, null);
}

public ResourceLink(String uri,String rel) {

    this.uri = uri;
    this.rel = rel;

}

@XmlAttribute(name = "href")
public String getUri() {
    return uri;
}
@XmlAttribute(name = "rel")
public String getRel(){return rel;}

public void setUri(String uri) {
    this.uri = uri;
}


}

Entity Class

实体类

package com.bts.entities;

import java.util.ArrayList;
import java.util.List;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.Transient;

import com.bts.rs.root.util.ResourceLink;
import com.fasterxml.Hymanson.annotation.JsonInclude;
import com.fasterxml.Hymanson.annotation.JsonInclude.Include;

@Entity
@Table(name="cities")
@JsonInclude(Include.NON_EMPTY)
public class City {
    @Id
    @Column(name="city_id")
    private Integer cityId;

    @Column(name="name")
    private String name;

    @Column(name="status")
    private int status;

    @Column(name="del_status")
    private int delStatus;

    @Transient
    List<ResourceLink> links = new ArrayList<ResourceLink>();

    // private 
    public City () {

    }

    public City (Integer id, String name) {
        this.cityId = id;
        this.name = name;
        this.status = 0;
        this.delStatus = 0;
    }

    // getters and setters for Non-transient properties

    // Below is the getter for lInks transient attribute
    public List<ResourceLink> getLinks(){
        return this.links;
    }

    // a method to add links - need not be a setter necessarily
    public void addResourceLink (String uri,String rel) {
        this.links.add(new ResourceLink(uri, rel));
    }   
}

Jersy Resource Provider

泽西资源提供者

@GET
@Path("/karanchadha")
@Produces({MediaType.APPLICATION_JSON})
@Transactional
public Response savePayment() {
    City c1 = new City();
    c1.setCityId(11);
    c1.setName("Jamshedpur");
    c1.addResourceLink("http://www.test.com/home", "self");
    c1.addResourceLink("http://www.test.com/2", "parent");

    return Response.status(201).entity(c1).build();
}

回答by Alexey Gavrilov

I'd like to share with my solution for serialising/deserialising Link objects using with Hymanson and the mix-in annotations.

我想与我分享使用 Hymanson 和混合注释的序列化/反序列化 Link 对象的解决方案。

LinkMixin:

链接混合:

@JsonAutoDetect(
        fieldVisibility = JsonAutoDetect.Visibility.NONE,
        getterVisibility = JsonAutoDetect.Visibility.NONE,
        isGetterVisibility = JsonAutoDetect.Visibility.NONE)
@JsonDeserialize(using = LinkMixin.LinkDeserializer.class)
public abstract class LinkMixin extends Link {

    private static final String HREF = "href";

    @JsonProperty(HREF)
    @Override
    public abstract URI getUri();

    @JsonAnyGetter
    public abstract Map<String, String> getParams();

    public static class LinkDeserializer extends JsonDeserializer<Link> {

        @Override
        public Link deserialize(
                final JsonParser p,
                final DeserializationContext ctxt) throws IOException {
            final Map<String, String> params = p.readValueAs(
                    new TypeReference<Map<String, String>>() {});
            if (params == null) {
                return null;
            }
            final String uri = params.remove(HREF);
            if (uri == null) {
                return null;
            }
            final Builder builder = Link.fromUri(uri);
            params.forEach(builder::param);
            return builder.build();
        }
    }
}

Example:

例子:

final ObjectMapper mapper = new ObjectMapper();
mapper.addMixIn(Link.class, LinkMixin.class);
final Link link = Link.fromUri("http://example.com")
        .rel("self")
        .title("xxx")
        .param("custom", "my")
        .build();
final String json = mapper
        .writerWithDefaultPrettyPrinter()
        .writeValueAsString(Collections.singleton(link));
System.out.println(json);
final List<Link> o = mapper.readValue(json, new TypeReference<List<Link>>() {});
System.out.println(o);

Output:

输出:

[ {
  "href" : "http://example.com",
  "rel" : "self",
  "title" : "xxx",
  "custom" : "my"
} ]
[<http://example.com>; rel="self"; title="xxx"; custom="my"]