[JERSEY-551] Improved serialization of java.util.Map Created: 22/Jun/10  Updated: 04/Nov/13

Status: Open
Project: jersey
Component/s: media
Affects Version/s: None
Fix Version/s: icebox

Type: Improvement Priority: Major
Reporter: romixlev Assignee: Unassigned
Resolution: Unresolved Votes: 2
Labels: None
Remaining Estimate: Not Specified
Time Spent: Not Specified
Original Estimate: Not Specified
Environment:

Operating System: All
Platform: All
URL: http://old.nabble.com/Custom-mapping-for-Maps-in-JAXB-ts28779221r0.html


Attachments: Java Source File JsonMapAdapter.java     Java Source File JsonMapAdapter.java    
Issuezilla Id: 551

 Description   

Currently, to serialize java.util.Map objects to JSON, one needs to write a
special @XmlJavaTypeAdapter annotation and usually covert the map into list of
(key,value) entires, which are then marshalled/unmarshalled by JAXB.
The representation for a key/value pair produced by this approach usually looks
like this:

{"key": "MyKey", "value" : "MyValue"}

This is not very natural for JSON, as

{"MyKey":"MyValue"}

would be a more usual
JSON-like representation. Unfortunately, this JSON-friendly representation
cannot be easily produced by jersey-json, because it works via JAXB and JAXB
cannot produce this out of the box easily, as it relies on XML Schemas and this
representation requires XML tag names that are dynamic, which is impossible.

Therefore, it would be nice to introduce the possibility to serialize Maps into
a natural JSON format.

Please find attached a class called JsonMapAdapter.
It can be used like this to achieve the desired effect:

@XmlRootElement(name="YourClassElementName")
class YourClass {
...

@XmlElement(name="YourMapElementName")
// Here is the trick: Use use special map adapter for maps (see below)
@XmlJavaTypeAdapter(JsonMapAdapter.class)
private Map<String,String> yourMap = new LinkedHashMap<String,String>();

...
}

Please see this mailing list discussion for more information:
http://old.nabble.com/Custom-mapping-for-Maps-in-JAXB-ts28779221r0.html

NOTE: This approach seems to work nicely for Map<String,String>. But if values
are JAXB-annotated classes, it most likely would not work out of the box. The
way to solve this issue still needs to be investigated.



 Comments   
Comment by romixlev [ 22/Jun/10 ]

Created an attachment (id=131)
XML type adapter implementation for producing JSON friendly format from java.util.Map

Comment by romixlev [ 25/Jun/10 ]

Created an attachment (id=133)
New version of JsonMapAdapter that supports also JAXB-annotated classes as Values of Maps

Comment by romixlev [ 25/Jun/10 ]

>NOTE: This approach seems to work nicely for Map<String,String>. But if values
>are JAXB-annotated classes, it most likely would not work out of the box. The
>way to solve this issue still needs to be investigated.

I attached a new version of the JsonMapAdapter class, which tries to address the
mentioned limitation. It seems to work for me on a bunch of use-cases. In
particular, values inside maps can be @XmlRootElement types. And this new
version also supports @XmlRootElement, where name or namespace annotation
parameters are not specified.

An important point for mapped notation is not to forget to call the xml2JsonNs
in the builder with a complete set of namespace used by your JAXB classes, as
namespace information is otherwise lost in the mapped notation.

Please test if it works for you and provide input about discovered problems.

TODO: Right now, unmarshalling would only work for a value of class C, if at
least one object of class C was marshalled before as a value of the map. So,
first marshal, then unmarshal. The reason for this limitation is due to the fact
that JsonMapAdapter needs to be able to map qualified XML element names to the
JAXB annotated classes and JAXBContexts. And at the moment this mapping is done
implicitly during marshalling.
Alternatively, one can think about explicit APIs for developers to provide such
a mapping at run-time to the generic JsonMapAdapter.

Comment by mike_meessen [ 28/Nov/10 ]

romixlev, your solution looks like a good starting point!

I ran into an issue though:
in your "marshal" method, the following line fails if the key is numeric:

Element keyValueElement = doc.createElement(entry.getKey().toString());

entity.getKey().toString() produces e.g. "4", which is not a valid xml entity and causes:

SEVERE: org.w3c.dom.DOMException: INVALID_CHARACTER_ERR: An invalid or illegal XML character is specified.
SEVERE: at com.sun.org.apache.xerces.internal.dom.CoreDocumentImpl.createElement(CoreDocumentImpl.java:618)

Any idea how to fix this? If I change the line to the following, it works but output is obviously wrong:

Element keyValueElement = doc.createElement("x" + entry.getKey().toString());

All in all, XML isn't Json and having to jump through it ends up being a real pain :/

Comment by Paul Merlin [ 24/Feb/11 ]

@mike_meessen I ran into the very same issue, it is impossible to use a string key starting with a number. In case of UUIDs it works only when the UUID start with a letter.

From what I understand it's impossible to solve the issue except by removing JAXB from the equation.

That's a real pitty that JAX-RS 1 cannot produce simple json like that. I don't know about JAX-RS 2 but if it's still based on JAXB the problem will remain.

I hope someone can prove us wrong and tell us "there is a way and this is ...".

Regards

Generated at Sat May 30 11:56:02 UTC 2015 using JIRA 6.2.3#6260-sha1:63ef1d6dac3f4f4d7db4c1effd405ba38ccdc558.