jersey
  1. jersey
  2. JERSEY-551

Improved serialization of java.util.Map

    Details

    • 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.

      1. JsonMapAdapter.java
        7 kB
        romixlev
      2. JsonMapAdapter.java
        2 kB
        romixlev

        Activity

        Hide
        Paul Merlin added a comment -

        @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

        Show
        Paul Merlin added a comment - @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
        Hide
        mike_meessen added a comment -

        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 :/

        Show
        mike_meessen added a comment - 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 :/
        Hide
        romixlev added a comment -

        >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.

        Show
        romixlev added a comment - >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.
        Hide
        romixlev added a comment -

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

        Show
        romixlev added a comment - Created an attachment (id=133) New version of JsonMapAdapter that supports also JAXB-annotated classes as Values of Maps
        Hide
        romixlev added a comment -

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

        Show
        romixlev added a comment - Created an attachment (id=131) XML type adapter implementation for producing JSON friendly format from java.util.Map

          People

          • Assignee:
            Unassigned
            Reporter:
            romixlev
          • Votes:
            2 Vote for this issue
            Watchers:
            1 Start watching this issue

            Dates

            • Created:
              Updated: