Как я могу настроить сериализацию списка объектов JAXB в JSON? - PullRequest
54 голосов
/ 04 февраля 2010

Я использую Jersey для создания веб-службы REST для серверного компонента.

JAXB-аннотированный объект, который я хочу сериализовать в списке, выглядит следующим образом:

@XmlRootElement(name = "distribution")
@XmlType(name = "tDistribution", propOrder = {
    "id", "name"
})
public class XMLDistribution {
    private String id;
    private String name;
    // no-args constructor, getters, setters, etc
}

У меня есть ресурс REST для получения одного дистрибутива, который выглядит следующим образом:

@Path("/distribution/{id: [1-9][0-9]*}")
public class RESTDistribution {
    @GET
    @Produces("application/json")
    public XMLDistribution retrieve(@PathParam("id") String id) {
        return retrieveDistribution(Long.parseLong(id));
    }
    // business logic (retrieveDistribution(long))
}

У меня также есть ресурс REST для получения списка всех дистрибутивов, который выглядит следующим образом:

@Path("/distributions")
public class RESTDistributions {
    @GET
    @Produces("application/json")
    public List<XMLDistribution> retrieveAll() {
        return retrieveDistributions();
    }
    // business logic (retrieveDistributions())
}

Я использую ContextResolver для настройки сериализации JAXB, которая в настоящее время настроена следующим образом:

@Provider
@Produces("application/json")
public class JAXBJSONContextResolver implements ContextResolver<JAXBContext> {
    private JAXBContext context;
    public JAXBJSONContextResolver() throws Exception {
        JSONConfiguration.MappedBuilder b = JSONConfiguration.mapped();
        b.nonStrings("id");
        b.rootUnwrapping(true);
        b.arrays("distribution");
        context = new JSONJAXBContext(b.build(), XMLDistribution.class);
    }
    @Override
    public JAXBContext getContext(Class<?> objectType) {
        return context;
    }
}

Работают оба ресурса REST, а также преобразователь контекста.Это пример вывода для первого:

// path: /distribution/1
{"id":1,"name":"Example Distribution"}

Это именно то, что я хочу.Это пример вывода для списка:

// path: /distributions
{"distribution":[{"id":1,"name":"Sample Distribution 1"},{"id":2,"name":"Sample Distribution 2"}]}

Это не совсем то, что я хочу.

Я не понимаю, почему там есть включающий тег distribution.Я хотел удалить его с помощью .rootUnwrapping(true) в средстве разрешения контекста, но очевидно, что он удаляет только другой тег.Это вывод с .rootUnwrapping(false):

// path: /distribution/1
{"distribution":{"id":1,"name":"Example Distribution"}} // not ok
// path: /distributions
{"xMLDistributions":{"distribution":[{"id":1,"name":"Sample Distribution 1"},{"id":2,"name":"Sample Distribution 2"}]}}

Мне также пришлось настроить .arrays("distribution"), чтобы всегда получать массив JSON, даже с одним элементом.

В идеале, я быхотел бы получить это в качестве вывода:

// path: /distribution/1
{"id":1,"name":"Example Distribution"} // currently works
// path: /distributions
[{"id":1,"name":"Sample Distribution 1"},{"id":2,"name":"Sample Distribution 2"}]

Я пытался вернуть List<XMLDistribution>, XMLDistributionList (обертка вокруг списка), XMLDistribution[], но я не мог найти способчтобы получить простой JSON-массив дистрибутивов в нужном мне формате.

Я также попробовал другие нотации, возвращаемые JSONConfiguration.natural(), JSONConfiguration.mappedJettison() и т. д., и не смог получить ничего похожего на то, что мне нужно.

Кто-нибудь знает, возможно ли настроить JAXB для этого?

Ответы [ 2 ]

102 голосов
/ 29 июня 2010

Я нашел решение: замените сериализатор JSON JAXB на сериализатор JSON с лучшим поведением, как Джексон. Самый простой способ - использовать Jackson-Jaxrs, который уже сделал это для вас. Класс является JacksonJsonProvider. Все, что вам нужно сделать, это отредактировать файл web.xml вашего проекта, чтобы Джерси (или другая реализация JAX-RS) сканировала его. Вот что вам нужно добавить:

<init-param>
  <param-name>com.sun.jersey.config.property.packages</param-name>
  <param-value>your.project.packages;org.codehaus.jackson.jaxrs</param-value>
</init-param>

И это все, что нужно сделать. Джексон будет использоваться для сериализации JSON и работает так, как вы ожидаете для списков и массивов.

Более длинный способ - написать свой собственный зарегистрированный MessageBodyWriter для создания "application / json". Вот пример:

@Provider
@Produces("application/json")
public class JsonMessageBodyWriter implements MessageBodyWriter {
    @Override
    public long getSize(Object obj, Class type, Type genericType,
            Annotation[] annotations, MediaType mediaType) {
        return -1;
    }

    @Override
    public boolean isWriteable(Class type, Type genericType,
            Annotation annotations[], MediaType mediaType) {
        return true;
    }

    @Override
    public void writeTo(Object target, Class type, Type genericType,
            Annotation[] annotations, MediaType mediaType,
            MultivaluedMap httpHeaders, OutputStream outputStream)
            throws IOException {        
        new ObjectMapper().writeValue(outputStream, target);
    }
}

Вам нужно будет убедиться, что ваш web.xml включает пакет, как и для готового решения выше.

В любом случае: вуаля! Вы увидите правильно сформированный JSON.

Вы можете скачать Джексон здесь: http://jackson.codehaus.org/

13 голосов
/ 16 апреля 2013

Ответ Джонатана великолепен, и он был очень полезен для меня.

Просто обновление:

если вы используете версию 2.x Джексона (например, версию 2.1), то класс com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider, поэтому web.xml:

<init-param>
  <param-name>com.sun.jersey.config.property.packages</param-name>
  <param-value>your.project.packages;com.fasterxml.jackson.jaxrs.json</param-value>
</init-param>
...