Search code examples
restjerseypojo

RESTful POJO -> JSON mapping not picking up new fields


I have a puzzling RESTful problem. I use REST using Jersey that uses POJO mapping to convert Java objects (Beans?) to JSON. Initially this worked great, but now, upon trying to add new fields to a Bean, they don't show up in the JSON the client receives. I will explain more what I mean by this later, but first let me show some code.

The controller code is here (edited to simplify it... the essential parts remain):

@Path( "study" )
public class StudyQueryController 
{
    @Path( "/query" )
    @GET
    @Produces( { MediaType.APPLICATION_JSON } )
    public List< PatientBean > queryForStudies(
            @QueryParam( "keyword" ) String keyword,
            @QueryParam( "fromDate" ) String fromDate,
            @QueryParam( "toDate" ) String toDate,
            @QueryParam( "sites" ) Set< Integer > sites,
            @DefaultValue( CLOUD_STYPE ) @QueryParam( "stypes" ) Set< Integer > stypes,
            @DefaultValue( "false" ) @QueryParam( "incDeleted" ) boolean incDeleted )
    {
        UserModel user = AuthFilter.requestingUser;

        try ( KHDatabase khdb = new KHDatabase() )
        {
               [... code is here that sets up the queries, keys, etc...]

            CommonQueries.PSSTree tree = CommonQueries.getPatientStudySeriesTree( khdb, keys );
            List< PatientBean > ret = PatientBean.mapPSSTree( tree );

            return ret;
        }
        catch (SQLException e)
        {
            [...throws error here...]
        }
    }
}

Notice that a List made up of PatientBean objects is returned. The PatientBean code is here:

public class PatientBean {

    private String patientName;
    private String patientBleah;
    private String patientID;
    private String patientSex;
    private String patientDOB;
    private int patientSite;
    private String siteName;
    private StudyBean[] studies;

//  public String getPatientName() {
//      return patientName;
//  }
    public String getPatientBleah() {       // TEST
        return patientBleah;
    }
    public String getPatientID() {
        return patientID;
    }
    public String getPatientSex() {
        return patientSex;
    }
    public String getPatientDOB() {
        return patientDOB;
    }
    public int getPatientSite() {
        return patientSite;
    }
    public void setSiteName(String newName) {
        siteName = newName;
    }
    public String getSiteName() {
        return siteName;
    }
    public StudyBean[] getStudies() {
        return studies;
    }

    /** Maps a <code>KHPatient</code>, its collection of <code>KHStudy</code>, and the map of
     *  study keys to lists of <code>KHSeries</code> of the studies, to an instance of this class. */
    public PatientBean( KHPatient pt, List< KHStudy > sts, Map< String, List< KHSeries > > srs  )
    {
        patientName = pt.getFullName().toDICOMPN();
        patientBleah = pt.getFullName().toDICOMPN();
        patientID = pt.id;
        patientSex = Character.toString( pt.sex.getDICOMCode() );
        patientDOB = pt.birthdate != null ? StringUtils.printISODate( pt.birthdate ) : "";
        patientSite = pt.site_key;


        List< StudyBean > lst = new ArrayList< StudyBean >( sts.size() );
        for ( KHStudy st : sts )
        {
            List< KHSeries > lsr = srs.get( st.key() );
            if ( lsr == null )
                lsr = Collections.emptyList();
            lst.add( new StudyBean( st, lsr ) );
        }
        studies = lst.toArray( new StudyBean[ lst.size() ] );
    }

    /** Maps a <code>CommonQueries.PSSTree</code> to an array of {@link PatientBean}. */
    public static List< PatientBean > mapPSSTree( CommonQueries.PSSTree tree )
    {
        List< PatientBean > lpt = new ArrayList< PatientBean >( tree.pts.size() );
        for ( Map.Entry< Integer, KHPatient > entry : tree.pts.entrySet() )
        {
            KHPatient pt = entry.getValue();
            List< KHStudy > lst = tree.sts.get( entry.getKey() );
            PatientBean bpt = new PatientBean( pt, lst, tree.srs );
            lpt.add( bpt );
        }
        return lpt;
    }
}

Most of the fields, such as patientName, patientID, patientSEX, patientDOB, and patientSite are part of the original version of this Bean, and are returned via JSON just fine. New fields, such as patientBleah and siteName, are NOT returned.

To ensure that this file was indeed getting recompiled by maven, I tried commenting out the getPatientName() method. This resulted in the client getting "undefined" for patientName, as expected. However, patientBleah and siteName do not show up at all in the client JSON.

I for the life of me have no idea why commenting out getPatientName() has an effect, while adding the other fields (and corresponding getter methods) has no effect.

I am rather new to RESTful services and POJO, so perhaps I am missing something obvious. I am under the impression that POJO mapping simply requires a class with a field and a corresponding public getter method, and it does the rest.

Snippet from the server's web.xml in case this is helpful as well:

  <servlet>
    <servlet-name>AimCloudREST</servlet-name>
    <servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
    <init-param>
      <param-name>com.sun.jersey.config.property.packages</param-name>
      <param-value>com.aimcloud.server;com.aimcloud.util</param-value>
    </init-param>
    <init-param>
      <param-name>com.sun.jersey.spi.container.ContainerRequestFilters</param-name>
      <param-value>com.aimcloud.util.AuthFilter</param-value>
    </init-param>
    <init-param>
      <param-name>com.sun.jersey.spi.container.ContainerResponseFilters</param-name>
      <param-value>com.aimcloud.util.ErrorFilter;com.aimcloud.util.AuthFilter</param-value>
    </init-param>
    <init-param>
      <param-name>com.sun.jersey.api.json.POJOMappingFeature</param-name>
      <param-value>true</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
  </servlet>

I did try using "mvn clean package" to compile, but this didn't help. I also tried stopping and restarting tomcat after recompiling.


Solution

  • I discovered the issue is that the JSON response being sent to the client starts as an unnamed array. In other words, it looked like this:

    [{"patients":[{"patientName":"ASHA01^^^^==","patientBleah":"ASHA01^^^^==",[...],"patientSite":1}]
    

    This is NOT parsed properly by JavaScript. Arrays must be named, like so:

    {"myArray":[...]}
    

    I traced this back to this line in the RESTful Jersey code:

    List< PatientBean > ret = PatientBean.mapPSSTree( tree );
    

    This "ret" object is what's returned to the user. This is what results in an unnamed array being returned as the JSON, which can't be parsed correctly by JavaScript. (Despite being parsed correctly by http://www.jsoneditoronline.org/)

    The solution here was to wrap my result in a higher level object which has a single element: List. I then return this object, and then my array has a name, and the resulting JSON is interpreted properly.

    public class ResultBean {
    
        private List< PatientBean > patients;
    
        public List< PatientBean > getPatients() {
            return patients;
        }
    
        public ResultBean( List< PatientBean > inPatients )
        {
            patients = inPatients;
        }
    }