1

In order to solve the bug in Jersey that fails to serialize correctly a list with (only) one element, to wit:

"list":"thing"

instead of

"list": [ "thing" ]

I've written the code below which very nearly solves it, but (infuriatingly) gives me no way to tell it not to enclose the whole result in double quotes like this:

"list": "[ "thing" ]"

I'm out of options and would thank profusely anyone who sees clearly through this. Note that I've also attempted the ContextResolver< JAXBContext > as a @Provider solution suggested by a couple of posts out there, but Jersey never calls that code at all. This solution is the only one that comes close.

By the way, here's the consuming field in the POJO:

@XmlAnyElement
@XmlJavaTypeAdapter( JaxBListAdapter.class )
private List< String > list = new ArrayList< String >();

And here's the code:

public class JaxBListAdapter extends XmlAdapter< Element, List< String > >
{
    private static Logger log = Logger.getLogger( JaxBListAdapter.class );

    private DocumentBuilder documentBuilder =
                   DocumentBuilderFactory.newInstance().newDocumentBuilder();

    @Override
    public Element marshal( List< String > list )
    {
        Document document    = documentBuilder.newDocument();
        Element  rootElement = document.createElement( "list" );

        document.appendChild( rootElement );

        if( list != null )
        {
            StringBuilder sb = new StringBuilder();

            sb.append( "[ " );

            boolean first = true;

            for( String item : list )
            {
                if( first )
                    first = false;
                else
                    sb.append( ", " );

                sb.append( "\"" + item + "\"" );
            }

            sb.append( " ]" );

            rootElement.setTextContent( sb.toString() );
        }

        return rootElement;
    }

    @Override
    public List< String > unmarshal( Element rootElement )
    {
        // Hmmmm... never callled?
        NodeList       nodeList = rootElement.getChildNodes();
        List< String > list     = new ArrayList< String >( nodeList.getLength() );

        for( int x = 0; x < nodeList.getLength(); x++ )
        {
            Node node = nodeList.item( x );

            if( node.getNodeType() == Node.ELEMENT_NODE )
                list.add( node.getTextContent() );
        }

        return list;
    }
}
Russ Bateman
  • 18,333
  • 14
  • 49
  • 65
  • Uh, it turns out that the the ContextResolver< JAXBContext >/@Provider not working was wrong. I finally realized that I did need to tell Jersey about it in web.xml. Now it works. I'm going to post that solution here as an answer, but not as THE answer. It might still be worth answering (or, at least, poo-pooing) this question. – Russ Bateman Mar 13 '13 at 18:38

1 Answers1

0

I hope I won't offend anyone by posting this work-around that some might accuse as being unrelated or only obliquely related to the original question. It is a solution to the problem I was trying to solve using the approach in the question. Someone looking at the question for the same reason might find this useful.

Ultimately, the solution using ContextResolver< JAXBContext > works.

Here's my code:

package com.acme.web.user.pojo;

import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;

import javax.ws.rs.ext.ContextResolver;
import javax.ws.rs.ext.Provider;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;

import com.sun.jersey.api.json.JSONConfiguration;
import com.sun.jersey.api.json.JSONJAXBContext;

/**
 * This class will solve lists containing only one member which Jersey doesn't bracket.
 * It ensures the brackets [ ... ] in generated JSON.
 */
@Provider
public class JaxBContextResolver implements ContextResolver< JAXBContext >
{
    private final JAXBContext context;

    @SuppressWarnings( "rawtypes" )
    private final Set< Class > types;

    @SuppressWarnings( "rawtypes" )
    private final Class[] classTypes = { Account.class };

    @SuppressWarnings( "rawtypes" )
    public JaxBContextResolver() throws JAXBException
    {
        context = new JSONJAXBContext( JSONConfiguration.natural().build(), classTypes );
        types   = new HashSet< Class >( Arrays.asList( classTypes ) );
    }

    public JAXBContext getContext( Class< ? > objectType )
    {
        return types.contains( objectType ) ? context : null;
    }
}

But, you have to add the package to web.xml too or Jersey won't go looking for this class as a @Provider.

<init-param>
  <param-name>com.sun.jersey.config.property.packages</param-name>
  <param-value>com.acme.web.user.pojo</param-value>
</init-param>
Russ Bateman
  • 18,333
  • 14
  • 49
  • 65