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
JAX-RS HATEOAS Using Jersey, Unwanted Link properties in JSON
提问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 Link
object 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 JerseyLink
properties.
我想使用 Jersey 的声明式链接而不是推出我自己的实现。我最终使用 Hymanson 注释只是为了忽略其他JerseyLink
属性。
@JsonIgnoreProperties({ "uriBuilder", "params", "type", "rels" })
Has anyone used declarative linking with Jersey and had the expected JSON output (e.g., href
instead of uri
, without extra Jersey properties) without having to use JsonIgnoreProperties
or 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-annotations
to handle marshalling using JAXB annotations. jersey-media-json-Hymanson
will automatically register the required JaxbAnnotationModule
. For Hymanson-jaxrs-json-provider
, using HymansonJsonProvider
will not support JAXB annotations (without confgiruation), and using HymansonJsonJaxbProvider
will 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 HymansonJsonProvider
or 2), create a ContextResolver
for ObjectMapper
where we don'tregister the JaxbAnnotationModule
. You seem to be doing one of those.
我们可以通过所有不需要的属性获得结果的方法是1),使用Hymanson-jaxrs-json-provider
'sHymansonJsonProvider
或2),ContextResolver
为ObjectMapper
我们没有注册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-Hymanson
and Hymanson-jaxrs-json-provider
...
对于jersey-media-json-Hymanson
和Hymanson-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 ContextResolver
for the ObjectMapper
, where we register the serializer
然后创建一个ContextResolver
for 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 JaxbLink
class, 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 LinkAdapter
would 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 HymansonFeature
is enabled. The examples above show using the JAXB/JSON providers separately, but given just the HymansonFeature
is enabled, the JAXB version of the provider should be used. This may actually be the more preferred solution. No need to create an ContextResolvers
for the ObjectMapper
:-D
现在我考虑一下,这LinkAdapter
也将与 Hymanson 提供商合作。无需创建 Hymanson Serializer/Deserializer。鉴于HymansonFeature
已启用,Hymanson 模块应该已经支持开箱即用的 JAXB 注释。上面的示例显示单独使用 JAXB/JSON 提供程序,但如果仅HymansonFeature
启用了 ,则应使用提供程序的 JAXB 版本。这实际上可能是更优选的解决方案。无需ContextResolvers
为ObjectMapper
:-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 LinkAdapter
or ContextResolver<ObjectMapper>
.
谢谢@peeskillet 和@Nir Sivan 的回答。但是我能够在不使用LinkAdapter
or 的情况下使其工作ContextResolver<ObjectMapper>
。
I just added a instance variable of the custom Link type (here ResourceLink
which is analogous to your LinkJaxb
) to my entity class as a @Transient
property 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"]