web-dev-qa-db-ja.com

Jerseyを使用したJAX-RS HATEOAS、JSONの不要なリンクプロパティ

Jersey 2.9以降、宣言型リンクを介してハイパーメディア駆動型REST APIのリンク関係を作成することが可能になりました。

このコード、例えば:

@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;

...理論的には、次のようなJSONが生成されると予想されます。

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

ただし、ジャージーは私が必要としない多くのプロパティを持つさまざまなJSONを生成します。

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

hrefの代わりにuriを使用することにも注意してください。 JerseyのLinkオブジェクトの実装を調べたところ、 JerseyLink が見つかりました。

独自の実装を展開するのではなく、ジャージーの宣言型リンクを使用したい。私は他のJerseyLinkプロパティを無視するためだけにJacksonアノテーションを使用することになりました。

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

誰かがジャージーとの宣言的リンクを使用し、hrefやその他のハックを使用せずに、予期されるJSON出力(たとえば、uriではなくJsonIgnorePropertiesで、余分なジャージープロパティなし)を使用しましたか?

ありがとう。

編集

私はハックであると私が思うアプローチを使用してこれを解決しましたが、私にとってはうまくいき、複雑なアダプターの使用を必要としません。

ジャージーによって挿入されたリンクの代わりに実際に別のオブジェクトを公開できることに気づきました。

ResourceLinkというラッパーオブジェクトを作成しました。

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

  //getters and setters
}

次に、私の表現オブジェクトにゲッターメソッドがあります。

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

   return link;
}

そこで、ジャージーを使用してリンクを挿入しましたが、表現オブジェクトのゲッターメソッドで別のオブジェクトを返しました。これは、getterメソッドを作成しなかったため、注入されたリンクオブジェクトではなく、JSONにシリアル化されるプロパティになります。

21
arjaynacion

調査

環境:Jersey 2.13(すべてのプロバイダーバージョンも2.13です)。

宣言的なリンクを使用しても、プログラムによるリンクを使用しても、シリアル化は違いません。 私ができる:-)という理由だけで、プログラマティックを選択しました

テストクラス:

_@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); 
}
_

さまざまなプロバイダーでの結果(追加構成なし)

jersey-media-moxy

依存

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

結果(奇妙な)

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

jersey-media-json-jackson

依存

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

結果(近いですが、paramsとは何ですか?)

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

jackson-jaxrs-json-provider

依存

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

結果(2つの異なるJSONプロバイダーによる2つの異なる結果)

resourceConfig.register(JacksonJsonProvider.class);

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

resourceConfig.register(JacksonJaxbJsonProvider.class);

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

My結論

フィールドに@XmlJavaTypeAdapter(Link.JaxbAdapter.class)の注釈を付けています。このアダプターのスニペットを見てみましょう

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

したがって、LinkからJaxbLinkにマーシャリングされます

_public static class JaxbLink {

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

jersey-media-moxy

バグのようです...以下の解決策を参照してください。

その他

他の2つは _jackson-module-jaxb-annotations_ に依存しており、JAXBアノテーションを使用してマーシャリングを処理します。 _jersey-media-json-jackson_は、必要なJaxbAnnotationModuleを自動的に登録します。 _jackson-jaxrs-json-provider_の場合、JacksonJsonProviderを使用してもJAXB注釈はサポートされず(構成なし)、JacksonJsonJaxbProviderを使用するとJAXB注釈がサポートされます。

したがって、haveJAXBアノテーションサポートがある場合、JaxbLinkにマーシャリングされ、この結果が得られます。

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

すべての不要なプロパティで結果を得る方法は、1)、_jackson-jaxrs-json-provider_のJacksonJsonProviderまたは2)を使用して、ContextResolverObjectMapperを作成します。我々しないでくださいJaxbAnnotationModuleを登録します。あなたはそれらの1つをしているようです。


ソリューション

上記の方法では、目的の場所に到達できません(つまり、paramsがありません)。

_jersey-media-json-jackson_および_jackson-jaxrs-json-provider_の場合...

... Jacksonを使用していますが、現時点で考えられるのは、カスタムシリアライザーを作成することだけです。

_import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.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();
    }
}
_

次に、ContextResolverObjectMapperを作成します。ここで、シリアライザーを登録します。

_@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;
    }
}
_

これが結果です

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

jersey-media-moxyの場合、JaxbLinkクラスにセッターが欠落している Bug があるように見えるため、マーシャリングは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;  
    }    
}
_

代わりにこのアダプターを使用する

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

望ましい出力が得られます

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

更新

今考えてみると、LinkAdapterはJacksonプロバイダーでも機能します。 Jackson Serializer/Deserializerを作成する必要はありません。 JacksonFeatureが有効になっている場合、Jacksonモジュールは既にJAXBアノテーションをサポートしているはずです。上記の例では、JAXB/JSONプロバイダーを個別に使用する方法を示していますが、JacksonFeatureのみが有効になっている場合は、プロバイダーのJAXBバージョンを使用する必要があります。これは実際にはより好ましい解決策かもしれません。 ContextResolversObjectMapperを作成する必要はありません:-D

次のように、パッケージレベルでアノテーションを宣言することも可能です here

20
Paul Samsotha

Jacksonと ミックスインアノテーション を使用して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();
        }
    }
}

例:

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);

出力:

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

提案された更新ソリューションを使用して、まだparamsマップ内にrelパーツを取得していました。

リンクアダプタークラスにいくつかの変更を加えました

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;
    }


}

現在、必要な2つのパラメーター(rel、href)のみが保持されています。Jaxbリンクをリンクに非整列化する必要があるのはいつか理解できませんでした。私にとって重要だったのは、Link to Jaxbリンクマーシャリングでした。

1
Nir Sivan

@peeskilletと@Nir Sivanに感謝します。しかし、私はそれを機能させることができましたなしLinkAdapterまたはContextResolver<ObjectMapper>を使用します。

カスタムリンクタイプのインスタンス変数(ここではResourceLinkに類似したLinkJaxb)を@Transientプロパティとしてエンティティクラスに追加しました。その後、Jackson構成が自動的に含まれました応答JSONのその属性

リソースリンク-クラス

import javax.xml.bind.annotation.XmlAttribute;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.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;
}


}

エンティティークラス

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.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.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リソースプロバイダー

@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();
}
0
Karan Chadha