user291701
user291701

Reputation: 39711

How to use a cached json string with gson?

I have a class that holds onto a cached json string:

class Foo {
    private transient String cachedJson; // value = [ "john", "mary" ]
    private List<String> names;
}

when I go to serialize an instance of the Foo class using gson, is there a way I could have a custom serializer, and just write the cached string for the "names" field instead of having the serializer re-serialize everything again? Something like:

public class CustomParserFoo implements 
JsonSerializer<Foo> 
{
    @Override
    public JsonElement serialize(Foo src, 
                                 Type typeOfSrc, 
                                 JsonSerializationContext context) 
    {
        JsonObject element = new JsonObject();
        if (src.cachedJson != null) {
            // cached value present, use it.
            element.addProperty("names", src.cachedJson); 
        } else {
            // have to really serialize it.
            element.addProperty("names", ...); 
        }
        return element;
    }
}

The problem with the above is that gson will try to escape the string you supply (which is already json-escaped).

The use case is something like having a very large object, and only one field might change, and you have to serialize it often - so it'd be nice to not have to re-serialize the entire object, only the attributes that have changed.

Thanks

Upvotes: 1

Views: 2098

Answers (2)

Rafael Sanches
Rafael Sanches

Reputation: 1823

In our service, we were spending 200ms per request just doing gson serialization of cached objects. We changed our architecture to cache the json strings and return them directly.

You would have to repackage the gson library and create your own:

package repackaged.com.google.gson;

public class JsonElementCache extends JsonElement {
    String cache;
    public JsonElementCache(String cache) {
        this.cache = cache;
    }

    public String getCache() {
        return cache;
    }

    @Override
    JsonElement deepCopy() {
        return new JsonElementCache(this.cache);
    }
}

this will allow you to then change this line in the TypeAdapters class:

 @Override public void write(JsonWriter out, JsonElement value) throws IOException {
      if (value == null || value.isJsonNull()) {
      if (value instanceof JsonElementCache) {
        out.writeCached(((JsonElementCache) value).getCache());
      }
      else if (value == null || value.isJsonNull()) {

Also this in JsonWriter.class:

public void writeCached(String str) {
    try {
        writeDeferredName();
        beforeValue(false);
        out.write(str);
    }
    catch (Exception e) {
        e.printStackTrace();
    }
}

Hope it makes sense.

Upvotes: 0

Brian Roach
Brian Roach

Reputation: 76908

When serializing using Gson, you're creating a parse tree and Gson does the writing; unfortunately it does not offer the feature of telling it to pass through a field in your class because it's already JSON.

The way you'd have to do it is use the com.google.gson.JsonParser class to give you a JsonElement:

JsonElement cachedArray = new JsonParser().parse(src.cachedJson);
element.addProperty("names", cachedArray);

Because this means you're deserializing the cached JSON so that gson can later write out the whole parse tree, it may make more sense to cache your JSON as a JsonArray instead.

Edit to add: Worth mentioning is that the Jackson JSON parser does offer this serialization feature. You can annotate a field or method with @JsonRawValue and it passes it through.

Upvotes: 1

Related Questions