Ошибка десериализации JSON (HTTP 400) с Jersey / MOXy

У меня есть относительно простой веб-сервис RESTful, который использует Jersey и Eclipselink MOXy.

Я могу отправлять POST-запросы к службе, если данные отформатированы как XML, но если я отправлю их как JSON, сервер сгенерирует HTTP 400 (неверный запрос) с сообщением: «Запрос, отправленный клиентом, был синтаксически неверным. ".

Сервисная сторона выглядит так:

@POST
@Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
public Subscription post(Subscription Subscription) {
    return Subscriptions.addSubscription(Subscription);
}

Если я отправлю ему XML-данные из Javascript на такой веб-странице, проблем не будет:

 $.ajax({
    url: 'http://localhost:8080/MyService/subscription',
type: 'POST',
    data:  "<subscription><notificationType>EMAIL</notificationType><notificationAddress>[email protected]</notificationAddress></subscription>",
    headers: { 
            Accept : "application/xml",
            "Content-Type": "application/xml"
    },
     // .. other params ... 
 );

Однако с эквивалентным JSON я получаю HTTP 400 - Bad Request:

 $.ajax({
    url: 'http://localhost:8080/MyService/subscription',
type: 'POST',
    data:  JSON.stringify(
                {subscription:{notificationType:"EMAIL",notificationAddress:"[email protected]"}}
            ),
    dataType: 'json'
    headers: { 
            Accept : "application/json",
            "Content-Type": "application/json"
     }
     // .. other params ... 
 );

Я проверил запрос с помощью Fiddler, и форматирование данных и заголовки выглядят правильно.

Интересно то, что я могу успешно демаршалировать ту же самую строку JSON, если вставлю ее в этот код:

String json = "{\"subscription\":{\"notificationType\":\"EMAIL\",\"notificationAddress\":\"[email protected]\"}}";
JAXBContext context = JAXBContext.newInstance(Subscription.class);
Unmarshaller m = context.createUnmarshaller();
m.setProperty("eclipselink.media-type", "application/json");
StringReader sr = new StringReader(json);
Subscription sub = (Subscription)m.unmarshal(sr);
System.out.println(sub.toString());

Класс подписки определяется как:

@XmlRootElement(name="subscription")
public class Subscription {

    public enum NotificationType { EMAIL, SMS };

    private String notificationAddress;

    private NotificationType notificationType;

    public String getNotificationAddress() {
        return notificationAddress;
    }

    public void setNotificationAddress(String notificationAddress) {
        this.notificationAddress = notificationAddress;
    }
    public NotificationType getNotificationType() {
        return notificationType;
    }

    public void setNotificationType(NotificationType notificationType) {
        this.notificationType = notificationType;
    }
    public Subscription() {
    }

    @Override
    public String toString() {
        String s = "Subscription";
        if (getNotificationAddress() != null) {
            s += "(" + getNotificationType().toString() + ":" + getNotificationAddress() + ")";
        }
        return s;
    }
}

Я настроил Eclipselink MOXy в качестве поставщика JAXB, добавив эту строку в jaxb.properties в пакете, который содержит мой класс подписчика:

javax.xml.bind.context.factory=org.eclipse.persistence.jaxb.JAXBContextFactory

и это, похоже, работает, по крайней мере, для маршалинга объектов, выходящих из службы.

Что мне не хватает?

EDIT: это то, что Fiddler фиксирует, когда я публикую данные JSON:

POST http://localhost:8080/MyService/subscription HTTP/1.1
Host: localhost:8080
Connection: keep-alive
Content-Length: 86
Accept: application/json
Origin: http://localhost:8080
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.116 Safari/537.36
Content-Type: application/json
Referer: http://localhost:8080/TestPage/AddSubscription.html
Accept-Encoding: gzip,deflate,sdch
Accept-Language: en-GB,en-US;q=0.8,en;q=0.6

{"subscription":{"notificationType":"EMAIL","notificationAddress":"[email protected]"}}

ОБНОВЛЕНИЕ:

Я взял вариант № 2 из ответа Блейза ниже, создал класс, производный от приложения, таким образом:

import java.util.*;
import javax.ws.rs.core.Application;
import org.eclipse.persistence.jaxb.rs.MOXyJsonProvider;

public class MyApplication  extends Application {
    @Override
    public Set<Class<?>> getClasses() {
        HashSet<Class<?>> set = new HashSet<Class<?>>(2);
        set.add(MOXyJsonProvider.class);
        set.add(SubscriptionResource.class); // the class containing my @POST service method.
        return set;
    }
 }

И добавил в web.xml:

   <param-name>javax.ws.rs.Application</param-name>
   <param-value>com.example.MyApplication</param-value> 

И теперь я не получаю HTTP 400, и код в моей службе срабатывает, чего не было раньше, однако переданный объект подписки имеет все неинициализированные поля, например notificationAddress имеет значение null. Если я публикую с использованием XML, он все равно работает нормально.

ОБНОВЛЕНИЕ №2:

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

https://www.dropbox.com/sh/2a6iqw65ey0ahrk/D2ILi_722z

Вышеупомянутая ссылка содержит .zip с двумя проектами Eclipse; TestService (служба RESTful Джерси, которая принимает объект подписки) и TestPage (страница .html с некоторым кодом JavaScript для POST объекта подписки в формате JSON или XML).

Если вы установите точку останова в методе post службы и используете тестовую страницу для отправки запроса JSON, вы увидите, что передается неинициализированный объект подписки.


person Dave W    schedule 02.07.2013    source источник


Ответы (1)


EclipseLink JAXB (MOXy) можно использовать с Джерси в несколько разных способов создания JSON.

Вариант №1 - Добавить файл jaxb.properties

Джерси может использовать реализацию JAXB (JSR-222) для создания JSON. Если вы добавите jaxb.properties в свою модель домена, вы можете указать MOXy в качестве этого поставщика. Некоторое время в Джерси существовала следующая ошибка, которая не позволяла использовать MOXy таким образом, что может быть тем, с чем вы столкнулись сейчас.

Этот метод создания JSON имеет некоторые ограничения и может не соответствовать вашим желаниям.

Вариант № 2 - Кредитное плечо MOXyJsonProvider

Начиная с EclipseLink 2.4.0 MOXy предлагает собственную привязку JSON на основе метаданных JAXB и MOXy. Вы можете настроить это с помощью MOXyJsonProvider.

import java.util.*;
import javax.ws.rs.core.Application;
import org.eclipse.persistence.jaxb.rs.MOXyJsonProvider;

public class MyApplication  extends Application {

    @Override
    public Set<Class<?>> getClasses() {
        HashSet<Class<?>> set = new HashSet<Class<?>>(1);
        set.add(SubscriptionResource.class);
        return set;
    }

    @Override
    public Set<Object> getSingletons() {
        MOXyJsonProvider moxyJsonProvider = new MOXyJsonProvider();
        moxyJsonProvider.setIncludeRoot(true);

        HashSet<Object> set = new HashSet<Object>(1);
        set.add(moxyJsonProvider);
        return set;
    }

} 

Для дополнительной информации

Именно так я бы рекомендовал использовать MOXy с Jersey или любым другим поставщиком JAX-RS.

person bdoughan    schedule 02.07.2013
comment
@DaveW - Поскольку ваше сообщение JSON имеет корневой узел, вам нужно указать MOXyJsonProvider, чтобы он включил корневой узел. Я обновил свой ответ примером Application класса. - person bdoughan; 04.07.2013
comment
Потрясающие! Похоже, это помогло. (Спасибо за вашу помощь в этом и других вопросах - очень признательны). - person Dave W; 04.07.2013