Ответ 1
Invesitigation
Среда: Джерси 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);
}
Результаты с разными поставщиками (без дополнительных конфигураций)
джерси-медиа-Moxy
Зависимость
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-moxy</artifactId>
</dependency>
Результат (странный)
{
"link": "[email protected]"
}
джерси-медиа-JSON-джексон
Зависимость
<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"
}
}
джексон-jaxrs-JSON-провайдер
Зависимость
<dependency>
<groupId>com.fasterxml.jackson.jaxrs</groupId>
<artifactId>jackson-jaxrs-json-provider</artifactId>
<version>2.4.0</version>
</dependency>
Результат (два разных результата с двумя разными поставщиками JSON)
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;
...
}
джерси-медиа-Moxy
Кажется, это ошибка... См. ниже в решениях.
Другие
Остальные два зависят от jackson-module-jaxb-annotations
для обработки маршаллинга с использованием аннотаций JAXB. jersey-media-json-jackson
автоматически зарегистрирует требуемый JaxbAnnotationModule
. Для jackson-jaxrs-json-provider
использование JacksonJsonProvider
не будет поддерживать аннотации JAXB (без конфликации), а использование JacksonJsonJaxbProvider
даст нам поддержку аннотации JAXB.
Итак, если у нас есть поддержка аннотаций JAXB, мы получим marshalled до JaxbLink
, что даст этот результат
{
"link": {
"params": {
"rel": "stackoverflow"
},
"href": "https://stackoverflow.com/questions/24968448"
}
}
Способы получения результата со всеми нежелательными свойствами: 1), используйте jackson-jaxrs-json-provider
JacksonJsonProvider
или 2), создайте ContextResolver
для ObjectMapper
, где не зарегистрировать JaxbAnnotationModule
. Кажется, вы делаете один из них.
Решение
Выше все еще не получается, куда мы хотим добраться (т.е. нет 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
, поэтому маршаллинг возвращается к вызову 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"
}
}
UPDATE
Теперь, когда я думаю об этом, LinkAdapter
также будет работать с поставщиком Jackson. Не нужно создавать Jackson Serializer/Deserializer. Модуль Jackson должен уже поддерживать аннотации JAXB в поле, если включен параметр JacksonFeature
. Приведенные выше примеры показывают использование JAXB/JSON-провайдеров отдельно, но при включенном только JacksonFeature
должна использоваться версия поставщика JAXB. На самом деле это может быть более предпочтительным решением. Не нужно создавать ContextResolvers
для ObjectMapper
:-D
Также можно объявить аннотацию на уровне пакета, как показано здесь