В XStream есть лучший способ сделать маршалл / анмаршалл List <Object>в JSON и Java - PullRequest
9 голосов
/ 08 мая 2009

Я использую XStream и сериализатор JSON JETTISON Stax JSON для отправки / получения сообщений в / из клиентов JSON javascripts и веб-приложений Java.

Я хочу иметь возможность создавать список объектов для отправки на сервер и правильного распределения в Java, но формат, в котором XStream и JSON ожидают его, очень неинтуитивен и требует, чтобы наши библиотеки javascript перепрыгивали через обручи.

[РЕДАКТИРОВАТЬ Обновление проблемы с использованием GSON библиотека] Я попытался использовать библиотеку GSON , но она не может десериализовать конкретные объекты, если у меня есть только ожидаемые универсальные суперклассы (XStream и Jettison обрабатывают это, потому что информация о типе запекается в сериализации).

GSON FAQ заявляет Ограничение Коллекции :

Ограничения коллекций

Может сериализовать коллекцию произвольных объектов, но не может десериализовать из нее

Поскольку пользователь не может указать тип получаемого объекта

При десериализации, Collection должен иметь определенный универсальный тип

Может быть, я использую плохие практики Java, но как бы мне построить систему обмена сообщениями JSON to Java, которая отправляла / получала различные конкретные объекты сообщений в формате JSON?

Например, это не получается:

public static void main(String[] args) {
    Gson gson = new Gson();

    MockMessage mock1 = new MockMessage();
    MockMessage mock2 = new MockMessage();
    MockMessageOther mock3 = new MockMessageOther();

    List<MockMessage> messages = new ArrayList<MockMessage>();
    messages.add(mock1);
    messages.add(mock2);
    messages.add(mock3);

    String jsonString = gson.toJson(messages);

    //JSON list format is non-intuitive single element array with class name fields
    System.out.println(jsonString);
    List gsonJSONUnmarshalledMessages = (List)gson.fromJson(jsonString, List.class);
    //This will print 3 messages unmarshalled
    System.out.println("XStream format JSON Number of messages unmarshalled: " + gsonJSONUnmarshalledMessages.size());
}

[{"val":1},{"val":1},{"otherVal":1,"val":1}]
Exception in thread "main" com.google.gson.JsonParseException: The JsonDeserializer com.google.gson.DefaultTypeAdapters$CollectionTypeAdapter@638bd7f1 failed to deserialized json object [{"val":1},{"val":1},{"otherVal":1,"val":1}] given the type interface java.util.List

Вот пример, я хочу отправить список из 3 объектов Message, 2 имеют одинаковый тип, а третий - другой тип.

import java.util.ArrayList;
import java.util.List;

import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.json.JettisonMappedXmlDriver;

class MockMessage {
    int val = 1;
}
class MockMessageOther {
    int otherVal = 1;
}

public class TestJSONXStream {


    public static void main(String[] args) {
        JettisonMappedXmlDriver xmlDriver = new JettisonMappedXmlDriver();        
        XStream xstream = new XStream(xmlDriver);

        MockMessage mock1 = new MockMessage();
        MockMessage mock2 = new MockMessage();
        MockMessageOther mock3 = new MockMessageOther();

        List messages = new ArrayList();
        messages.add(mock1);
        messages.add(mock2);
        messages.add(mock3);

        String jsonString = xstream.toXML(messages);

        //JSON list format is non-intuitive single element array with class name fields
        System.out.println(jsonString);
        List xstreamJSONUnmarshalledMessages = (List)xstream.fromXML(jsonString);
        //This will print 3 messages unmarshalled
        System.out.println("XStream format JSON Number of messages unmarshalled: " + xstreamJSONUnmarshalledMessages.size());

        //Attempt to deserialize a reasonable looking JSON string
        String jsonTest = 
              "{"+
                "\"list\" : ["+ 
                          "{"+
                          "\"MockMessage\" : {"+
                              "\"val\" : 1"+
                          "}"+
                      "}, {"+
                          "\"MockMessage\" : {"+
                              "\"val\" : 1"+
                          "}"+
                      "}, {"+
                          "\"MockMessageOther\" : {"+
                              "\"otherVal\" : 1"+
                          "}"+
                      "} ]"+
                  "};";

        List unmarshalledMessages = (List)xstream.fromXML(jsonTest);

        //We expect 3 messages but XStream only deserializes one
        System.out.println("Normal format JSON Number of messages unmarshalled: " + unmarshalledMessages.size());
    }

}

Интуитивно я ожидаю, что XStream JSON будет сериализован (и сможет правильно десериализоваться) из следующего формата:

{
    "list" : [ 
        {
        "MockMessage" : {
            "val" : 1
        }
    }, {
        "MockMessage" : {
            "val" : 1
        }
    }, {
        "MockMessageOther" : {
            "otherVal" : 1
        }
    } ]
}

Вместо этого XStream создает единый список элементов с полями, которые называются именами классов и вложенными массивами объектов одного типа.

{
    "list" : [ {
        "MockMessage" : [ {
            "val" : 1
        }, {
            "val" : 1
        } ],
        "MockMessageOther" : {
            "otherVal" : 1
        }
    } ]
}

Проблема может быть вызвана использованием XStream XML CollectionConverter ?

Есть ли у кого-нибудь предложение для хорошей сериализации Java-объектов JSON, которая позволяет вам читать / записывать произвольные Java-объекты. Я посмотрел на JSON-процессор Джексона Java , но когда вы читали объекты из потока, вы должны были указать, какой тип объекта он отличается от XStream, где он будет считываться в любом объекте (поскольку сериализованный XStream JSON содержит информация об имени класса).

Ответы [ 3 ]

6 голосов
/ 11 мая 2009

Я согласен с другим автором в том, что XStream не очень подходит - это OXM (Object / Xml Mapper), а JSON обрабатывается как вторичный формат вывода с использованием пути обработки XML. Вот почему необходимо «соглашение» (о том, как преобразовать иерархическую XML-модель в объектно-графовую модель json и наоборот); и ваш выбор сводится к использованию того, что является наименее навязчивым из неоптимальных вариантов. Это работает нормально, если XML является вашим основным форматом данных, и вам просто нужна некоторая элементарная поддержка JSON (*).

Чтобы получить хорошую поддержку JSON, я хотел бы рассмотреть возможность использования библиотеки обработки JSON, которая выполняет реальное отображение OJM (я полагаю, что Свенсон тоже, но дополнительно), например:

Кроме того: даже если вам действительно нужна поддержка как XML, так и JSON, лучше использовать IMO для этих задач с помощью отдельных библиотек - объекты (бины) для использования на стороне сервера не должны отличаться, только библиотеки сериализации, которые преобразуют в / из XML и JSON.

0 голосов
/ 08 мая 2009

Сопоставитель типов Свенсона, основанный на полном имени класса, будет выглядеть примерно так

public class ClassNameBasedTypeMapper extends PropertyValueBasedTypeMapper
{
    protected Class getTypeHintFromTypeProperty(String value) throws IllegalStateException
    {
        try
        {
            return Class.forName(value);
        }
        catch (ClassNotFoundException e)
        {
            throw new IllegalStateException(value + " is no valid class", e);
        }
    }
}

, которая не является идеальной реализацией, поскольку она наследует конфигурацию PropertyValueBasedTypeMapper без особой необходимости. (должен включать более чистую версию в svenson)

Настройка очень похожа на приведенную выше

JSONParser parser = new JSONParser();
ClassNameBasedTypeMapper mapper = new ClassNameBasedTypeMapper();
mapper.setParsePathInfo("[]");
parser.setTypeMapper(mapper);

List foos = parser
    .parse( List.class, "[{\"type\":\"package.Foo\"},{\"type\":\"package.Bar\"}]");
0 голосов
/ 08 мая 2009

Я понимаю, что это не по теме, но я бы хотел представить решение в svenson JSON .

Вам действительно нужны открытые поля в ваших классах домена? Помимо использования свойств, svenson может обрабатывать подобные случаи с помощью более простого вывода JSON со свойством дискриминатора

class Message
{
   // .. your properties with getters and setters .. 
   // special property "type" acts a signal for conversion
}

class MessageOther
{
  ...
}

List list = new ArrayList();
list.add(new Message());
list.add(new MessageOther());
list.add(new Message());

String jsonDataSet = JSON.defaultJSON().forValue(list);

будет выводить JSON как

[
    {"type":"message", ... }, 
    {"type":"message_other", ... }, 
    {"type":"message", ... }
]

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

    // configure reusable parse instance
    JSONParser parser = new JSONParser();

    // type mapper to map to your types
    PropertyValueBasedTypeMapper mapper = new PropertyValueBasedTypeMapper();
    mapper.setParsePathInfo("[]");
    mapper.addFieldValueMapping("message", Message.class);
    mapper.addFieldValueMapping("message_other", MessageOther.class);
    parser.setTypeMapper(mapper);

    List list = parser.parse(List.class, jsonDataset);
...