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にシリアル化されるプロパティになります。
環境: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"
}
}
_
フィールドに@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)を使用して、ContextResolver
のObjectMapper
を作成します。我々しないでください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();
}
}
_
次に、ContextResolver
の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;
}
}
_
これが結果です
_{
"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バージョンを使用する必要があります。これは実際にはより好ましい解決策かもしれません。 ContextResolvers
のObjectMapper
を作成する必要はありません:-D
次のように、パッケージレベルでアノテーションを宣言することも可能です here
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"]
提案された更新ソリューションを使用して、まだ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リンクマーシャリングでした。
@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));
}
}
@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();
}