views:

4949

answers:

3

I'm using XStream and JETTISON's Stax JSON serializer to send/receive messages to/from JSON javascripts clients and Java web applications.

I want to be able to create a list of objects to send to the server and be properly marshalled into Java but the format that XStream and JSON expect it in is very non-intuitive and requires our javascript libraries to jump through hoops.

[EDIT Update issues using GSON library] I attempted to use the GSON library but it cannot deserialize concrete objects when I only have it expect generic super classes (XStream and Jettison handles this because type information is baked into the serialization).

GSON FAQ states Collection Limitation:

Collections Limitations

Can serialize collection of arbitrary objects but can not deserialize from it

Because there is no way for the user to indicate the type of the resulting object

While deserializing, Collection must be of a specific generic type

Maybe I'm using bad java practices but how would I go about building a JSON to Java messaging framework that sent/received various concrete Message objects in JSON format?

For example this fails:

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

Here's an example, I want to send a list of 3 Message objects, 2 are of the same type and the 3rd is a different type.

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());
    }

}

Intuitively I expect the XStream JSON to be serialized (and able to deserialize correctly) from the following format:

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

Instead XStream creates a single element list with fields that are named the classnames and nested arrays of Objects of the same type.

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

The trouble may be caused by it using the XStream XML CollectionConverter?

Does anyone have a suggestion for a good JSON Java object serialization that allows you to read/write arbitrary Java objects. I looked at the Jackson Java JSON Processor but when you were reading in objects from a stream you had to specify what type of object it was unlike XStream where it will read in any object (because the serialized XStream JSON contains class name information).

A: 

I realize this is off-topic, but I'd like to present a solution in svenson JSON.

Do you really need public fields in your domain classes? Apart from having to use properties, svenson can handle cases like this with a more simple JSON output with a discriminator property

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);

would output JSON like

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

which could be parsed again with code like this

    // 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);
fforw
That sounds like an interesting approach, the only improvements I'd want is a way to not have to manually map classes to string field names when parsing it back (is there any support to infer class names, maybe output could be more verbose and contain the fulle package and class name).The public fields were just an example (although with serialization your private data is your public interface).
Dougnukem
A: 

A svenson type mapper based on the full class name would look something like this

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);
        }
    }
}

which is not an ideal implementation as it inherits the configuration of PropertyValueBasedTypeMapper without really needing. (should include a cleaner version in svenson)

The setup is very much like above

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\"}]");
fforw
+2  A: 

I agree with other poster in that XStream is not a good fit -- it's an OXM (Object/Xml Mapper), and JSON is handled as a secondary output format using XML processing path. This is why a "convention" (of how to convert hierarchich xml model into object-graph model of json and vice versa) is needed; and your choice boils down to using whatever is least intrusive of sub-optimal choices. That works ok if XML is your primary data format, and you just need some rudimentary JSON(-like) support.

To get good JSON-support, I would consider using a JSON processing library that does real OJM mapping (I assume Svenson does too, but additionally), such as:

Also: even if you do need to support both XML and JSON, you are IMO better off using separate libraries for these tasks -- objects (beans) to use on server-side need not be different, just serialization libs that convert to/from xml and json.

StaxMan
GSON looked promising but it still suffers from the problem of not being able to deserialize a collection or single element of a concrete type when all it knows is that is the parent class.For example in a generic messaging system all messages extend the base Message class and the message passing system is sent a list of concrete Message objects in JSON format. This works fine in XStream but GSON seems to have issues (see above I edited my post).
Dougnukem
Yes, this is a big problem, and is rather hard to solve. One thing XML does have over json is bit more support for metadata (attributes vs elements; convention of using "xsi:type", schema for type defs).Not impossible to solve, just tricky. Jackson version 2.0 will try to tackle this. :)
StaxMan
For what it is worth, Jackson versions 1.5 and up do support handling of polymorphic classes (deserialize into sub-classes of base class)
StaxMan