keeping Refs clean on deserialization?

246 views
Skip to first unread message

Matthew Pease

unread,
Aug 17, 2012, 12:15:09 PM8/17/12
to objectify...@googlegroups.com
I'm writing a REST based system using Objectify.  I'd like to use Ref<?>s internally and expose them via only getJavaObject and setJavaObject methods, if possible.   

I'd also like to keep my Ref<?>s  "clean" at all times, meaning no half full objects referenced by a Ref<?>

I'd like to accept this JSON as input:   (it represents an event and the creator that made the event)
{
 'desc' : 'gogo party',
 'creator' : {
     'id' : 44
  }
}


I think I should represent this in Java code for Objectify as:

@Entity
Event {
 @Id Long id;
 String desc;
 Ref<User> creator;

 void setCreator(User creator) {  this.creator = Ref.create(creator.getId(), creator); }
 User getCreator() {  return creator.get(); }
}

@Entity
User {
 @Id Long id;
 String name;

 Key<User> getId() { return Key.create(User.class, id); }
}


When deserializing, all that I know about the User is their id is 44L.    I want to deserialize this by filling event.creator with just the key portion of the ref set, not the value.  The value will be a partially filled in User object & I'd rather not have it in the Ref at all.

Any suggestions of how to do this?    Am I being too nit picky?


Thank you-
Matt

Jeff Schnitzer

unread,
Aug 17, 2012, 12:29:24 PM8/17/12
to objectify...@googlegroups.com
Assuming you use Jackson, look at
com.googlecode.objectify.util.jackson.ObjectifyJacksonModule. It
makes Key, Key<?>, and Ref<?> serialize into "what you want" (for my
interpretation of "what you want" ;-). It will serialize Ref<?>s in
one of two ways:

* If the ref has been loaded, it serializes to the entity.
* If the ref has not been loaded, it serializes to the stringified
version of the key

However, it's not quite so smart about deserializing refs. Right now
it only accepts the stringified key form. You can look at the
RefDeserializer and maybe come up with logic that is specific to your
application?

One issue I see is that the RefDeserializer has no general way of
getting the Key for an entity, and therefore can't create a Ref<?>
even after it deserializes your entity object. I need to do this work
to move key metadata into a static structure so you can
Ref.create(object) to make this work. I need 48 hours per day :-)

Jeff
Message has been deleted

Matthew Pease

unread,
Aug 17, 2012, 5:30:32 PM8/17/12
to objectify...@googlegroups.com, je...@infohazard.org
Yes, I'm using Jackson.  I'll have a look over ObjectifyJacksonModule and RefDeserializer.  (tomorrow as I'm in Europe)

I don't mind so much having to use the stringified keys.  Thought it seems like overkill if there is no @Parent.  

I tried to better describe "what I want".  :)

Deserializing:
 Convert sub-object:
"foo" : {
"id" : ID_VALUE
}

If only "id" is set then foo will be Ref<Foo> where ref.key == Key<Foo> is set using ID_VALUE and value is null.

"foo" : {
"id" : ID_VALUE,
"other" : "field"
}

If more than "id" is set then foo will be Ref<Foo> where value is a foo instance with the "other" field also set.

    Having a Ref.create(object) would be awesome -- but how about using @Parent and @Id?   Can't you figure out the Ref using some reflection?
   
    In this way you could also determine how you can interpret ID_VALUE on deserialization. If there is no @Parent, then Key.create(Class, ID_VALUE). If there is a parent then ID_VALUE must be a stringified Key w/ parent.

Serializing:
Sounds like ObjectifyJacksonModule is what I want.

Cheers -
Matt

Matthew Pease

unread,
Aug 20, 2012, 6:44:26 PM8/20/12
to objectify...@googlegroups.com, je...@infohazard.org
Hmm

  Any tips on using ObjectifyJacksonModule with RestEasy?   OK... I guess this is getting a little off topic.

  But maybe someone out there has done this?   

  Problem is that ObjectifyJacksonModule is based on Jackson 2.0   and the ObjectMapper you can obtain from ResteasyJacksonProvider is a Jackson 1.9 sort of ObjectMapper.

   The result is that the registerModule() method doesn't like ObjectifyJacksonModule.

Matt

On Friday, August 17, 2012 6:29:24 PM UTC+2, Jeff Schnitzer wrote:

Jeff Schnitzer

unread,
Aug 20, 2012, 8:38:34 PM8/20/12
to objectify...@googlegroups.com
On Mon, Aug 20, 2012 at 6:44 PM, Matthew Pease <mpe...@gmail.com> wrote:
> Any tips on using ObjectifyJacksonModule with RestEasy? OK... I guess
> this is getting a little off topic.
>
> But maybe someone out there has done this?
>
> Problem is that ObjectifyJacksonModule is based on Jackson 2.0 and the
> ObjectMapper you can obtain from ResteasyJacksonProvider is a Jackson 1.9
> sort of ObjectMapper.
>
> The result is that the registerModule() method doesn't like
> ObjectifyJacksonModule.

Yeah, don't use the ResteasyJacksonProvider. Derive your own
JacksonJsonProvider and use that. I'm not totally sure how you
configure it if you aren't using Guice, probably you just register it
as a normal provider.

Here's what ours looks like, but it has a lot of extra stuff to make
@JsonView work on jaxrs methods. You can ignore that. Really the
only important line is setMapper() in the constructor; pass in your
own ObjectMapper instance:


package st.voo.tick.util.json;

import java.io.IOException;
import java.io.OutputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;

import javax.inject.Inject;
import javax.inject.Singleton;
import javax.ws.rs.Consumes;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.Provider;

import unsuck.lang.ReflectionUtils;

import com.fasterxml.jackson.annotation.JsonView;
import com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider;

/**
* Overrides the normal jackson json provider's objectmapper with the
voost-specific one.
* This makes jaxrs calls serialize as we want. Note that we don't
bother using Resteasy's
* jackson integration, which loads itself outside of Guice and
becomes unavailable to us.
*
* We actually use resteasy's produces/consumes instructions here though.
*/
@Singleton
@Provider
@Consumes({"application/*+json", "text/json"})
@Produces({"application/*+json", "text/json"})
//@Slf4j
public class VoostJacksonProvider extends JacksonJsonProvider {

/** Never used, just to obtain the annotation reference */
private static class Fodder {
@SuppressWarnings("unused")
@JsonView(Object.class)
public int hasJsonView;
}

/** */
private static JsonView JSON_VIEW_ANNO =
ReflectionUtils.getField(Fodder.class,
"hasJsonView").getAnnotation(JsonView.class);
private static Annotation[] JSON_VIEW_ONLY = new Annotation[] {
JSON_VIEW_ANNO };

/** */
@Inject
public VoostJacksonProvider() {
this.setMapper(VoostObjectMapper.INSTANCE);
}

/** Unfortunately we need to add @JsonView(Object.class) to any call
that doesn't already have @JsonView */
@Override
public void writeTo(Object value, Class<?> type, Type genericType,
Annotation[] annotations, MediaType mediaType, MultivaluedMap<String,
Object> httpHeaders, OutputStream entityStream) throws IOException {

if (annotations == null) {
annotations = JSON_VIEW_ONLY;
} else if (!hasJsonView(annotations)) {
Annotation[] added = new Annotation[annotations.length + 1];
for (int i=0; i<annotations.length; i++)
added[i] = annotations[i];

added[annotations.length] = JSON_VIEW_ANNO;

annotations = added;
}

super.writeTo(value, type, genericType, annotations, mediaType,
httpHeaders, entityStream);
}

private boolean hasJsonView(Annotation[] annotations) {
for (Annotation anno: annotations)
if (anno instanceof JsonView)
return true;

return false;
}
}
Reply all
Reply to author
Forward
0 new messages