[2.3-java] Bind JSON to model with nested collection/map

368 views
Skip to first unread message

Oriental Sensation

unread,
Oct 12, 2014, 1:25:59 PM10/12/14
to play-fr...@googlegroups.com
Hi,

My issue is very simple:

I built a model with few entities, each corresponding to its database field. This is straightforward. I also have an API gateway that works with JSON only. Accepts JSON and replies with JSON. So, once the client sends in JSON data, I bind it to the model and save it in database.

When the JSON has only simple K:V values, it's easy. However, I wish now to support a situation where:
  • JSON is like so (nested collection/map):
    {"k1": "v1", "k2": "v2", "geo": {"lat": 12, "long": 13}}
     
  • The database obviously will have two fields, in the same table, to host the geo data.
My problem now is how to convey this in the aforementioned model?

Your help is greatly appreciated, thank you!

/OS

PS: In Java please.

Josh Padnick

unread,
Oct 12, 2014, 6:49:13 PM10/12/14
to play-fr...@googlegroups.com
Play uses the Jackson FasterXML library to process JSON (don't be fooled by the name; it's really a suite of tools, one of which is JSON parsing).  So you can always research there to see your full array of options.

What you probably need to do is both serialize and deserialize between your POJO and a JSON object.  Here are examples of both of those, using a JSON that has an embedded rich object as per your question.

Defining Your Model
public class OrgUser {


   
private java.lang.Integer userId;
   
private java.lang.String  username;
   
private java.lang.Integer orgPersonId;
   
private OrgPerson orgPerson;


   
public OrgUser() {}


 
// This annotation on a getter tells Jackson FasterXML to encode this property simply as "id" when converting from POJO to JSON
   
@JsonProperty("id")
 
public java.lang.Integer getUserId() {
 
return this.userId;
 
}


   
// This annotation on a setter tells Jackson FasterXML to look call this setter when Jackson encounters an "id" in the JSON when converting to POJO
   
@JsonProperty("id")
 
public void setUserId(java.lang.Integer userId) {
     
this.userId = userId;
 
}


 
//... standard getter/setter's
 
 
// Jackson will automatically convert this OrgPerson POJO to a JSON with a property "orgPerson" whose properties are the POJO properties of OrgPerson
   
public OrgPerson getOrgPerson() {
       
return orgPerson;
   
}


   
// Jackson will automatically convert any "OrgPerson" property on a JSON to the "orgPerson" POJO property on this POJO
   
public void setOrgPerson(OrgPerson orgPerson) {
       
this.orgPerson = orgPerson;
   
}
}


Then in your controller you can convert to or from JSON as follows.

Controller: Convert a JSON Payload to a POJO

JsonNode json = request().body().asJson();
// Assumes the JSON payload is something like { orgUser: { ...key-value pairs... } }
OrgUser orgUser = Json.fromJson( json.get( "orgUser" ), OrgUser.class );

Controller: Convert from a POJO to JSON

OrgUser orgUser = somehowGetOrgUser();
ObjectNode json = Json.newObject();
json
.put( "orgUser", Json.toJson( orgUser ) );

It's pretty easy to work with once you setup your first example.  HTH,

Josh

Oriental Sensation

unread,
Oct 14, 2014, 2:53:23 AM10/14/14
to play-fr...@googlegroups.com
Many, many thanks Josh for this complete answer. I will take it from here and hopefully apply the ideas in my model.

/OS

Oriental Sensation

unread,
Oct 14, 2014, 1:00:32 PM10/14/14
to play-fr...@googlegroups.com
Hi again,

I was hoping that what you showed earlier was enough, but unfortunately I think I'm missing a key understanding here.

I'll expand on the OrgUser class that you showed: In my use-case, the same class is used as an ORM model too. This means that it must serve two purposes: As JSON POJO and a model for Ebean. So if you concentrate on orgPerson attribute, that is a POJO that needs to be modeled so Ebean could handle it and properly store/load it from persistent layer.

This is, I believe, the last key understanding that I'm still struggling with.

Have you an idea on how this is done?

Thanks again,

/OS


On Monday, October 13, 2014 12:49:13 AM UTC+2, Josh Padnick wrote:

Josh Padnick

unread,
Oct 14, 2014, 1:37:41 PM10/14/14
to play-fr...@googlegroups.com
Hi there OS,

I'm glad the initial post was of some help.  I'm afraid I can't help you with Ebean since I have no experience with it. 

One word of caution on EBean.  It's going to be deprecated in future versions of the Play docs over JPA (See https://docs.google.com/a/omedix.com/document/d/11sVi1-REAIDFVHvwBrfRt1uXkBzROHQYgmcZNGJtDnA/pub under the "Database" heading).  It will still work fine, but if you're just getting started, you may be better off with JPA.

FWIW, we use jOOQ (http://www.jooq.org), which has been outstanding.  You can see a sample of how we use it at this post: https://groups.google.com/d/msg/jooq-user/YTa-QnS3hI4/KnKSJ_FRDOcJ

Basically, our flow is as follows:
There may be leaner way to do this, and the boilerplate can be cumbersome sometimes, but we never have to delineate all the properties of a POJO for anything except when designing the database.  Also, when we find the time, we'll probably write some code generators to automate-away most of the boilerplate.  So far, it's kept things very organized and clean.

Anyway, I know this doesn't answer your direct question, but hopefully this is helpful.

Josh

Oriental Sensation

unread,
Oct 14, 2014, 3:24:10 PM10/14/14
to play-fr...@googlegroups.com
Josh,

You have been incredibly helpful, consistent and patient with me. Thank you very much!
I know about Ebean being deprecated. I am not a fan of JPA so I'm going to investigate jOOQ as I think this would be quick and easy to implement.

Thank you again, and since this is now off-topic discussion, I guess we can tie it up and call it a resolved matter :)

/OS

Josh Padnick

unread,
Oct 14, 2014, 4:23:14 PM10/14/14
to play-fr...@googlegroups.com
Happy to help!

We had a bit of a learning curve even to do basic CRUD in Java with Play, but once we got the patterns down, it's been a pleasure.  Java 8 is also nice because lambda's make the code a lot more concise.

Here's an example DAO implementation, in case this helps. Note that we're using Google Guice for DI.  Also, there's probably a better way to handle passing the database connection around.  We stuff it in the ctx().args object, which is where you can put request-scoped items, and then our service class just looks to see if it's available, or otherwise throws an exception.  This caused a couple problems when doing testing, but most of our DAO tests just connect directly to the database.

LocationRecord is auto-generated by jOOQ, as is the LOCATION reference to our "Location" table.  The line db.jooq.Utils.resetChangedOnNotNull( locationRecord ); is because of this issue, and you can use this fix in the meantime.

Note how we use our business rules to set some property likes lastModifiedBy, but otherwise, jOOQ handles all our properties automagically for us. 

Also, check out how easy the find() query is.  jOOQ gives you typesafe SQL statements written in Java so it's easy to customize these types of queries with JOINs or other various SQL concepts.

Josh

public class LocationDaoImpl implements LocationDao {

   
private Location location;

   
private final AragornDatabaseConnection aragornDatabaseConnection;
   
private final SessionSvc sessionSvc;

   
@Inject
   
public LocationDaoImpl( AragornDatabaseConnection aragornDatabaseConnection,
                           
SessionSvc sessionSvc ) {
       
this.aragornDatabaseConnection = aragornDatabaseConnection;
       
this.sessionSvc = sessionSvc;
   
}


   
@Override
   
public void setLocation(Location location) {
       
this.location = location;
   
}


   
@Override
   
public Location insert() throws AragornDaoInvalidStateException, SQLException {
       
// Validate the service class state
       
if ( this.location == null ) {
           
throw new AragornDaoInvalidStateException( "ERROR: You called insert(), but location has not been set yet." );
       
}

       
// Get database connection
        aragornDatabaseConnection
.setHttpContext( ctx() );
       
DSLContext create = DSL.using( aragornDatabaseConnection.getConfiguration() );

       
this.location.setCreatedByUserId( sessionSvc.getCurrentUserId() );
       
this.location.setLastModifiedByUserId( sessionSvc.getCurrentUserId() );
       
LocationRecord locationRecord = create.newRecord( LOCATION, this.location );
        db
.jooq.Utils.resetChangedOnNotNull( locationRecord );
        locationRecord
.insert();


       
return locationRecord.into( Location.class );
   
}

    @Override
    public Location findLocationById(Integer locationId) throws SQLException, AragornDaoInvalidParameterException {
        // Validation
        if ( locationId == null ) {
            throw new AragornDaoInvalidParameterException( "ERROR: You attempted to find a Location you but supplied a null locationId" );
        }

        // Get database connection
        aragornDatabaseConnection.setHttpContext( ctx() );
        DSLContext create = DSL.using( aragornDatabaseConnection.getConfiguration() );

        Record result = create.select()
                .from( LOCATION )
                .where( LOCATION.LOCATION_ID.eq( locationId ) )
                .fetchOne();

        // Convert the results into POJOs
        Location location = result.into(Location.class);

        return location;
    }

Oriental Sensation

unread,
Oct 15, 2014, 3:00:14 AM10/15/14
to play-fr...@googlegroups.com
Many thanks again, Josh. This looks very nice and easy, I like it.

I'm going to investigate using jOOQ with Play, hopefully it'll be an easy ride :)

/OS
Reply all
Reply to author
Forward
0 new messages