Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Gson custom serialization

I wish to have a custom GSON deserializer such that whenever it is deserializing a JSON object (i.e. anything within curly brackets { ... }), it will look for a $type node and deserialize using its inbuilt deserializing capability to that type. If no $type object is found, it just does what it normal does.

So for example, I would want this to work:

{
    "$type": "my.package.CustomMessage"
    "payload" : {
        "$type": "my.package.PayloadMessage",
        "key": "value"
    }
}

public class CustomMessage {

    public Object payload;
}

public class PayloadMessage implements Payload {

    public String key;
}

Calling: Object customMessage = gson.fromJson(jsonString, Object.class).

So currently if I change the payload type to the Payload interface:

public class CustomMessage {

    public Payload payload;
}

Then the following TypeAdapaterFactory will do what I want:

final TypeAdapter<T> delegate = gson.getDelegateAdapter(this, type);
final TypeAdapter<JsonElement> elementAdapter = gson.getAdapter(JsonElement.class);
final PojoTypeAdapter thisAdapter = this;

public T read(JsonReader reader) throws IOException {

    JsonElement jsonElement = (JsonElement)elementAdapter.read(reader);

    if (!jsonElement.isJsonObject()) {
        return delegate.fromJsonTree(jsonElement);
    }

    JsonObject jsonObject = jsonElement.getAsJsonObject();
    JsonElement typeElement = jsonObject.get("$type");

    if (typeElement == null) {
        return delegate.fromJsonTree(jsonElement);
    }

    try {
        return (T) gson.getDelegateAdapter(
                thisAdapter,
                TypeToken.get(Class.forName(typeElement.getAsString()))).fromJsonTree(jsonElement);
    } catch (ClassNotFoundException ex) {
        throw new IOException(ex.getMessage());
    }
}

However, I would like it to work when payload is of type Object or any type for that matter, and throw some sort of type match exception if it can't assign the variable.

like image 796
Cheetah Avatar asked May 16 '14 13:05

Cheetah


People also ask

How do you serialize with Gson?

Serialization in the context of Gson means converting a Java object to its JSON representation. In order to do the serialization, we need to create the Gson object, which handles the conversion. Next, we need to call the function toJson() and pass the User object. Program output.

Does Gson need serializable?

No, it is not necessary. Gson use reflection in order to produce the desired json. You should implements Serializable when: save it on disk.

What is Gson serialization and Deserialization?

Gson can serialize a collection of arbitrary objects but can't deserialize the data without additional information. That's because there's no way for the user to indicate the type of the resulting object. Instead, while deserializing, the Collection must be of a specific, generic type.

What does Gson toJson do?

Gson is a Java library that can be used to convert Java objects into their JSON representation. It can also be used to convert a JSON string to an equivalent Java object.


2 Answers

Looking at the source for Gson, I have found what I think is the issue:

// built-in type adapters that cannot be overridden
factories.add(TypeAdapters.JSON_ELEMENT_FACTORY);
factories.add(ObjectTypeAdapter.FACTORY);

// user's type adapters
factories.addAll(typeAdapterFactories);

As you can see the ObjectTypeAdapter will take precedence over my factory.

The only solution as far as I can see is to use reflection to remove the ObjectTypeAdapter from the list or insert my factory before it. I have done this and it works.

like image 195
Cheetah Avatar answered Oct 23 '22 10:10

Cheetah


I don't know how you can achieve it with Gson but you have such a feature in Genson by default.

To enable it just do:

Genson genson = new Genson.Builder().setWithClassMetadata(true).create();

You can also register aliases for your class names:

Genson genson = new Genson.Builder().addAlias("myClass", my.package.SomeClass.class).create();

This has however some limitations:

  • at the moment you can't change the key used to identify the type, it is @class
  • it must be present in your json before the other properties - but looks fine as it is the case in your examples
  • Works only with json objects and not arrays or litterals
like image 37
eugen Avatar answered Oct 23 '22 09:10

eugen