8

To solve my type mismatch problem discussed in this thread I created custom Deserializers and added them to ObjectMapper. However the performance deteriorates significantly with this.

With default deserializer i get 1-2 Garbage collection calls in logcat while with custom deserializer there are at least 7-8 GC calls, and hence the processing time is also increase significantly.

My Deserializer :

public class Deserializer<T> {

public JsonDeserializer<T> getDeserializer(final Class<T> cls) {
  return new JsonDeserializer<T> (){

     @Override
     public T deserialize(JsonParser jp, DeserializationContext arg1) throws IOException, JsonProcessingException {
        JsonNode node = jp.readValueAsTree();
        if (node.isObject()) {
          return new ObjectMapper().convertValue(node, cls);
        }
        return null;
     }
 };
}
} 

And I am using this to add to Mapper

public class DeserializerAttachedMapper<T> {

   public ObjectMapper getMapperAttachedWith(final Class<T> cls , JsonDeserializer<T> deserializer) {
      ObjectMapper mapper = new ObjectMapper();
      SimpleModule module = new SimpleModule(deserializer.toString(), new Version(1, 0, 0, null, null, null));
      module.addDeserializer(cls, deserializer);
      mapper.registerModule(module);
      return mapper;
   }
}

EDIT: Added extra data

My JSON is of considerable size but not huge: I have pasted it here

Now for parsing the same JSON if i use this code:

   String response = ConnectionManager.doGet(mAuthType, url, authToken);
    FLog.d("location object response" + response);
  //        SimpleModule module = new SimpleModule("UserModule", new Version(1, 0, 0, null, null, null));
  //        JsonDeserializer<User> userDeserializer = new Deserializer<User>().getDeserializer(User.class);     
  //        module.addDeserializer(User.class, userDeserializer);

    ObjectMapper mapper = new ObjectMapper();
  //        mapper.registerModule(module);
    JsonNode tree = mapper.readTree(response);
    Integer code = Integer.parseInt(tree.get("code").asText().trim());

    if(Constants.API_RESPONSE_SUCCESS_CODE == code) {
        ExploreLocationObject locationObject = mapper.convertValue(tree.path("response").get("locationObject"), ExploreLocationObject.class);
        FLog.d("locationObject" + locationObject);
        FLog.d("locationObject events" + locationObject.getEvents().size());
        return locationObject;
    }       
    return null;    

Then my logcat is like this

But if I use this code for same JSON

        String response = ConnectionManager.doGet(mAuthType, url, authToken);
    FLog.d("location object response" + response);
    SimpleModule module = new SimpleModule("UserModule", new Version(1, 0, 0, null, null, null));
    JsonDeserializer<User> userDeserializer = new Deserializer<User>().getDeserializer(User.class);

    module.addDeserializer(User.class, userDeserializer);
    ObjectMapper mapper = new ObjectMapper();
    mapper.registerModule(module);
    JsonNode tree = mapper.readTree(response);
    Integer code = Integer.parseInt(tree.get("code").asText().trim());

    if(Constants.API_RESPONSE_SUCCESS_CODE == code) {
        ExploreLocationObject locationObject = mapper.convertValue(tree.path("response").get("locationObject"), ExploreLocationObject.class);
        FLog.d("locationObject" + locationObject);
        FLog.d("locationObject events" + locationObject.getEvents().size());
        return locationObject;
    }       
    return null;        

Then my logcat is like this

Community
  • 1
  • 1
vKashyap
  • 580
  • 6
  • 17
  • 1
    One additional comment: make absolutely sure you reuse `ObjectMapper` -- it is a heavy-weight object and should not be created once per request. Otherwise it could definitely cause lots of GC activity. Hard to know for sure from just code above. – StaxMan Nov 03 '12 at 04:12
  • 1
    Actually I noticed that your deserializer creates an ObjectMapper: this is very costly. You can avoid that by using `JsonParser.getCodec()`, casting result to `ObjectMapper` (which is safe upcast). That should help quite a bit too. – StaxMan Nov 03 '12 at 04:15
  • I modified my code and added object mapper to my singleton, the performance improved for default deserializer but the problem with custom deserializer is still there. Also, I noticed that if i add two deserializers to same module then the default deserializer gets called not the custom one. The `JsonParser.getCodec()` isn't a static method , so I will have to create `JsonParser` for each request. won't that be expensive too?? – vKashyap Nov 05 '12 at 05:30
  • Behavior of two deserializers sounds wrong; default one should only be called if there is no custom one for the type. On `JsonParser` -- should not create new one, but if you are in `deserialize(...)` method, you are passed one (not mapper). So all I am saying is that you can always find a mapper, iff you have `JsonParser` or `JsonGenerator` (that has reference; this is true if parser/generator was created by `ObjectMapper`). – StaxMan Nov 05 '12 at 18:12
  • just wanted to add that `Jsonparser.getCodec()` in deserializer results in `stackOverFlow` exception in this case, probably because it calls the same `deserialize(JsonParser jp, DeserializationContext arg1)` method of the deserializer, resulting in infinite loop. – vKashyap Dec 13 '12 at 08:43

2 Answers2

3

How big is the object? Code basically builds a tree model (sort of dom tree), and that will take something like 3x-5x as much memory as the original document. So I assume your input is a huge JSON document.

You can definitely write a more efficient version using Streaming API. Something like:

JsonParser jp = mapper.getJsonFactory().createJsonParser(input);
JsonToken t = jp.nextToken();
if (t == JsonToken.START_OBJECT) {
   return mapper.readValue(jp, classToBindTo);
}
return null;

it is also possible to implement this with data-binding (as JsonDeserializer), but it gets bit complicated just because you want to delegate to "default" deserializer. To do this, you would need to implement BeanDeserializerModifier, and replace standard deserializer when "modifyDeserializer" is called: your own code can retain reference to the original deserializer and delegate to it, instead of using intermediate tree model.

StaxMan
  • 113,358
  • 34
  • 211
  • 239
0

If you are not tied to jackson you could also try Genson http://code.google.com/p/genson/. In your case there are two main advantages: you will not loose in performance, it should be easier to implement. If the property event does not start with upper letter annotate it with @JsonProperty("Event") (same for the other properties starting with an upper letter). With the following code you should be done:

Genson genson = new Genson.Builder()
            .withDeserializerFactory(new EventDeserializerFactory()).create();

YourRootClass[] bean = genson.deserialize(json, YourRootClass[].class);

class EventDeserializerFactory implements Factory<Deserializer<Event>> {

    public Deserializer<Event> create(Type type, Genson genson) {
        return new EventDeserializer(genson.getBeanDescriptorFactory().provide(Event.class,
                genson));
    }

}

class EventDeserializer implements Deserializer<Event> {
    private final Deserializer<Event> standardEventDeserializer;

    public EventDeserializer(Deserializer<Event> standardEventDeserializer) {
        this.standardEventDeserializer = standardEventDeserializer;
    }

    public Event deserialize(ObjectReader reader, Context ctx) throws TransformationException,
            IOException {
        if (ValueType.ARRAY == reader.getValueType()) {
            reader.beginArray().endArray();
            return null;
        }
        return standardEventDeserializer.deserialize(reader, ctx);
    }
}
eugen
  • 5,856
  • 2
  • 29
  • 26
  • Thanks for the answer. will definitely give it a shot. But, atm I will first try improving performance with jackson. – vKashyap Nov 02 '12 at 04:17