8

In one of our projects we use a java webapp talking to a MongoDB instance. In the database, we use DBRefs to keep track of some object relations. We (de)serialize with POJO objects using jackson (using mongodb-jackson-mapper).

However, we use the same POJOs to then (de)serialize to the outside world, where our front end deals with presenting the JSON.

Now, we need a way for the serialization for the outside world to contain the referenced object from a DBRef (so that the UI can present the full object), while we obviously want to have the DBRef written to the database, and not the whole object.

Right now I wrote some untested static nested class code:

public static class FooReference {
    public DBRef<Foo> foo;

    // FIXME how to ensure that this doesn't go into the database?
    public Foo getFoo() {
        return foo.fetch();
    }
}

Ideally I would like a way to annotate this so that I could (de)serialize it either with or without the getFoo() result, probably depending on some configuration object. Is this possible? Do you see a better way of going about doing this?

Gijs
  • 5,201
  • 1
  • 27
  • 42
  • How do I do that and how does it address the issue? It's my code so I can do whatever, but your comment isn't clear, and Google isn't helping, sorry. – Gijs Aug 27 '12 at 12:13
  • "transient" is a java keyword to indicate that a field should not be serialised/persisted - http://www.java-samples.com/showtutorial.php?tutorialid=331 – Nicholas Albion Aug 27 '12 at 12:16
  • Erm, that's a field modifier. This is a method. – Gijs Aug 27 '12 at 12:21
  • Does mongodb have a Transient or NonPersistant annotation for methods? – Nicholas Albion Aug 27 '12 at 12:25
  • 1
    Mongo itself has drivers (which deal with plain map-like objects), which have been extended/replaced/wrapped in various different ways to provide POJO support. It turns out Morphia has a Transient and a NotStored annotation, but I'm using mongo-jackson-mapper. A bit more googling shows that Jackson supports Views for serializing, and mongo-jackson-mapper lets you specify a view to use, so it seems that that would work. Thanks for the pointers! – Gijs Aug 27 '12 at 12:32

2 Answers2

8

From looking at options, it seems you can annotate properties to only be shown if a given View is passed to the ObjectMapper used for serialization. You could thus edit the class:

public static class FooReference {
    public DBRef<Foo> foo;

    @JsonView(Views.WebView.class)
    public Foo getFoo() {
        return foo.fetch();
    }
}

and provide:

class Views {
    static class WebView { }
}

and then serialize after creating a configuration with the correct view:

SerializationConfig conf = objectMapper.getSerializationConfig().withView(Views.WebView.class);
objectMapper.setSerializationConfig(conf);

Which would then serialize it. Not specifying the view when serializing with the MongoDB wrapper would mean the method would be ignored. Properties without a JsonView annotation are serialized by default, a behaviour you can change by specifying:

objectMapper.configure(SerializationConfig.Feature.DEFAULT_VIEW_INCLUSION, false);

More info is available on the Jackson Wiki.

There are still other alternatives, too, it turns out: there are Jackson MixIns which would let you override (de)serialization behaviour of parts of a class without modifying the class itself, and as of Jackson 2.0 (very recent release) there are filters, too.

Gijs
  • 5,201
  • 1
  • 27
  • 42
2

Use a custom JSONSerializer and apply your logic in the serialize method:

public static class FooReference {
    public DBRef<Foo> foo;

    @JsonSerialize(using = CustomSerializer.class)
    public Foo getFoo() {
        return foo.fetch();
    }
}

public class CustomSerializer extends JsonSerializer<Object> {
   public void serialize(Object value, JsonGenerator jgen, SerializerProvider provider)
       throws IOException, JsonProcessingException {
     // jgen.writeObjectField ...
   }
}
João Silva
  • 89,303
  • 29
  • 152
  • 158
  • Wouldn't this call `getFoo()` unconditionally? That would be suboptimal, as it implies a database request. Additionally, as far as I can tell the serializer would be constructed using the class ref, and therefore it has no context where it could deduce whether or not to serialize the object. So I'd need to rely on some kind of static flag present in the same thread that I twiddle just before/after serializing/deserializing? That seems very fragile. – Gijs Aug 27 '12 at 12:17
  • Yes, I've misread your question. But instead of returning `foo.fetch()` you could return a wrapper, which, in `serialize`, will determine if you need to make a database request, or serialize the field. – João Silva Aug 27 '12 at 12:20
  • That sounds good with respect to the first point I made. Regarding the second: ideally, I don't want any data in the model/POJO regarding what's going on outside the model (IE, "how am I being serialized"). That is, I'd prefer to 'pick' one of two serialization methods when I make the call to (de)serialize it. Does that make sense and is it possible using the method you described? (upvoted for pointing out JsonSerialize, in any case, but not sure it's a solution for this problem just yet) – Gijs Aug 27 '12 at 12:25
  • I follow you, it is possible using this method, but it will indeed somehow couple the serialization mechanism with the model. The only mechanism that *I* know in Jackson that allows you to do this is really `JSONSerializer`, so I'm afraid you'll have to put your custom logic in there. – João Silva Aug 27 '12 at 12:40