
|
Re: [jettison-dev] Re: [jettison-user] Re: inconsistent JSON conversion of single element ArrayList fields
Dan, I've been talking with the JACKSON dev team it sounds like "polymorphic deserialization" is something they're tackling in the next release. I'm going to help them out with it and see if the solution fits my problem.
XStream/Jettison has worked for us and I would like to be able to patch it to support polymorphic deserialization in a cleaner manner. Supporting an @class meta-data would produce much cleaner JSON for dynamic languages handling polymorphic data and would give them an easy "syntax" to produce JSON that could be consumed by strongly typed languages.
JACKSON discussion: http://archive.codehaus.org/lists/org.codehaus.jackson.dev/msg/fba570180911022245o701a628fj33c833bd61eb487a@...
~ doug daniels
On Wed, Oct 14, 2009 at 11:33 AM, Dan Diephouse <dan.diephouse@...> wrote:
Hi Doug,This sounds reasonable. Do you have a patch for this? I'm happy to give you commit rights if it's something you want to hack on. I don't have the bandwidth these days to work on Jettison too much.
BTW, you might also want to check out Jackson which overall is a much cleaner solution to the whole JSON databinding problem.
Dan
On Tue, Oct 13, 2009 at 6:59 PM, Doug Daniels <daniels.douglas@...> wrote:
It's been about 9 months since I brought up the topic but I wanted to ask for some feedback on a proposed JSON format change.
There's an issue with the usability of the JSON generated for Collections by the JettisonMappedXmlDriver that makes it difficult to use as a format for AJAX javascript clients or Flash clients.
When a collection is serialized to JSON (collection of Objects or primitive types) there are 3 special cases that you need to code to handle in client code that uses the JSON: 1. A single element of a given concrete type
2. Multiple elements of a concrete type 3. Multiple elements of different types
I'm wondering if there is some inherent limitation in the XML hierarchical implementation that wouldn't allow the JSON format to use a meta attribute like "@class" (which XStream appears to do to indicate different subclasses of a given Collection implementation) and look something like:
{"list": [
{"@class":"Message","id": 2},
{"@class":"NamedMessage","id": 2, "name": "testName"},
]}
Then at least the format would be consistent and we wouldn't have to write special javascript code to dig into an object graph just to iterate over the objects, and you'd only be forced to deal with explicit type information if you wanted to (e.g. javascript code could simply ignore "@class"). We could support primitive type Collections by doing basic String to primitive type conversion (Double if there is a decimal, Long if it is a numeric, Boolean if not numeric, String if all else fails), or worst case we could include the "@class" for primitive types as well (but that would pollute the generated JSON).
Here's an example of the existing system using XStream and JETTISON: 1. A single element of a given concrete type (a single element JSONArray with an object that has a field that is the Class that points to the actual JSONObject contents):
{"list": [ { "Message": {"id": 2} } ]}
2. Multiple elements of a concrete type (a single element JSONArray with an object that has 2 fields that are the Classes that point to the actual JSONObject contents)::
{"list": [{ "Message": {"id": 2}, "NamedMessage": { "id": 2, "name": "testName" } }]}
3. Multiple elements of different types: {"list": [ { "Message": [ {"id": 2}, {"id": 2} ], "NamedMessage": [ { "id": 2,
"name": "testName" }, { "id": 2, "name": "testName" } ] }]}
Here's an example of what I think the JSON should look like. The generated JSON could be optimized to not generate the "@class" meta-data field if the Collection is a primitive array like Message[] then we'd only need "@class" for subclasses of Message like NamedMessage, this won't work for parametrized Collection classes because of type erasure.:
1. A single element of a given concrete type:
{"list": [ {"@class":"Message","id": 2} ]}
2. Multiple elements of a concrete type:
{"list": [ {"@class":"Message","id": 2}, {"@class":"NamedMessage","id": 2, "name": "testName"},
]}
3. Multiple elements of different types: {"list": [
{"@class":"Message","id": 2},
{"@class":"NamedMessage","id": 2, "name": "testName"}, {"@class":"Message","id": 2},
{"@class":"NamedMessage","id": 2, "name": "testName"},
]}
Here's Java code to run the XStream Jettison test examples: package com.webwars.network.inventory;
import java.util.ArrayList; import java.util.List;
import org.codehaus.jettison.json.JSONException;
import org.codehaus.jettison.json.JSONObject;
import com.thoughtworks.xstream.XStream; import com.thoughtworks.xstream.io.json.JettisonMappedXmlDriver;
class Message { public long id = 2; }
class NamedMessage extends Message { public String name = "testName"; }
public class BasicJSONFormatTesting { public static void main(String[] args) throws JSONException {
BasicJSONFormatTesting jsonFormatTesting = new BasicJSONFormatTesting(); XStream xstream = new XStream(new JettisonMappedXmlDriver()); xstream.alias("Message", Message.class); xstream.alias("NamedMessage", NamedMessage.class);
List<Message> msgs = null; //Convert single element message to JSON msgs = jsonFormatTesting.generateSingleElementListOfMessages(); String jsonString = xstream.toXML(msgs);
String jsonPrettyPrint = new JSONObject(jsonString).toString(2); System.out.println(jsonPrettyPrint); //Convert one of each type to JSON msgs = jsonFormatTesting.generateOneElementOfEachTypeListOfMessages();
jsonString = xstream.toXML(msgs); jsonPrettyPrint = new JSONObject(jsonString).toString(2); System.out.println(jsonPrettyPrint); //Convert mixed list of messages to JSON
msgs = jsonFormatTesting.generateListOfMessages(); jsonString = xstream.toXML(msgs); jsonPrettyPrint = new JSONObject(jsonString).toString(2); System.out.println(jsonPrettyPrint);
} public Message generateSimpleMessage() { Message m = new Message(); return m; } public Message generateNamedMessage() { Message m = new NamedMessage();
return m; } public List<Message> generateSingleElementListOfMessages() { List<Message> messages = new ArrayList<Message>(); messages.add(generateSimpleMessage());
return messages; } public List<Message> generateOneElementOfEachTypeListOfMessages() { List<Message> messages = new ArrayList<Message>(); messages.add(generateSimpleMessage());
messages.add(generateNamedMessage()); return messages; } public List<Message> generateListOfMessages() { List<Message> messages = new ArrayList<Message>();
messages.add(generateSimpleMessage()); messages.add(generateNamedMessage()); messages.add(generateSimpleMessage()); messages.add(generateNamedMessage()); return messages;
}
}
On Tue, Jan 6, 2009 at 2:01 PM, Doug Daniels <daniels.douglas@...> wrote:
I forgot to include the test class.
JSONSingleElementTest.java:
import java.util.ArrayList;
import java.util.List;
import com.thoughtworks.xstream.XStream; import com.thoughtworks.xstream.io.json.JettisonMappedXmlDriver;
class Shelf { private List<Box> boxes = new ArrayList<Box>(); public void addBox(Box box) { boxes.add(box); } }
class Box { private List<Item> contents = new ArrayList<Item>();
public void addItem(Item item) { contents.add(item); } }
class Item { private String name;
public Item(String name) { this.name = name;
} }
public class JSONSingleElementTest {
/** * @param args */ public static void main(String[] args) { JettisonMappedXmlDriver xmlDriver = new JettisonMappedXmlDriver();
XStream xstream = new XStream(xmlDriver); Shelf shelf = new Shelf(); Box b = new Box(); shelf.addBox(b); Item item1 = new Item("test1"); b.addItem(item1);
Item item2 = new Item("test2"); b.addItem(item2); b = new Box(); shelf.addBox(b); item1 = new Item("test1"); b.addItem(item1);
item2 = new Item("test2"); b.addItem(item2); b = new Box(); shelf.addBox(b); item1 = new Item("test1"); b.addItem(item1);
item2 = new Item("test2");
b.addItem(item2); b = new Box(); shelf.addBox(b); item1 = new Item("test1"); b.addItem(item1); item2 = new Item("test2");
b.addItem(item2);
String jsonString = xstream.toXML(shelf); System.out.println(jsonString); } } On Tue, Jan 6, 2009 at 12:56 PM, Doug Daniels <daniels.douglas@...> wrote:
I'm converting an object graph into JSON using XStream (1.3.2-SNAPSHOT) and the JettisonMappedXmlDriver (Jettison 1.1-SNAPSHOT), and when it converts a collection field that contain single element collection fields I get inconsistent JSON. The first element converts the child object's fields as single element Javascript Array the second element converts it as a Javascript Object the third and subsequent elements are single element Javascript Arrays. See the below JSON, I also included a test class. I'm able to serialize and deserialize the JSON string in Java fine, but it's a bit weird in Javascript when I expect the fieldto be an Array and sometimes its an Object.
I noticed on the release page that there was some kind of support added to Jettison 1.1 to handle single element arrays and collections: "Support special Jettison functionality for JSON to detect collections or arrays with one element introduced with Jettison 1.0."
Here's the JSON output (the trouble is the second contents object is a Javascript Object { } and not a single element javascript Array [ { } ] like the other elements): { "com.webwars.network.combat.Shelf" : {
"boxes" : [ { "com.webwars.network.combat.Box" : [ { "contents" : [ { "com.webwars.network.combat.Item" : [ { "name" :"test1"
}, { "name" :"test2" } ] } ] }, { "contents" : { "com.webwars.network.combat.Item" : [ {
"name" :"test1" }, { "name" :"test2" } ] } }, {
"contents" : [ { "com.webwars.network.combat.Item" : [ { "name" :"test1" }, { "name" :"test2"
} ] } ] }, { "contents" : [ { "com.webwars.network.combat.Item" : [ { "name" :"test1"
}, { "name" :"test2" } ] } ] } ] } ] } }
JSONSingleElementTest.java
-- Dan Diephouse http://mulesource.com | http://netzooid.com/blog
|